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


当前回答

协议版本的clearlight的答案上面,因为协议是伟大的。随便放哪都行。扣篮!

extension UITextViewPlaceholder where Self: UIViewController {

    // Use this in ViewController's ViewDidLoad method.
    func addPlaceholder(text: String, toTextView: UITextView, font: UIFont? = nil) {
        placeholderLabel = UILabel()
        placeholderLabel.text = text
        placeholderLabel.font = font ?? UIFont.italicSystemFont(ofSize: (toTextView.font?.pointSize)!)
        placeholderLabel.sizeToFit()
        toTextView.addSubview(placeholderLabel)
        placeholderLabel.frame.origin = CGPoint(x: 5, y: (toTextView.font?.pointSize)! / 2)
        placeholderLabel.textColor = UIColor.lightGray
        placeholderLabel.isHidden = !toTextView.text.isEmpty
    }

    // Use this function in the ViewController's textViewDidChange delegate method.
    func textViewWithPlaceholderDidChange(_ textView: UITextView) {
        placeholderLabel.isHidden = !textView.text.isEmpty
    }
}

其他回答

我知道这是一个老问题,但想分享我认为是一个有用的方式扩展UITextView有placeholderText和placeholderColor字段。基本上你将UITextView转换为UITextField,然后设置attributedPlaceholder字段。PlaceholderText和placeholderColor是IBInspectable字段,所以它们的值可以在IB中设置,并与UITextField占位符功能完全相同。

UITextView+Extend.h

#import <UIKit/UIKit.h>

@interface UITextView (Extend)

@property (nonatomic) IBInspectable NSString *placeholderText;
@property (nonatomic) IBInspectable UIColor *placeholderColor;

@end

UITextView+Extend.m

#import "UITextView+Extend.h"
#import "objc/runtime.h"

@implementation UITextView (Extend)

- (void)setPlaceholderText:(NSString *)placeholderText
{
    objc_setAssociatedObject(self, @selector(placeholderText), placeholderText, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self updatePlaceholderText];
}

- (NSString*)placeholderText
{
    return objc_getAssociatedObject(self, @selector(placeholderText));
}

- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
    objc_setAssociatedObject(self, @selector(placeholderColor), placeholderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    [self updatePlaceholderText];
}

- (UIColor*)placeholderColor
{
    return objc_getAssociatedObject(self, @selector(placeholderColor));
}

- (void)updatePlaceholderText
{
    NSString *text = self.placeholderText;
    UIColor *color = self.placeholderColor;
    if(text && color)
    {
        UITextField *textField = (UITextField*)self;
        textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:text attributes:@{NSForegroundColorAttributeName:color}];
    }
}

@end

我很惊讶没有人提到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类似的计算变量在多几行中实现。

另一个解决方案是使用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
    }
}

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

我试着用clearlight的答案来简化代码。

extension UITextView{

    func setPlaceholder() {

        let placeholderLabel = UILabel()
        placeholderLabel.text = "Enter some text..."
        placeholderLabel.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
        placeholderLabel.sizeToFit()
        placeholderLabel.tag = 222
        placeholderLabel.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
        placeholderLabel.textColor = UIColor.lightGray
        placeholderLabel.isHidden = !self.text.isEmpty

        self.addSubview(placeholderLabel)
    }

    func checkPlaceholder() {
        let placeholderLabel = self.viewWithTag(222) as! UILabel
        placeholderLabel.isHidden = !self.text.isEmpty
    }

}

使用

override func viewDidLoad() {
    textView.delegate = self
    textView.setPlaceholder()
}

func textViewDidChange(_ textView: UITextView) {
    textView.checkPlaceholder()
}

迅速:

添加你的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
        }
    }
}