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


当前回答

与这篇文章中几乎所有的答案相反,UITextView确实有一个占位符属性。由于我无法理解的原因,它只在IB中出现,例如:

<userDefinedRuntimeAttributes>
  <userDefinedRuntimeAttribute type="string" keyPath="placeholder" value="My Placeholder"/>
</userDefinedRuntimeAttributes>

因此,如果你正在使用故事板,一个静态占位符就足够了,只需在检查器上设置属性。

你也可以像这样在代码中设置这个属性:

textView.setValue("My Placeholder", forKeyPath: "placeholder")

它的多云天气,这是通过私有API访问,因为属性是暴露的。

我还没有尝试过用这种方法提交。但我将很快以这种方式提交,并将相应地更新这个答案。

更新:

我已经在多个版本中发布了这个代码,苹果没有任何问题。

更新: 这将只适用于Xcode pre 11.2

其他回答

我相信这是一个非常干净的解决方案。它在实际文本视图下面添加了一个虚拟文本视图,并根据实际文本视图中的文本显示或隐藏它:

import Foundation
import UIKit

class TextViewWithPlaceholder: UITextView {

    private var placeholderTextView: UITextView = UITextView()

    var placeholder: String? {
        didSet {
            placeholderTextView.text = placeholder
        }
    }

    override var text: String! {
        didSet {
            placeholderTextView.isHidden = text.isEmpty == false
        }
    }

    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        commonInit()
    }

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

    private func commonInit() {
        applyCommonTextViewAttributes(to: self)
        configureMainTextView()
        addPlaceholderTextView()
        NotificationCenter.default.addObserver(self,
                                               selector: #selector(textDidChange),
                                               name: UITextView.textDidChangeNotification,
                                               object: nil)
    }

    func addPlaceholderTextView() {
        applyCommonTextViewAttributes(to: placeholderTextView)
        configurePlaceholderTextView()
        insertSubview(placeholderTextView, at: 0)
    }

    private func applyCommonTextViewAttributes(to textView: UITextView) {
        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.textContainer.lineFragmentPadding = 0
        textView.textContainerInset = UIEdgeInsets(top: 10,
                                                   left: 10,
                                                   bottom: 10,
                                                   right: 10)
    }

    private func configureMainTextView() {
        // Do any configuration of the actual text view here
    }

    private func configurePlaceholderTextView() {
        placeholderTextView.text = placeholder
        placeholderTextView.font = font
        placeholderTextView.textColor = UIColor.lightGray
        placeholderTextView.frame = bounds
        placeholderTextView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        placeholderTextView.frame = bounds
    }

    @objc func textDidChange() {
        placeholderTextView.isHidden = !text.isEmpty
    }

}

迅速:

以编程方式或通过Interface Builder添加你的文本视图,如果是最后一个,创建outlet:

@IBOutlet weak var yourTextView: UITextView!

请添加委托(UITextViewDelegate):

class ViewController: UIViewController, UITextViewDelegate {

在viewDidLoad方法中,添加如下内容:

override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

