如何在UITextView中添加占位符,类似于你可以为UITextField设置的占位符,在Swift中?


当前回答


浮动的占位符


将占位符标签放置在文本视图之上,通过跟踪文本视图字符计数的变化来设置其字体、颜色和管理占位符可见性,这是简单、安全且可靠的。

斯威夫特5:

class NotesViewController : UIViewController {

    @IBOutlet var textView : UITextView!
    var placeholderLabel : UILabel!
        
    override func viewDidLoad() {
        super.viewDidLoad()
    
        textView.delegate = self
        placeholderLabel = UILabel()
        placeholderLabel.text = "Enter some text..."
        placeholderLabel.font = .italicSystemFont(ofSize: (textView.font?.pointSize)!)
        placeholderLabel.sizeToFit()
        textView.addSubview(placeholderLabel)
        placeholderLabel.frame.origin = CGPoint(x: 5, y: (textView.font?.pointSize)! / 2)
        placeholderLabel.textColor = .tertiaryLabel
        placeholderLabel.isHidden = !textView.text.isEmpty
    }
}

extension NotesViewController : UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        placeholderLabel.isHidden = !textView.text.isEmpty
    }
}

其他回答

不,没有任何占位符可用的textview。你必须把标签上面,当用户进入textview,然后隐藏它或设置默认值时,用户输入删除所有值。

我喜欢@nerdist的解决方案。在此基础上,我创建了UITextView的扩展:

import Foundation
import UIKit

extension UITextView
{
  private func add(_ placeholder: UILabel) {
    for view in self.subviews {
        if let lbl = view as? UILabel  {
            if lbl.text == placeholder.text {
                lbl.removeFromSuperview()
            }
        }
    }
    self.addSubview(placeholder)
  }

  func addPlaceholder(_ placeholder: UILabel?) {
    if let ph = placeholder {
      ph.numberOfLines = 0  // support for multiple lines
      ph.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
      ph.sizeToFit()
      self.add(ph)
      ph.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
      ph.textColor = UIColor(white: 0, alpha: 0.3)
      updateVisibility(ph)
    }
  }

  func updateVisibility(_ placeHolder: UILabel?) {
    if let ph = placeHolder {
      ph.isHidden = !self.text.isEmpty
    }
  }
}

例如,在ViewController类中,我是这样使用它的:

class MyViewController: UIViewController, UITextViewDelegate {
  private var notePlaceholder: UILabel!
  @IBOutlet weak var txtNote: UITextView!
  ...
  // UIViewController
  override func viewDidLoad() {
    notePlaceholder = UILabel()
    notePlaceholder.text = "title\nsubtitle\nmore..."
    txtNote.addPlaceholder(notePlaceholder)
    ...
  }

  // UITextViewDelegate
  func textViewDidChange(_ textView: UITextView) {
    txtNote.updateVisbility(notePlaceholder)
    ...
  }

UITextview的占位符!

更新:

如果你在代码中改变textview的文本,记得调用updateVisibitly方法来隐藏占位符:

txtNote.text = "something in code"
txtNote.updateVisibility(self.notePlaceholder) // hide placeholder if text is not empty.

为了防止占位符被添加多次,在扩展中添加了一个私有add()函数。

另一个解决方案是使用keyboardWillHide和keyboardWillShow通知,就像我做的那样。

首先,您需要分别处理viewWillAppear和viewWillAppear方法中的侦听和取消侦听通知(以处理内存泄漏)。

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    setupKeyboardNotificationListeners(enable: true)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    setupKeyboardNotificationListeners(enable: false)
}

然后是处理监听/取消监听通知的方法:

private func setupKeyboardNotificationListeners(enable: Bool) {
        if enable {
            NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
            NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
        } else {
            NotificationCenter.default.removeObserver(self)
        }
    }

然后在keyboardWillHide和keyboardWillShow的两个方法中,你处理文本的占位符和颜色变化。

@objc func keyboardWillShow(notification: NSNotification) {
    if self.textView.text == self.placeholder {
        self.textView.text = ""
        self.textView.textColor = .black
    }
}

@objc func keyboardWillHide(notification: NSNotification) {
    if self.textView.text.isEmpty {
        self.textView.text = self.placeholder
        self.textView.textColor = .lightGrey
    }
}

我发现这个解决方案是目前为止最好的,因为文本会在键盘出现时立即删除,而不是在用户开始输入时删除,这可能会导致混乱。

