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


当前回答

以下是我解决这个问题的方法(Swift 4):

这个想法是做出最简单的解决方案,允许使用不同颜色的占位符,调整占位符大小,不会覆盖委托,同时保持所有UITextView函数正常工作。

import UIKit

class PlaceholderTextView: UITextView {
    var placeholderColor: UIColor = .lightGray
    var defaultTextColor: UIColor = .black

    private var isShowingPlaceholder = false {
        didSet {
            if isShowingPlaceholder {
                text = placeholder
                textColor = placeholderColor
            } else {
                textColor = defaultTextColor
            }
        }
    }

    var placeholder: String? {
        didSet {
            isShowingPlaceholder = !hasText
        }
    }

    @objc private func textViewDidBeginEditing(notification: Notification) {
        textColor = defaultTextColor
        if isShowingPlaceholder { text = nil }
    }

    @objc private func textViewDidEndEditing(notification: Notification) {
        isShowingPlaceholder = !hasText
    }

    // MARK: - Construction -
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        setup()
    }

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

    private func setup() {
        NotificationCenter.default.addObserver(self, selector: #selector(textViewDidBeginEditing(notification:)), name: UITextView.textDidBeginEditingNotification, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(textViewDidEndEditing(notification:)), name: UITextView.textDidEndEditingNotification, object: nil)
    }

    // MARK: - Destruction -
    deinit { NotificationCenter.default.removeObserver(self) }
}

其他回答

我很惊讶没有人提到NSTextStorageDelegate。UITextViewDelegate的方法只能由用户交互触发,而不是以编程方式触发。例如,当你以编程方式设置一个文本视图的文本属性时,你必须自己设置占位符的可见性,因为委派方法不会被调用。

然而,使用NSTextStorageDelegate的textStorage(_:didProcessEditing:range:changeInLength:)方法,你会收到任何文本更改的通知,即使它是通过编程完成的。就像这样分配它:

textView.textStorage.delegate = self

(在UITextView中,这个委派属性默认为nil,所以它不会影响任何默认行为。)

将它与@clearlight演示的UILabel技术结合起来,可以轻松地将整个UITextView的占位符实现包装成一个扩展。

extension UITextView {

    private class PlaceholderLabel: UILabel { }

    private var placeholderLabel: PlaceholderLabel {
        if let label = subviews.compactMap( { $0 as? PlaceholderLabel }).first {
            return label
        } else {
            let label = PlaceholderLabel(frame: .zero)
            label.font = font
            addSubview(label)
            return label
        }
    }

    @IBInspectable
    var placeholder: String {
        get {
            return subviews.compactMap( { $0 as? PlaceholderLabel }).first?.text ?? ""
        }
        set {
            let placeholderLabel = self.placeholderLabel
            placeholderLabel.text = newValue
            placeholderLabel.numberOfLines = 0
            let width = frame.width - textContainer.lineFragmentPadding * 2
            let size = placeholderLabel.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
            placeholderLabel.frame.size.height = size.height
            placeholderLabel.frame.size.width = width
            placeholderLabel.frame.origin = CGPoint(x: textContainer.lineFragmentPadding, y: textContainerInset.top)

            textStorage.delegate = self
        }
    }

}

extension UITextView: NSTextStorageDelegate {

    public func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {
        if editedMask.contains(.editedCharacters) {
            placeholderLabel.isHidden = !text.isEmpty
        }
    }

}

注意,使用了一个名为PlaceholderLabel的私有(嵌套)类。它根本没有实现,但它为我们提供了一种识别占位符标签的方法,这比使用tag属性要“快捷”得多。

使用这种方法,你仍然可以将UITextView的委托分配给其他人。

你甚至不需要改变文本视图的类。只要添加扩展,你就可以为项目中的每个UITextView分配一个占位符字符串,甚至在接口生成器中也是如此。

出于清晰的原因,我省略了placeholderColor属性的实现,但是它可以用与placeholder类似的计算变量在多几行中实现。

迅速:

添加你的TextView @IBOutlet:

@IBOutlet weak var txtViewMessage: UITextView!