    yourTextView.delegate = self
    yourTextView.text = "Placeholder text goes right here..."
    yourTextView.textColor = UIColor.lightGray

现在让我来介绍一下神奇的部分,添加这个函数:

func textViewDidBeginEditing(_ textView: UITextView) {

    if yourTextView.textColor == UIColor.lightGray {
        yourTextView.text = ""
        yourTextView.textColor = UIColor.black
    }
}

请注意,这将在编辑开始时执行,在那里我们将检查条件来告知状态,使用color属性。 设置文本为nil我不建议。在此之后,我们将文本颜色设置为所需的颜色,在本例中为黑色。

现在也添加这个函数:

func textViewDidEndEditing(_ textView: UITextView) {

    if yourTextView.text == "" {

        yourTextView.text = "Placeholder text ..."
        yourTextView.textColor = UIColor.lightGray
    }
}

让我坚持一下,不要与零进行比较,我已经试过了,它不会起作用。然后我们将值设置回占位符样式,并将颜色设置回占位符颜色,因为这是在textViewDidBeginEditing中检查的条件。

在swift2.2:

public class CustomTextView: UITextView {

private struct Constants {
    static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
}
private let placeholderLabel: UILabel = UILabel()

private var placeholderLabelConstraints = [NSLayoutConstraint]()

@IBInspectable public var placeholder: String = "" {
    didSet {
        placeholderLabel.text = placeholder
    }
}

@IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor {
    didSet {
        placeholderLabel.textColor = placeholderColor
    }
}

override public var font: UIFont! {
    didSet {
        placeholderLabel.font = font
    }
}

override public var textAlignment: NSTextAlignment {
    didSet {
        placeholderLabel.textAlignment = textAlignment
    }
}

override public var text: String! {
    didSet {
        textDidChange()
    }
}

override public var attributedText: NSAttributedString! {
    didSet {
        textDidChange()
    }
}

override public var textContainerInset: UIEdgeInsets {
    didSet {
        updateConstraintsForPlaceholderLabel()
    }
}

override public init(frame: CGRect, textContainer: NSTextContainer?) {
    super.init(frame: frame, textContainer: textContainer)
    commonInit()
}

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

private func commonInit() {
    NSNotificationCenter.defaultCenter().addObserver(self,
                                                     selector: #selector(textDidChange),
                                                     name: UITextViewTextDidChangeNotification,
                                                     object: nil)

    placeholderLabel.font = font
    placeholderLabel.textColor = placeholderColor
    placeholderLabel.textAlignment = textAlignment
    placeholderLabel.text = placeholder
    placeholderLabel.numberOfLines = 0
    placeholderLabel.backgroundColor = UIColor.clearColor()
    placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
    addSubview(placeholderLabel)
    updateConstraintsForPlaceholderLabel()
}

private func updateConstraintsForPlaceholderLabel() {
    var newConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
                                                                        options: [],
                                                                        metrics: nil,
                                                                        views: ["placeholder": placeholderLabel])
    newConstraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|-(\(textContainerInset.top))-[placeholder]",
                                                                     options: [],
                                                                     metrics: nil,
                                                                     views: ["placeholder": placeholderLabel])
    newConstraints.append(NSLayoutConstraint(
        item: placeholderLabel,
        attribute: .Width,
        relatedBy: .Equal,
        toItem: self,
        attribute: .Width,
        multiplier: 1.0,
        constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
        ))
    removeConstraints(placeholderLabelConstraints)
    addConstraints(newConstraints)
    placeholderLabelConstraints = newConstraints
}

@objc private func textDidChange() {
    placeholderLabel.hidden = !text.isEmpty
}

public override func layoutSubviews() {
    super.layoutSubviews()
    placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
}

deinit {
    NSNotificationCenter.defaultCenter().removeObserver(self,
                                                        name: UITextViewTextDidChangeNotification,
                                                        object: nil)
}

}

在swift3:

import UIKit

类 CustomTextView: { UITextView

private struct Constants {
    static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
}
private let placeholderLabel: UILabel = UILabel()

private var placeholderLabelConstraints = [NSLayoutConstraint]()

@IBInspectable public var placeholder: String = "" {
    didSet {
        placeholderLabel.text = placeholder
    }
}

@IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor {
    didSet {
        placeholderLabel.textColor = placeholderColor
    }
}

override public var font: UIFont! {
    didSet {
        placeholderLabel.font = font
    }
}

override public var textAlignment: NSTextAlignment {
    didSet {
        placeholderLabel.textAlignment = textAlignment
    }
}

override public var text: String! {
    didSet {
        textDidChange()
    }
}

override public var attributedText: NSAttributedString! {
    didSet {
        textDidChange()
    }
}

override public var textContainerInset: UIEdgeInsets {
    didSet {
        updateConstraintsForPlaceholderLabel()
    }
}

override public init(frame: CGRect, textContainer: NSTextContainer?) {
    super.init(frame: frame, textContainer: textContainer)
    commonInit()
}

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

private func commonInit() {
    NotificationCenter.default.addObserver(self,
                                                     selector: #selector(textDidChange),
                                                     name: NSNotification.Name.UITextViewTextDidChange,
                                                     object: nil)

    placeholderLabel.font = font
    placeholderLabel.textColor = placeholderColor
    placeholderLabel.textAlignment = textAlignment
    placeholderLabel.text = placeholder
    placeholderLabel.numberOfLines = 0
    placeholderLabel.backgroundColor = UIColor.clear
    placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
    addSubview(placeholderLabel)
    updateConstraintsForPlaceholderLabel()
}

private func updateConstraintsForPlaceholderLabel() {
    var newConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
        options: [],
        metrics: nil,
        views: ["placeholder": placeholderLabel])
    newConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-(\(textContainerInset.top))-[placeholder]",
        options: [],
        metrics: nil,
        views: ["placeholder": placeholderLabel])
    newConstraints.append(NSLayoutConstraint(
        item: placeholderLabel,
        attribute: .width,
        relatedBy: .equal,
        toItem: self,
        attribute: .width,
        multiplier: 1.0,
        constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
    ))
    removeConstraints(placeholderLabelConstraints)
    addConstraints(newConstraints)
    placeholderLabelConstraints = newConstraints
}

