Placeholder in UITextView

前端 未结 30 2825
野趣味
野趣味 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:21

    If someone needs a Solution for Swift:

    Add UITextViewDelegate to your class

    var placeHolderText = "Placeholder Text..."
    
    override func viewDidLoad() {
        super.viewDidLoad()
        textView.delegate = self
    }
    
    func textViewShouldBeginEditing(textView: UITextView) -> Bool {
    
        self.textView.textColor = .black
    
        if(self.textView.text == placeHolderText) {
            self.textView.text = ""
        }
    
        return true
    }
    
    func textViewDidEndEditing(textView: UITextView) {
        if(textView.text == "") {
            self.textView.text = placeHolderText
            self.textView.textColor = .lightGray
        }
    }
    
    override func viewWillAppear(animated: Bool) {
    
        if(currentQuestion.answerDisplayValue == "") {
            self.textView.text = placeHolderText
            self.textView.textColor = .lightGray
        } else {
            self.textView.text = "xxx" // load default text / or stored 
            self.textView.textColor = .black
        }
    }
    
    0 讨论(0)
  • 2020-11-22 16:23

    This mimics UITextField's placeholder perfectly, where the place holder text stays until you actually type something.

    private let placeholder = "Type here"
    
    @IBOutlet weak var textView: UITextView! {
        didSet {
            textView.textColor = UIColor.lightGray
            textView.text = placeholder
            textView.selectedRange = NSRange(location: 0, length: 0)
        }
    }
    
    extension ViewController: UITextViewDelegate {
    
        func textViewDidChangeSelection(_ textView: UITextView) {
            // Move cursor to beginning on first tap
            if textView.text == placeholder {
                textView.selectedRange = NSRange(location: 0, length: 0)
            }
        }
    
        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            if textView.text == placeholder && !text.isEmpty {
                textView.text = nil
                textView.textColor = UIColor.black
                textView.selectedRange = NSRange(location: 0, length: 0)
            }
            return true
        }
    
        func textViewDidChange(_ textView: UITextView) {
            if textView.text.isEmpty {
                textView.textColor = UIColor.lightGray
                textView.text = placeholder
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 16:24

    This thread has had plenty of answers, but here's the version I prefer.

    It extends the existing UITextView class so is easily reuseable, and it doesn't intercept the events like textViewDidChange (which might break user's code, if they were already intercepting these events elsewhere).

    Using my code (shown below), you can easily add a placeholder to any of your UITextViews like this:

    self.textViewComments.placeholder = @"(Enter some comments here.)";
    

    When you set this new placeholder value, it quietly adds a UILabel on top of your UITextView, then hide/shows it as necessary:

    enter image description here

    Okay, to make these changes, add a "UITextViewHelper.h" file containing this code:

    //  UITextViewHelper.h
    //  Created by Michael Gledhill on 13/02/15.
    
    #import <Foundation/Foundation.h>
    
    @interface UITextView (UITextViewHelper)
    
    @property (nonatomic, strong) NSString* placeholder;
    @property (nonatomic, strong) UILabel* placeholderLabel;
    @property (nonatomic, strong) NSString* textValue;
    
    -(void)checkIfNeedToDisplayPlaceholder;
    
    @end
    

    ...and a UITextViewHelper.m file containing this:

    //  UITextViewHelper.m
    //  Created by Michael Gledhill on 13/02/15.
    //
    //  This UITextView category allows us to easily display a PlaceHolder string in our UITextView.
    //  The downside is that, your code needs to set the "textValue" rather than the "text" value to safely set the UITextView's text.
    //
    #import "UITextViewHelper.h"
    #import <objc/runtime.h>
    
    @implementation UITextView (UITextViewHelper)
    
    #define UI_PLACEHOLDER_TEXT_COLOR [UIColor colorWithRed:170.0/255.0 green:170.0/255.0 blue:170.0/255.0 alpha:1.0]
    
    @dynamic placeholder;
    @dynamic placeholderLabel;
    @dynamic textValue;
    
    -(void)setTextValue:(NSString *)textValue
    {
        //  Change the text of our UITextView, and check whether we need to display the placeholder.
        self.text = textValue;
        [self checkIfNeedToDisplayPlaceholder];
    }
    -(NSString*)textValue
    {
        return self.text;
    }
    
    -(void)checkIfNeedToDisplayPlaceholder
    {
        //  If our UITextView is empty, display our Placeholder label (if we have one)
        if (self.placeholderLabel == nil)
            return;
    
        self.placeholderLabel.hidden = (![self.text isEqualToString:@""]);
    }
    
    -(void)onTap
    {
        //  When the user taps in our UITextView, we'll see if we need to remove the placeholder text.
        [self checkIfNeedToDisplayPlaceholder];
    
        //  Make the onscreen keyboard appear.
        [self becomeFirstResponder];
    }
    
    -(void)keyPressed:(NSNotification*)notification
    {
        //  The user has just typed a character in our UITextView (or pressed the delete key).
        //  Do we need to display our Placeholder label ?
       [self checkIfNeedToDisplayPlaceholder];
    }
    
    #pragma mark - Add a "placeHolder" string to the UITextView class
    
    NSString const *kKeyPlaceHolder = @"kKeyPlaceHolder";
    -(void)setPlaceholder:(NSString *)_placeholder
    {
        //  Sets our "placeholder" text string, creates a new UILabel to contain it, and modifies our UITextView to cope with
        //  showing/hiding the UILabel when needed.
        objc_setAssociatedObject(self, &kKeyPlaceHolder, (id)_placeholder, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
        self.placeholderLabel = [[UILabel alloc] initWithFrame:self.frame];
        self.placeholderLabel.numberOfLines = 1;
        self.placeholderLabel.text = _placeholder;
        self.placeholderLabel.textColor = UI_PLACEHOLDER_TEXT_COLOR;
        self.placeholderLabel.backgroundColor = [UIColor clearColor];
        self.placeholderLabel.userInteractionEnabled = true;
        self.placeholderLabel.font = self.font;
        [self addSubview:self.placeholderLabel];
    
        [self.placeholderLabel sizeToFit];
    
        //  Whenever the user taps within the UITextView, we'll give the textview the focus, and hide the placeholder if necessary.
        [self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(onTap)]];
    
        //  Whenever the user types something in the UITextView, we'll see if we need to hide/show the placeholder label.
        [[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(keyPressed:) name:UITextViewTextDidChangeNotification object:nil];
    
        [self checkIfNeedToDisplayPlaceholder];
    }
    -(NSString*)placeholder
    {
        //  Returns our "placeholder" text string
        return objc_getAssociatedObject(self, &kKeyPlaceHolder);
    }
    
    #pragma mark - Add a "UILabel" to this UITextView class
    
    NSString const *kKeyLabel = @"kKeyLabel";
    -(void)setPlaceholderLabel:(UILabel *)placeholderLabel
    {
        //  Stores our new UILabel (which contains our placeholder string)
        objc_setAssociatedObject(self, &kKeyLabel, (id)placeholderLabel, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
        [[NSNotificationCenter defaultCenter] addObserver:self selector: @selector(keyPressed:) name:UITextViewTextDidChangeNotification object:nil];
    
        [self checkIfNeedToDisplayPlaceholder];
    }
    -(UILabel*)placeholderLabel
    {
        //  Returns our new UILabel
        return objc_getAssociatedObject(self, &kKeyLabel);
    }
    @end
    

    Yup, it's a lot of code, but once you've added it to your project and included the .h file...

    #import "UITextViewHelper.h"
    

    ...you can easily use placeholders in UITextViews.

    There's one gotcha though.

    If you do this:

    self.textViewComments.placeholder = @"(Enter some comments here.)";
    self.textViewComments.text = @"Ooooh, hello there";
    

    ...the placeholder will appear on top of the text. When you set the text value, none of the regular notifications gets called, so I couldn't work out how to call my function to decide whether to show/hide the placeholder.

    The solution is to set the textValue rather than text:

    self.textViewComments.placeholder = @"(Enter some comments here.)";
    self.textViewComments.textValue = @"Ooooh, hello there";
    

    Alternatively, you can set the text value, then call checkIfNeedToDisplayPlaceholder.

    self.textViewComments.text = @"Ooooh, hello there";
    [self.textViewComments checkIfNeedToDisplayPlaceholder];
    

    I like solutions like this, as they "fill the gap" between what Apple provides us with, and what we (as developers) actually need in our apps. You write this code once, add it to your library of "helper" .m/.h files, and, over time, the SDK actually starts becoming less frustrating.

    (I wrote a similar helper for adding a "clear" button to my UITextViews, another thing which annoyingly exists in UITextField but not in UITextView...)

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

    Below is a Swift port of "SAMTextView" ObjC code posted as one of the first handful of replies to the question. I tested it on iOS 8. I tweaked a couple of things, including the bounds offset for the placement of the placeholder text, as the original was too high and too far right (used suggestion in one of the comments to that post).

    I know there are a lot of simple solutions, but I like the approach of subclassing UITextView because it's reusable and I don't have to clutter classes utilizing it with the mechanisms.

    Swift 2.2:

    import UIKit
    
    class PlaceholderTextView: UITextView {
    
        @IBInspectable var placeholderColor: UIColor = UIColor.lightGrayColor()
        @IBInspectable var placeholderText: String = ""
    
        override var font: UIFont? {
            didSet {
                setNeedsDisplay()
            }
        }
    
        override var contentInset: UIEdgeInsets {
            didSet {
                setNeedsDisplay()
            }
        }
    
        override var textAlignment: NSTextAlignment {
            didSet {
                setNeedsDisplay()
            }
        }
    
        override var text: String? {
            didSet {
                setNeedsDisplay()
            }
        }
    
        override var attributedText: NSAttributedString? {
            didSet {
                setNeedsDisplay()
            }
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setUp()
        }
    
        override init(frame: CGRect, textContainer: NSTextContainer?) {
            super.init(frame: frame, textContainer: textContainer)
        }
    
        private func setUp() {
            NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PlaceholderTextView.textChanged(_:)),
                                                             name: UITextViewTextDidChangeNotification, object: self)
        }
    
        func textChanged(notification: NSNotification) {
            setNeedsDisplay()
        }
    
        func placeholderRectForBounds(bounds: CGRect) -> CGRect {
            var x = contentInset.left + 4.0
            var y = contentInset.top  + 9.0
            let w = frame.size.width - contentInset.left - contentInset.right - 16.0
            let h = frame.size.height - contentInset.top - contentInset.bottom - 16.0
    
            if let style = self.typingAttributes[NSParagraphStyleAttributeName] as? NSParagraphStyle {
                x += style.headIndent
                y += style.firstLineHeadIndent
            }
            return CGRect(x: x, y: y, width: w, height: h)
        }
    
        override func drawRect(rect: CGRect) {
            if text!.isEmpty && !placeholderText.isEmpty {
                let paragraphStyle = NSMutableParagraphStyle()
                paragraphStyle.alignment = textAlignment
                let attributes: [ String: AnyObject ] = [
                    NSFontAttributeName : font!,
                    NSForegroundColorAttributeName : placeholderColor,
                    NSParagraphStyleAttributeName  : paragraphStyle]
    
                placeholderText.drawInRect(placeholderRectForBounds(bounds), withAttributes: attributes)
            }
            super.drawRect(rect)
        }
    }
    

    Swift 4.2:

    import UIKit
    
    class PlaceholderTextView: UITextView {
    
        @IBInspectable var placeholderColor: UIColor = UIColor.lightGray
        @IBInspectable var placeholderText: String = ""
    
        override var font: UIFont? {
            didSet {
                setNeedsDisplay()
            }
        }
    
        override var contentInset: UIEdgeInsets {
            didSet {
                setNeedsDisplay()
            }
        }
    
        override var textAlignment: NSTextAlignment {
            didSet {
                setNeedsDisplay()
            }
        }
    
        override var text: String? {
            didSet {
                setNeedsDisplay()
            }
        }
    
        override var attributedText: NSAttributedString? {
            didSet {
                setNeedsDisplay()
            }
        }
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            setUp()
        }
    
        override init(frame: CGRect, textContainer: NSTextContainer?) {
            super.init(frame: frame, textContainer: textContainer)
        }
    
        private func setUp() {
            NotificationCenter.default.addObserver(self,
             selector: #selector(self.textChanged(notification:)),
             name: Notification.Name("UITextViewTextDidChangeNotification"),
             object: nil)
        }
    
        @objc func textChanged(notification: NSNotification) {
            setNeedsDisplay()
        }
    
        func placeholderRectForBounds(bounds: CGRect) -> CGRect {
            var x = contentInset.left + 4.0
            var y = contentInset.top  + 9.0
            let w = frame.size.width - contentInset.left - contentInset.right - 16.0
            let h = frame.size.height - contentInset.top - contentInset.bottom - 16.0
    
            if let style = self.typingAttributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle {
                x += style.headIndent
                y += style.firstLineHeadIndent
            }
            return CGRect(x: x, y: y, width: w, height: h)
        }
    
        override func draw(_ rect: CGRect) {
            if text!.isEmpty && !placeholderText.isEmpty {
                let paragraphStyle = NSMutableParagraphStyle()
                paragraphStyle.alignment = textAlignment
                let attributes: [NSAttributedString.Key: Any] = [
                NSAttributedString.Key(rawValue: NSAttributedString.Key.font.rawValue) : font!,
                NSAttributedString.Key(rawValue: NSAttributedString.Key.foregroundColor.rawValue) : placeholderColor,
                NSAttributedString.Key(rawValue: NSAttributedString.Key.paragraphStyle.rawValue)  : paragraphStyle]
    
                placeholderText.draw(in: placeholderRectForBounds(bounds: bounds), withAttributes: attributes)
            }
            super.draw(rect)
        }
    }
    
    0 讨论(0)
  • 2020-11-22 16:27
        - (void)textViewDidChange:(UITextView *)textView
    {
        placeholderLabel.hidden = YES;
    }
    

    put a label over the textview.

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

    I recommend use pod 'UITextView+Placeholder'

    pod 'UITextView+Placeholder'
    

    on your code

    #import "UITextView+Placeholder.h"
    
    ////    
    
    UITextView *textView = [[UITextView alloc] init];
    textView.placeholder = @"How are you?";
    textView.placeholderColor = [UIColor lightGrayColor];
    
    0 讨论(0)
提交回复
热议问题