在viewWillAppear方法中,添加以下内容:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    txtViewMessage.delegate = self    // Give TextViewMessage delegate Method
    
    txtViewMessage.text = "Place Holder Name"
    txtViewMessage.textColor = UIColor.lightGray
}

请添加委托使用扩展(UITextViewDelegate):

// MARK: - UITextViewDelegate
extension ViewController: UITextViewDelegate {

    func textViewDidBeginEditing(_ textView: UITextView) {

        if !txtViewMessage.text!.isEmpty && txtViewMessage.text! == "Place Holder Name" {
            txtViewMessage.text = ""
            txtViewMessage.textColor = UIColor.black
        }
    }

    func textViewDidEndEditing(_ textView: UITextView) {
    
        if txtViewMessage.text.isEmpty {
            txtViewMessage.text = "Place Holder Name"
            txtViewMessage.textColor = UIColor.lightGray
        }
    }
}

迅速的回答

这是一个自定义类,用于动画占位符。

class CustomTextView: UITextView {
    
    //   MARK: - public
    
    public var placeHolderText: String? = "Enter Reason.."
    
    public lazy var placeHolderLabel: UILabel! = {
        let placeHolderLabel = UILabel(frame: .zero)
        placeHolderLabel.numberOfLines = 0
        placeHolderLabel.backgroundColor = .clear
        placeHolderLabel.alpha = 0.5
        return placeHolderLabel
    }()
    
    //   MARK: - Init
    
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        enableNotifications()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        enableNotifications()
    }
    
    func setup() {
        placeHolderLabel.frame = CGRect(x: 8, y: 8, width: self.bounds.size.width - 16, height: 15)
        placeHolderLabel.sizeToFit()
    }
    
    //   MARK: - Cycle
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 8)
        returnKeyType = .done
        addSubview(placeHolderLabel)
        placeHolderLabel.frame = CGRect(x: 8, y: 8, width: self.bounds.size.width - 16, height: 15)
        placeHolderLabel.textColor = textColor
        placeHolderLabel.font = font
        placeHolderLabel.text = placeHolderText
        bringSubviewToFront(placeHolderLabel)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        setup()
    }
    
    //   MARK: - Notifications
    
    private func enableNotifications() {
        NotificationCenter.default.addObserver(self, selector: #selector(textDidChangeNotification(_:)), name: UITextView.textDidChangeNotification , object: nil)
    }
    
    @objc func textDidChangeNotification(_ notify: Notification) {
        guard self == notify.object as? UITextView else { return }
        guard placeHolderText != nil else { return }
        
        UIView.animate(withDuration: 0.25, animations: {
            self.placeHolderLabel.alpha = (self.text.count == 0) ? 0.5 : 0
        }, completion: nil)
    }
    
}

func setPlaceholder(){ var placeholderLabel = UILabel() placeholderLabel.text = "Describe your need..." placeholderLabel.font = UIFont.init(name: "Lato-Regular", size: 15.0) ?? UIFont.boldSystemFont(ofSize: 14.0) placeholderLabel.sizeToFit() descriptionTextView.addSubview(placeholderLabel) placeholderLabel.frame.origin = CGPoint(x: 5, y: (descriptionTextView.font?.pointSize)! / 2) placeholderLabel.textColor = UIColor.lightGray placeholderLabel.isHidden = !descriptionTextView.text.isEmpty } //Delegate Method. func textViewDidChange(_ textView: UITextView) { placeholderLabel.isHidden = !textView.text.isEmpty }

我们的解决方案避免了与UITextView文本和textColor属性的混淆,如果您要维护字符计数器,这是非常方便的。

很简单:

1)在Storyboard中创建一个与主UITextView属性相同的虚拟UITextView。将占位符文本分配给虚拟文本。

2)对齐两个uitextview的上、左、右边缘。

3)把假人放在主人的后面。

4)重写master的textViewDidChange(textView:)委托函数,如果master有0个字符,则显示dummy。否则,显示master。

这假设两个uitextview的背景都是透明的。如果没有,当有0个字符时,将假人放在上面,当有>个0个字符时,将它推到下面。您还必须交换响应器,以确保光标跟随正确的UITextView。