我的应用程序使用UITextView。现在我想让UITextView有一个占位符,类似于你可以为UITextField设置的占位符。

如何做到这一点?


当前回答

在swift 5。工作很好。

class BaseTextView: UITextView {

    // MARK: - Views
    private var placeholderLabel: UIlabel!

    // MARK: - Init
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        setupUI()
        startupSetup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupUI()
        startupSetup()
    }

    deinit {
        NotificationCenter.default.removeObserver(self)
    }    
}

// MARK: - Setup UI
private extension BaseTextView {
    func setupUI() {
        addPlaceholderLabel()

        textColor = .textColor
    }

    func addPlaceholderLabel() {
        placeholderLabel = BaseLabel(frame: .zero)
        placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
        insertSubview(placeholderLabel, at: 0)

        placeholderLabel.alpha = 0
        placeholderLabel.numberOfLines = 0
        placeholderLabel.backgroundColor = .clear
        placeholderLabel.textColor = .lightTextColor
        placeholderLabel.lineBreakMode = .byWordWrapping
        placeholderLabel.isUserInteractionEnabled = false
        placeholderLabel.font = UIFont.openSansSemibold.withSize(12)

        placeholderLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8).isActive = true
        placeholderLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 5).isActive = true
        placeholderLabel.rightAnchor.constraint(lessThanOrEqualTo: rightAnchor, constant: -8).isActive = true
        placeholderLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8).isActive = true
    }
}

// MARK: - Startup
private extension BaseTextView {
    func startupSetup() {
        addObservers()
        textChanged(nil)
        font = UIFont.openSansSemibold.withSize(12)
    }    

    func addObservers() {
        NotificationCenter.default.addObserver(self, selector: #selector(textChanged(_:)), name: UITextView.textDidChangeNotification, object: nil)
    }
}

// MARK: - Actions
private extension BaseTextView {
    @objc func textChanged(_ sender: Notification?) {
        UIView.animate(withDuration: 0.2) {
            self.placeholderLabel.alpha = self.text.count == 0 ? 1 : 0
        }    
    }
}

// MARK: - Public methods
extension BaseTextView {
    public func setPlaceholder(_ placeholder: String) {
        placeholderLabel.text = placeholder
    }
}

其他回答

    - (void)textViewDidChange:(UITextView *)textView
{
    placeholderLabel.hidden = YES;
}

在textview上放一个标签。

我找到了一个很容易模仿占位符的方法

在NIB或代码设置你的textView的textColor为lightGrayColor(大多数时候) 确保你的textView的委托链接到文件的所有者和实现UITextViewDelegate在你的头文件 将文本视图的默认文本设置为(例如:"Foobar placeholder") 实现:(BOOL) textViewShouldBeginEditing:(UITextView *)textView

编辑:

Changed if statements to compare tags rather than text. If the user deleted their text it was possible to also accidentally delete a portion of the place holder @"Foobar placeholder".This meant if the user re-entered the textView the following delegate method, -(BOOL) textViewShouldBeginEditing:(UITextView *) textView, it would not work as expected. I tried comparing by the color of the text in the if statement but found that light grey color set in interface builder is not the same as light grey color set in code with [UIColor lightGreyColor]

- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
    if(textView.tag == 0) {
        textView.text = @"";
        textView.textColor = [UIColor blackColor];
        textView.tag = 1;
    }
    return YES;
}

当键盘返回并且[textView length] == 0时,也可以重置占位符文本

编辑:

只是为了让最后一部分更清楚——下面是如何将占位符文本设置回去:

- (void)textViewDidChange:(UITextView *)textView
{
   if([textView.text length] == 0)
   {
       textView.text = @"Foobar placeholder";
       textView.textColor = [UIColor lightGrayColor];
       textView.tag = 0;
   }
}
- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
    //NSLog(@"textViewShouldBeginEditing");
    if( [tvComment.text isEqualToString:@"Comment"] && [tvComment.textColor isEqual:[UIColor lightGrayColor]] ){
        tvComment.text = @"";
        tvComment.textColor = [UIColor blackColor];
    }
    return YES;
}