@objc private func textDidChange() {
    placeholderLabel.isHidden = !text.isEmpty
}

public override func layoutSubviews() {
    super.layoutSubviews()
    placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
}

deinit {
    NotificationCenter.default.removeObserver(self,
                                                        name: NSNotification.Name.UITextViewTextDidChange,
                                                        object: nil)
}

}

我用swift写了一个类。您需要在需要的时候导入这个类。

我知道这是一个老问题,但想分享我认为是一个有用的方式扩展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

Swift 4更新

UITextView本身没有占位符属性,所以你必须用UITextViewDelegate方法来创建和操作一个。我建议使用下面的解决方案#1或#2,这取决于所需的行为。

注意:对于任何一种解决方案,都需要将UITextViewDelegate添加到类中,并设置textView.delegate = self来使用文本视图的委托方法。


解决方案#1 -如果你想让占位符在用户选择文本视图时立即消失:

首先将UITextView设置为包含占位符文本,并将其设置为浅灰色,以模拟UITextField的占位符文本的外观。要么在viewDidLoad中这样做,要么在文本视图创建时这样做。

textView.text = "Placeholder"
textView.textColor = UIColor.lightGray

然后,当用户开始编辑文本视图时,如果文本视图包含一个占位符(即,如果其文本颜色是浅灰色),则清除占位符文本,并将文本颜色设置为黑色,以便容纳用户的输入。

func textViewDidBeginEditing(_ textView: UITextView) {
    if textView.textColor == UIColor.lightGray {
        textView.text = nil
        textView.textColor = UIColor.black
    }
}

然后,当用户完成编辑文本视图并将其作为第一响应器时,如果文本视图为空,则通过重新添加占位符文本并将其颜色设置为浅灰色来重置其占位符。

func textViewDidEndEditing(_ textView: UITextView) {
    if textView.text.isEmpty {
        textView.text = "Placeholder"
        textView.textColor = UIColor.lightGray
    }
}

解决方案#2 -如果你想要占位符显示文本视图是空的,即使文本视图被选中:

首先在viewDidLoad中设置占位符:

textView.text = "Placeholder"
textView.textColor = UIColor.lightGray

textView.becomeFirstResponder()

textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)

(注意:由于OP希望在视图加载时立即选择文本视图,所以我将文本视图选择合并到上面的代码中。如果这不是你想要的行为,你不希望在视图加载时选择文本视图,从上面的代码块中删除最后两行。)

然后使用shouldChangeTextInRange UITextViewDelegate方法,如下所示:

func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {

    // Combine the textView text and the replacement text to
    // create the updated text string
    let currentText:String = textView.text
    let updatedText = (currentText as NSString).replacingCharacters(in: range, with: text)

    // If updated text view will be empty, add the placeholder
    // and set the cursor to the beginning of the text view
    if updatedText.isEmpty {

        textView.text = "Placeholder"
        textView.textColor = UIColor.lightGray

        textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
    }

    // Else if the text view's placeholder is showing and the
    // length of the replacement string is greater than 0, set 
    // the text color to black then set its text to the
    // replacement string
     else if textView.textColor == UIColor.lightGray && !text.isEmpty {
        textView.textColor = UIColor.black
        textView.text = text
    }

    // For every other case, the text should change with the usual
    // behavior...
    else {
        return true
    }

    // ...otherwise return false since the updates have already
    // been made
    return false
}

也实现textViewDidChangeSelection,以防止用户改变光标的位置,而占位符是可见的。(注意:textViewDidChangeSelection在视图加载之前被调用,所以如果窗口是可见的,只检查文本视图的颜色):

func textViewDidChangeSelection(_ textView: UITextView) {
    if self.view.window != nil {
        if textView.textColor == UIColor.lightGray {
            textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
        }
    }
}