Placeholder in UITextView

前端 未结 30 2828
野趣味
野趣味 2020-11-22 16:01

My application uses an UITextView. Now I want the UITextView to have a placeholder similar to the one you can set for an UITextField.<

相关标签:
30条回答
  • 2020-11-22 16:09

    Take a look at UTPlaceholderTextView.

    This is a convenient subclass of UITextView that supports placeholder similiar to that of UITextField. Main peculiarities:

    • Does not use subviews
    • Does not override drawRect:
    • Placeholder could be of arbitrary length, and rendered just the same way as usual text
    0 讨论(0)
  • 2020-11-22 16:10

    I wasn't too happy with any of the solutions posted as they were a bit heavy. Adding views to the view isn't really ideal (especially in drawRect:). They both had leaks, which isn't acceptable either.

    Here is my solution: SAMTextView

    SAMTextView.h

    //
    //  SAMTextView.h
    //  SAMTextView
    //
    //  Created by Sam Soffes on 8/18/10.
    //  Copyright 2010-2013 Sam Soffes. All rights reserved.
    //
    
    #import <UIKit/UIKit.h>
    
    /**
     UITextView subclass that adds placeholder support like UITextField has.
     */
    @interface SAMTextView : UITextView
    
    /**
     The string that is displayed when there is no other text in the text view.
    
     The default value is `nil`.
     */
    @property (nonatomic, strong) NSString *placeholder;
    
    /**
     The color of the placeholder.
    
     The default is `[UIColor lightGrayColor]`.
     */
    @property (nonatomic, strong) UIColor *placeholderTextColor;
    
    /**
     Returns the drawing rectangle for the text views’s placeholder text.
    
     @param bounds The bounding rectangle of the receiver.
     @return The computed drawing rectangle for the placeholder text.
     */
    - (CGRect)placeholderRectForBounds:(CGRect)bounds;
    
    @end
    

    SAMTextView.m

    //
    //  SAMTextView.m
    //  SAMTextView
    //
    //  Created by Sam Soffes on 8/18/10.
    //  Copyright 2010-2013 Sam Soffes. All rights reserved.
    //
    
    #import "SAMTextView.h"
    
    @implementation SAMTextView
    
    #pragma mark - Accessors
    
    @synthesize placeholder = _placeholder;
    @synthesize placeholderTextColor = _placeholderTextColor;
    
    - (void)setText:(NSString *)string {
      [super setText:string];
      [self setNeedsDisplay];
    }
    
    
    - (void)insertText:(NSString *)string {
      [super insertText:string];
      [self setNeedsDisplay];
    }
    
    
    - (void)setAttributedText:(NSAttributedString *)attributedText {
      [super setAttributedText:attributedText];
      [self setNeedsDisplay];
    }
    
    
    - (void)setPlaceholder:(NSString *)string {
      if ([string isEqual:_placeholder]) {
        return;
      }
    
      _placeholder = string;
      [self setNeedsDisplay];
    }
    
    
    - (void)setContentInset:(UIEdgeInsets)contentInset {
      [super setContentInset:contentInset];
      [self setNeedsDisplay];
    }
    
    
    - (void)setFont:(UIFont *)font {
      [super setFont:font];
      [self setNeedsDisplay];
    }
    
    
    - (void)setTextAlignment:(NSTextAlignment)textAlignment {
      [super setTextAlignment:textAlignment];
      [self setNeedsDisplay];
    }
    
    
    #pragma mark - NSObject
    
    - (void)dealloc {
      [[NSNotificationCenter defaultCenter] removeObserver:self name:UITextViewTextDidChangeNotification object:self];
    }
    
    
    #pragma mark - UIView
    
    - (id)initWithCoder:(NSCoder *)aDecoder {
      if ((self = [super initWithCoder:aDecoder])) {
        [self initialize];
      }
      return self;
    }
    
    
    - (id)initWithFrame:(CGRect)frame {
      if ((self = [super initWithFrame:frame])) {
        [self initialize];
      }
      return self;
    }
    
    
    - (void)drawRect:(CGRect)rect {
      [super drawRect:rect];
    
      if (self.text.length == 0 && self.placeholder) {
        rect = [self placeholderRectForBounds:self.bounds];
    
        UIFont *font = self.font ? self.font : self.typingAttributes[NSFontAttributeName];
    
        // Draw the text
        [self.placeholderTextColor set];
        [self.placeholder drawInRect:rect withFont:font lineBreakMode:NSLineBreakByTruncatingTail alignment:self.textAlignment];
      }
    }
    
    
    #pragma mark - Placeholder
    
    - (CGRect)placeholderRectForBounds:(CGRect)bounds {
      // Inset the rect
      CGRect rect = UIEdgeInsetsInsetRect(bounds, self.contentInset);
    
      if (self.typingAttributes) {
        NSParagraphStyle *style = self.typingAttributes[NSParagraphStyleAttributeName];
        if (style) {
          rect.origin.x += style.headIndent;
          rect.origin.y += style.firstLineHeadIndent;
        }
      }
    
      return rect;
    }
    
    
    #pragma mark - Private
    
    - (void)initialize {
      [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:self];
    
      self.placeholderTextColor = [UIColor colorWithWhite:0.702f alpha:1.0f];
    }
    
    
    - (void)textChanged:(NSNotification *)notification {
      [self setNeedsDisplay];
    }
    
    @end
    

    It's a lot simpler than the others, as it doesn't use subviews (or have leaks). Feel free to use it.

    Update 11/10/11: It is now documented and supports use in Interface Builder.

    Update 11/24/13: Point to new repo.

    0 讨论(0)
  • 2020-11-22 16:10

    You could also create a new class TextViewWithPlaceholder as a subclass of UITextView.

    (This code is kind of rough -- but I think it's on the right track.)

    @interface TextViewWithPlaceholder : UITextView
    {
    
        NSString *placeholderText;  // make a property
        UIColor *placeholderColor;  // make a property
        UIColor *normalTextColor;   // cache text color here whenever you switch to the placeholderColor
    }
    
    - (void) setTextColor: (UIColor*) color
    {
       normalTextColor = color;
       [super setTextColor: color];
    }
    
    - (void) updateForTextChange
    {
        if ([self.text length] == 0)
        { 
            normalTextColor = self.textColor;
            self.textColor = placeholderColor;
            self.text = placeholderText;
        }
        else
        {
            self.textColor = normalTextColor;
        }
    
    }
    

    In your delegate, add this:

    - (void)textViewDidChange:(UITextView *)textView
    {
        if ([textView respondsToSelector: @selector(updateForTextChange)])
        {
            [textView updateForTextChange];
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 16:12

    I read through all of these, but came up with a very short, Swift 3, solution that has worked in all of my tests. It could stand a little more generality, but the process is simple. Here's the entire thing which I call "TextViewWithPlaceholder".

    import UIKit
    
    class TextViewWithPlaceholder: UITextView {
    
        public var placeholder: String?
        public var placeholderColor = UIColor.lightGray
    
        private var placeholderLabel: UILabel?
    
        // Set up notification listener when created from a XIB or storyboard.
        // You can also set up init() functions if you plan on creating
        // these programmatically.
        override func awakeFromNib() {
            super.awakeFromNib()
    
            NotificationCenter.default.addObserver(self,
                                               selector: #selector(TextViewWithPlaceholder.textDidChangeHandler(notification:)),
                                               name: .UITextViewTextDidChange,
                                               object: self)
    
            placeholderLabel = UILabel()
            placeholderLabel?.alpha = 0.85
            placeholderLabel?.textColor = placeholderColor
        }
    
        // By using layoutSubviews, you can size and position the placeholder
        // more accurately. I chose to hard-code the size of the placeholder
        // but you can combine this with other techniques shown in previous replies.
        override func layoutSubviews() {
            super.layoutSubviews()
    
            placeholderLabel?.textColor = placeholderColor
            placeholderLabel?.text = placeholder
    
            placeholderLabel?.frame = CGRect(x: 6, y: 4, width: self.bounds.size.width-16, height: 24)
    
            if text.isEmpty {
                addSubview(placeholderLabel!)
                bringSubview(toFront: placeholderLabel!)
            } else {
                placeholderLabel?.removeFromSuperview()
            }
        }
    
        // Whenever the text changes, just trigger a new layout pass.
        func textDidChangeHandler(notification: Notification) {
            layoutSubviews()
        }
    }
    
    0 讨论(0)
  • 2020-11-22 16:13

    Here's yet another way to do it, one that reproduces the slight indentation of UITextField's placeholder:

    Drag a UITextField right under the UITextView so that their top left corners are aligned. Add your placeholder text to the text field.

    In viewDidLoad, add:

    [tView setDelegate:self];
    tView.contentInset = UIEdgeInsetsMake(-8,-8,0,0);
    tView.backgroundColor = [UIColor clearColor];
    

    Then add:

    - (void)textViewDidChange:(UITextView *)textView {
        if (textView.text.length == 0) {
            textView.backgroundColor = [UIColor clearColor];            
        } else {
            textView.backgroundColor = [UIColor whiteColor];
        }
    }
    
    0 讨论(0)
  • 2020-11-22 16:13

    I have wrote a class in swift. You can import this class whenever required.

    import UIKit
    

    public class CustomTextView: UITextView {

    private struct Constants {
        static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
    }
    private let placeholderLabel: UILabel = UILabel()
    
    private var placeholderLabelConstraints = [NSLayoutConstraint]()
    
    @IBInspectable public var placeholder: String = "" {
        didSet {
            placeholderLabel.text = placeholder
        }
    }
    
    @IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor {
        didSet {
            placeholderLabel.textColor = placeholderColor
        }
    }
    
    override public var font: UIFont! {
        didSet {
            placeholderLabel.font = font
        }
    }
    
    override public var textAlignment: NSTextAlignment {
        didSet {
            placeholderLabel.textAlignment = textAlignment
        }
    }
    
    override public var text: String! {
        didSet {
            textDidChange()
        }
    }
    
    override public var attributedText: NSAttributedString! {
        didSet {
            textDidChange()
        }
    }
    
    override public var textContainerInset: UIEdgeInsets {
        didSet {
            updateConstraintsForPlaceholderLabel()
        }
    }
    
    override public init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        commonInit()
    }
    
    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        commonInit()
    }
    
    private func commonInit() {
        NSNotificationCenter.defaultCenter().addObserver(self,
                                                         selector: #selector(textDidChange),
                                                         name: UITextViewTextDidChangeNotification,
                                                         object: nil)
    
        placeholderLabel.font = font
        placeholderLabel.textColor = placeholderColor
        placeholderLabel.textAlignment = textAlignment
        placeholderLabel.text = placeholder
        placeholderLabel.numberOfLines = 0
        placeholderLabel.backgroundColor = UIColor.clearColor()
        placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(placeholderLabel)
        updateConstraintsForPlaceholderLabel()
    }
    
    private func updateConstraintsForPlaceholderLabel() {
        var newConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
                                                                            options: [],
                                                                            metrics: nil,
                                                                            views: ["placeholder": placeholderLabel])
        newConstraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|-(\(textContainerInset.top))-[placeholder]",
                                                                         options: [],
                                                                         metrics: nil,
                                                                         views: ["placeholder": placeholderLabel])
        newConstraints.append(NSLayoutConstraint(
            item: placeholderLabel,
            attribute: .Width,
            relatedBy: .Equal,
            toItem: self,
            attribute: .Width,
            multiplier: 1.0,
            constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
            ))
        removeConstraints(placeholderLabelConstraints)
        addConstraints(newConstraints)
        placeholderLabelConstraints = newConstraints
    }
    
    @objc private func textDidChange() {
        placeholderLabel.hidden = !text.isEmpty
    }
    
    public override func layoutSubviews() {
        super.layoutSubviews()
        placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
    }
    
    deinit {
        NSNotificationCenter.defaultCenter().removeObserver(self,
                                                            name: UITextViewTextDidChangeNotification,
                                                            object: nil)
    }
    

    }

    0 讨论(0)
提交回复
热议问题