- (void)keyboardWillBeHidden:(NSNotification*)aNotification{
    //NSLog(@"keyboardWillBeHidden");

    //Manage comment field placeholdertext
    if(tvComment.text.length == 0){
        tvComment.textColor = [UIColor lightGrayColor];
        tvComment.text = @"Comment";
    }
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    tvComment.textColor = [UIColor lightGrayColor];
}

TVComment是包含问题中的textView的属性。这样就可以了。

还有一个简单的答案,使用CATextLayer。

添加CATextLayer到UITextView的层。 使用UITextViewDelegate方法,简单地改变CATextLayer的颜色。

func txtViewPlaceholder() {
    let textlayer = CATextLayer()

    textlayer.frame = CGRect(x: 5, y: 5, width: 200, height: 18)
    textlayer.contentsScale = UIScreen.main.scale
    textlayer.fontSize = 12
    textlayer.alignmentMode = kCAAlignmentLeft
    textlayer.string = "Enter here"
    textlayer.isWrapped = true
    textlayer.name = "placeholder"
    textlayer.backgroundColor = UIColor.white.cgColor
    textlayer.foregroundColor = UIColor.black.cgColor

    yourTxtVw.layer.insertSublayer(textlayer, at: 0)
}

func removeAddPlaceholder(remove: Bool, textView: UITextView) {
    for layers in textView.layer.sublayers! where layers.name == "placeholder" {
        
        if remove {
            (layers as! CATextLayer).foregroundColor = UIColor.white.cgColor
        } else {
            (layers as! CATextLayer).foregroundColor = UIColor.black.cgColor
        }
        
    }
}


extension YourViewController : UITextViewDelegate {

    func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
    
        removeAddPlaceholder(remove: true, textView: textView)
    
        return true
    }

    func textViewDidEndEditing(_ textView: UITextView) {
    
        if textView.text.count <= 0 {
            removeAddPlaceholder(remove: false, textView: textView)
        }
    }

}

基于这里已经给出的一些很好的建议,我能够将以下轻量级的、与接口生成器兼容的UITextView子类组合在一起,它是:

包括可配置的占位符文本,样式就像UITextField一样。 不需要任何额外的子视图或约束。 不需要来自ViewController的任何委托或其他行为。 不需要任何通知。 保持该文本与查看字段的文本属性的任何外部类完全分离。

欢迎提出改进建议。

编辑1:更新为重置占位符格式,如果实际文本以编程方式设置。

编辑2:现在可以以编程方式检索占位符文本颜色。

斯威夫特v5:

import UIKit
@IBDesignable class TextViewWithPlaceholder: UITextView {
    
    override var text: String! { // Ensures that the placeholder text is never returned as the field's text
        get {
            if showingPlaceholder {
                return "" // When showing the placeholder, there's no real text to return
            } else { return super.text }
        }
        set {
            if showingPlaceholder {
                removePlaceholderFormatting() // If the placeholder text is what's being changed, it's no longer the placeholder
            }
            super.text = newValue
        }
    }
    @IBInspectable var placeholderText: String = ""
    @IBInspectable var placeholderTextColor: UIColor = .placeholderText
    private var showingPlaceholder: Bool = true // Keeps track of whether the field is currently showing a placeholder
    
    override func didMoveToWindow() {
        super.didMoveToWindow()
        if text.isEmpty {
            showPlaceholderText() // Load up the placeholder text when first appearing, but not if coming back to a view where text was already entered
        }
    }
    
    override public func becomeFirstResponder() -> Bool {
        
        // If the current text is the placeholder, remove it
        if showingPlaceholder {
            text = nil
            removePlaceholderFormatting()
        }
        return super.becomeFirstResponder()
    }
    
    override public func resignFirstResponder() -> Bool {
        
        // If there's no text, put the placeholder back
        if text.isEmpty {
            showPlaceholderText()
        }
        return super.resignFirstResponder()
    }
    
    private func showPlaceholderText() {
        
        text = placeholderText
        showingPlaceholder = true
        textColor = placeholderTextColor
    }
    
    private func removePlaceholderFormatting() {
        
        showingPlaceholder = false
        textColor = nil // Put the text back to the default, unmodified color
    }
}