还有一个解决方案(Swift 3):

import UIKit

protocol PlaceholderTextViewDelegate {
    func placeholderTextViewDidChangeText(_ text:String)
    func placeholderTextViewDidEndEditing(_ text:String)
}

final class PlaceholderTextView: UITextView {

    var notifier:PlaceholderTextViewDelegate?

    var placeholder: String? {
        didSet {
            placeholderLabel?.text = placeholder
        }
    }
    var placeholderColor = UIColor.lightGray
    var placeholderFont = UIFont.appMainFontForSize(14.0) {
        didSet {
            placeholderLabel?.font = placeholderFont
        }
    }

    fileprivate var placeholderLabel: UILabel?

    // MARK: - LifeCycle

    init() {
        super.init(frame: CGRect.zero, textContainer: nil)
        awakeFromNib()
    }

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

    override func awakeFromNib() {
        super.awakeFromNib()

        self.delegate = self
        NotificationCenter.default.addObserver(self, selector: #selector(PlaceholderTextView.textDidChangeHandler(notification:)), name: .UITextViewTextDidChange, object: nil)

        placeholderLabel = UILabel()
        placeholderLabel?.textColor = placeholderColor
        placeholderLabel?.text = placeholder
        placeholderLabel?.textAlignment = .left
        placeholderLabel?.numberOfLines = 0
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        placeholderLabel?.font = placeholderFont

        var height:CGFloat = placeholderFont.lineHeight
        if let data = placeholderLabel?.text {

            let expectedDefaultWidth:CGFloat = bounds.size.width
            let fontSize:CGFloat = placeholderFont.pointSize

            let textView = UITextView()
            textView.text = data
            textView.font = UIFont.appMainFontForSize(fontSize)
            let sizeForTextView = textView.sizeThatFits(CGSize(width: expectedDefaultWidth,
                                                               height: CGFloat.greatestFiniteMagnitude))
            let expectedTextViewHeight = sizeForTextView.height

            if expectedTextViewHeight > height {
                height = expectedTextViewHeight
            }
        }

        placeholderLabel?.frame = CGRect(x: 5, y: 0, width: bounds.size.width - 16, height: height)

        if text.isEmpty {
            addSubview(placeholderLabel!)
            bringSubview(toFront: placeholderLabel!)
        } else {
            placeholderLabel?.removeFromSuperview()
        }
    }

    func textDidChangeHandler(notification: Notification) {
        layoutSubviews()
    }

}

extension PlaceholderTextView : UITextViewDelegate {
    // MARK: - UITextViewDelegate
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        if(text == "\n") {
            textView.resignFirstResponder()
            return false
        }
        return true
    }

    func textViewDidChange(_ textView: UITextView) {
        notifier?.placeholderTextViewDidChangeText(textView.text)
    }

    func textViewDidEndEditing(_ textView: UITextView) {
        notifier?.placeholderTextViewDidEndEditing(textView.text)
    }
}

结果

import UIKit
import RxSwift

@IBDesignable class TextViewWithPlaceholder: UITextView {
    
    //MARK: - Propertise
    @IBInspectable var placeholderText: String = ""
    
    let placeholderLabel = LocalizedUILabel()
    private let hidePlaceholderObserver = PublishSubject<Bool>()
    let disposeBag = DisposeBag()
    
    
    //MARK: - Did Move To Window
    override func didMoveToWindow() {
        super.didMoveToWindow()
        observeOnTextViewEditing()
        configurePlaceholder()
    }
    
    
    //MARK: - Observe On Text View Editing
    private func observeOnTextViewEditing() {
        rx.text.subscribe(onNext: { [weak self] selectedText in
            guard let self = self else { return }
            self.hidePlaceholderObserver.onNext((selectedText?.isEmpty ?? true) ? false : true)
            
        }).disposed(by: disposeBag)
    }
    
    
    //MARK: - Observe On Show Hide Placeholder
    private func configurePlaceholder() {
        hidePlaceholderObserver
            .bind(to: placeholderLabel.rx.isHidden)
            .disposed(by: disposeBag)
        
        placeholderLabel.text = placeholderText
        placeholderLabel.font = UIFont(name: "Poppins-Semibold", size: 16) ?? UIFont()
        placeholderLabel.textColor = .lightGray
        
        placeholderLabel.sizeToFit()
        placeholderLabel.frame.origin = CGPoint(x: 8, y: 8)
        addSubview(placeholderLabel)
    }
    
}