【问题标题】:Add placeholder text inside UITextView in Swift?在 Swift 中的 UITextView 中添加占位符文本?
【发布时间】:2015-02-23 11:26:06
【问题描述】:

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

【问题讨论】:

  • 这是 iOS 开发中使用 UITextView 的一个古老问题。我已经编写了像这里提到的子类:stackoverflow.com/a/1704469/1403046。好处是您仍然可以拥有一个委托,以及在多个地方使用该类,而无需重新实现逻辑。
  • 我将如何使用您的子类,同时为项目使用 swift。使用桥接文件?
  • 你可以这样做,或者在 Swift 中重新实现它。答案中的代码比实际需要的要长。要点是显示/隐藏您在文本更改时收到通知的方法中添加的标签。
  • 您可以使用来自 GitHub 的 UIFloatLabelTextView 示例。这个位置占位符在写的时候在上面。真的很有趣的一个! github.com/ArtSabintsev/UIFloatLabelTextView
  • 老实说,实现这一点的最简单方法是使用自定义 textView 并在没有文本时添加在 textView 上绘制的占位符文本......到目前为止,其他答案一直是这个版本过于复杂,涉及有问题的状态管理(包括文本应该/不应该存在/不存在时的误报)

标签: ios swift uitextview placeholder


【解决方案1】:

为 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)
        }
    }
}

【讨论】:

  • 您好,请确保您将您的视图控制器设置为您的 textView 的委托。你可以通过创建一个从你的 textView 到你的 viewController 的出口来实现这一点。然后使用yourTextField.delegate = self。如果不这样做,textViewDidBeginEditingtextViewDidEndEditing 函数将不起作用。
  • 代码没有编译,我遇到了Cannot convert value of type 'NSRange' (aka '_NSRange') to expected argument type 'Range<String.Index>' (aka 'Range<String.CharacterView.Index>')的错误。
  • 我找到了解决方案,我将附上修改后的代码@iPeter。当前文本必须是 NSString 共振峰:let currentText = textView.text as NSString?。将let updatedText = 行转换为let updatedText = currentText?.replacingCharacters(in: range, with: text)。最后,将if updatedText.isEmpty 行转换为if (updatedText?.isEmpty)! {。这应该可以解决问题!
  • @LyndseyScott 在func textViewDidChangeSelection(_ textView: UITextView) 中设置func textViewDidChangeSelection(_ textView: UITextView) 会导致无限循环...
  • 对于如此简单的事情,(iOS) 开发人员竟需要竭尽全力,这绝对令人震惊。这是很久以前应该是 TextView 控件的一部分的基本功能,我不明白为什么 Apple 还没有解决这个问题。
【解决方案2】:

我通过使用两个不同的文本视图来做到这一点:

  1. 背景中的一个用作占位符。
  2. 在用户实际输入的前景中(具有透明背景)。

这个想法是,一旦用户开始在前景视图中输入内容,后台的占位符就会消失(如果用户删除所有内容,则会重新出现)。所以它的行为与单行文本字段的占位符完全一样。

这是我使用的代码。请注意,descriptionField 是用户输入的字段,descriptionPlaceholder 是后台的字段。

func textViewDidChange(descriptionField: UITextView) {
    if descriptionField.text.isEmpty == false {
        descriptionPlaceholder.text = ""
    } else {
        descriptionPlaceholder.text = descriptionPlaceholderText
    }
}

【讨论】:

  • 这种方式有点hacky,但它绝对是最简单的,并且可以准确地生成您想要的结果。好主意
【解决方案3】:

浮动占位符


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

斯威夫特 3:

class NotesViewController : UIViewController, UITextViewDelegate {

    @IBOutlet var textView : UITextView!
    var placeholderLabel : UILabel!

    override func viewDidLoad() {
        super.viewDidLoad()

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

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

Swift 2: 相同,除了:italicSystemFontOfSize(textView.font.pointSize)UIColor.lightGrayColor


【讨论】:

  • 这种方法无疑是我发现的最简单、最不容易出错的方法。太棒了。
  • 这是一个很好的方法,但是如果占位符文本很长,标签文本可能会超出范围..
  • 简单易用,并与现有行为很好地集成。无论如何,占位符消息不应该那么冗长,但是将行设置为 0 不会解决这个问题吗?
  • 我通常更喜欢这种方法,但如果文本居中对齐会出现问题,因为光标将位于占位符的顶部而不是左侧。
  • @blwinters - 这将是一个非常不寻常的极端情况,不是吗?但是假设在这种情况下它是一个多行输入字段(我必须假设),您不能调整垂直偏移计算以将占位符文本从光标移开一点吗?
【解决方案4】:

斯威夫特:

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

@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
    }
}

让我坚持,不要与nil 比较,我已经尝试过了,但它不起作用。然后我们将值设置回占位符样式,并将颜色设置回占位符颜色,因为这是签入textViewDidBeginEditing 的条件。

【讨论】:

    【解决方案5】:

    我不知道为什么人们将这个问题如此复杂化.... 这相当简单明了。这是提供请求功能的 UITextView 的子类。

    - (void)customInit
    {
        self.contentMode = UIViewContentModeRedraw;
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
    }
    
        - (void)textChanged:(NSNotification *)notification
        {
            if (notification.object == self) {
                if(self.textStorage.length != 0 || !self.textStorage.length) {
                    [self setNeedsDisplay];
                }
            }
        }
    
    
        #pragma mark - Setters
    
        - (void)setPlaceholderText:(NSString *)placeholderText withFont:(UIFont *)font
        {
            self.placeholderText = placeholderText;
            self.placeholderTextFont = font;
    
        }
    
    
    
        - (void)drawRect:(CGRect)rect
        {
            [super drawRect:rect];
            [[UIColor lightGrayColor] setFill];
    
            if (self.textStorage.length != 0) {
                return;
            }
    
            CGRect inset = CGRectInset(rect, 8, 8);//Default rect insets for textView
            NSDictionary *attributes =  @{NSFontAttributeName: self.placeholderTextFont, NSForegroundColorAttributeName: [UIColor grayColor]};
            [self.placeholderText drawInRect:inset withAttributes:attributes];
        }`
    

    【讨论】:

    • 仅供参考,在有人打败我之前......这可以直接翻译成 Swift。这里没有什么复杂的。
    【解决方案6】:

    在视图加载中设置值

        txtVw!.autocorrectionType = UITextAutocorrectionType.No
        txtVw!.text = "Write your Placeholder"
        txtVw!.textColor = UIColor.lightGrayColor()
    
    
    
    func textViewDidBeginEditing(textView: UITextView) {
        if (txtVw?.text == "Write your Placeholder")
    
        {
            txtVw!.text = nil
            txtVw!.textColor = UIColor.blackColor()
        }
    }
    
    func textViewDidEndEditing(textView: UITextView) {
        if txtVw!.text.isEmpty
        {
            txtVw!.text = "Write your Placeholder"
            txtVw!.textColor = UIColor.lightGrayColor()
        }
        textView.resignFirstResponder()
    }
    

    【讨论】:

      【解决方案7】:

      在 ios 中没有这样的属性可以直接在 TextView 中添加占位符,而是您可以添加标签并在 textView 中显示/隐藏更改。 SWIFT 2.0 并确保实现 textviewdelegate

      func textViewDidChange(TextView: UITextView)
      {
      
       if  txtShortDescription.text == ""
          {
              self.lblShortDescription.hidden = false
          }
          else
          {
              self.lblShortDescription.hidden = true
          }
      
      }
      

      【讨论】:

        【解决方案8】:

        如果您使用多个文本视图,这是我准备使用的解决方案

        func textViewShouldBeginEditing(textView: UITextView) -> Bool {        
            // Set cursor to the beginning if placeholder is set
            if textView.textColor == UIColor.lightGrayColor() {
                textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
            }
        
            return true
        }
        
        func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
            // Remove placeholder
            if textView.textColor == UIColor.lightGrayColor() && text.characters.count > 0 {
                textView.text = ""
                textView.textColor = UIColor.blackColor()
            }
        
            if text == "\n" {
                textView.resignFirstResponder()
                return false
            }
        
            return true
        }
        
        func textViewDidChange(textView: UITextView) {
            // Set placeholder if text is empty
            if textView.text.isEmpty {
                textView.text = NSLocalizedString("Hint", comment: "hint")
                textView.textColor = UIColor.lightGrayColor()
                textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
            }
        }
        
        func textViewDidChangeSelection(textView: UITextView) {
            // Set cursor to the beginning if placeholder is set
            let firstPosition = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
        
            // Do not change position recursively
            if textView.textColor == UIColor.lightGrayColor() && textView.selectedTextRange != firstPosition {
                textView.selectedTextRange = firstPosition
            }
        }
        

        【讨论】:

        • 非常好!
        【解决方案9】:

        Swift - 我编写了一个继承 UITextView 的类,并添加了一个 UILabel 作为子视图来充当占位符。

          import UIKit
          @IBDesignable
          class HintedTextView: UITextView {
        
              @IBInspectable var hintText: String = "hintText" {
                  didSet{
                      hintLabel.text = hintText
                  }
              }
        
              private lazy var hintLabel: UILabel = {
                  let label = UILabel()
                  label.font = UIFont.systemFontOfSize(16)
                  label.textColor = UIColor.lightGrayColor()
                  label.translatesAutoresizingMaskIntoConstraints = false
                  return label
              }()
        
        
              override init(frame: CGRect, textContainer: NSTextContainer?) {
                  super.init(frame: frame, textContainer: textContainer)
                  setupView()
              }
        
              required init?(coder aDecoder: NSCoder) {
                 super.init(coder: aDecoder)
                 setupView()
              }
        
              override func prepareForInterfaceBuilder() {
                 super.prepareForInterfaceBuilder()
                 setupView()
              }
        
              private func setupView() {
        
                translatesAutoresizingMaskIntoConstraints = false
                delegate = self
                font = UIFont.systemFontOfSize(16)
        
                addSubview(hintLabel)
        
                NSLayoutConstraint.activateConstraints([
        
                   hintLabel.leftAnchor.constraintEqualToAnchor(leftAnchor, constant: 4),
                   hintLabel.rightAnchor.constraintEqualToAnchor(rightAnchor, constant: 8),
                   hintLabel.topAnchor.constraintEqualToAnchor(topAnchor, constant: 4),
                   hintLabel.heightAnchor.constraintEqualToConstant(30)
                 ])
                }
        
              override func layoutSubviews() {
                super.layoutSubviews()
                setupView()
             }
        
        }
        

        【讨论】:

        • 使用约束来定位占位符的好例子,但是占位符的理想位置正好在文本的第一个字符的正上方,因此根据文本视图的位置计算该位置字体更好,因为它适应为文本视图设置的任何字体大小。
        【解决方案10】:

        我喜欢@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.
        

        为防止多次添加占位符,在extension 中添加了私有add() 函数。

        【讨论】:

        • 再次感谢您对原版的改进。这个周末我花了太多时间,但我想你会喜欢我最终想出的 EZ Placeholder 极端变体:stackoverflow.com/a/41081244/2079103 (我感谢你为占位符解决方案的发展做出了贡献)
        【解决方案11】:

        在 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写了一个类。您需要在需要时导入此类。

        【讨论】:

          【解决方案12】:

          我试图通过clearlightanswer 简化代码。

          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()
          }
          

          【讨论】:

          • 几个问题。 (1) 作为一个假设和“借用” UIView 标签属性值的扩展,有人可能会在他们自己的视图层次结构中使用相同的标签,而不知道扩展的用法,从而产生极难诊断的错误。类似的东西不属于库代码或扩展。 (2) 它仍然需要调用者声明一个委托。 Floating Placeholder 避免了黑客攻击,占用空间小,简单且完全本地化,这是一个安全的选择。
          【解决方案13】:

          另一种解决方案(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)
              }
          }
          

          结果

          【讨论】:

            【解决方案14】:

            由于声誉问题,我无法添加评论。在@clearlight 答案中再添加一个代表需求。

            func textViewDidBeginEditing(_ textView: UITextView) { 
                    cell.placeholderLabel.isHidden = !textView.text.isEmpty
            }
            

            需要

            因为textViewDidChange 不是第一次调用

            【讨论】:

              【解决方案15】:

              斯威夫特 3.1

              这个扩展对我很有效:https://github.com/devxoul/UITextView-Placeholder

              这是一个代码sn-p:

              通过 pod 安装:

              pod 'UITextView+Placeholder', '~> 1.2'
              

              将其导入您的班级

              import UITextView_Placeholder
              

              并将placeholder 属性添加到您已经创建的UITextView

              textView.placeholder = "Put some detail"
              

              就是这样... 这是它的外观(第三个框是UITextView

              【讨论】:

                【解决方案16】:

                没有可用于 textview 的占位符。当用户在 textview 中输入时,您必须在其上方放置标签,然后在用户输入时将其隐藏或设置为默认值删除所有值。

                【讨论】:

                  【解决方案17】:

                  使用此扩展这是在 UITextView 中设置占位符的最佳方式。 但请确保您已将委托附加到 TextView。您可以像这样设置占位符:-

                  yourTextView.placeholder = "Placeholder" 
                  
                  extension UITextView :UITextViewDelegate
                  {
                      
                      /// Resize the placeholder when the UITextView bounds change
                      override open var bounds: CGRect {
                          didSet {
                              self.resizePlaceholder()
                          }
                      }
                      
                      /// The UITextView placeholder text
                      public var placeholder: String? {
                          get {
                              var placeholderText: String?
                              
                              if let placeholderLabel = self.viewWithTag(100) as? UILabel {
                                  placeholderText = placeholderLabel.text
                              }
                              
                              return placeholderText
                          }
                          set {
                              if let placeholderLabel = self.viewWithTag(100) as! UILabel? {
                                  placeholderLabel.text = newValue
                                  placeholderLabel.sizeToFit()
                              } else {
                                  self.addPlaceholder(newValue!)
                              }
                          }
                      }
                      
                      /// When the UITextView did change, show or hide the label based on if the UITextView is empty or not
                      ///
                      /// - Parameter textView: The UITextView that got updated
                      public func textViewDidChange(_ textView: UITextView) {
                          if let placeholderLabel = self.viewWithTag(100) as? UILabel {
                              placeholderLabel.isHidden = self.text.count > 0
                          }
                      }
                      
                      /// Resize the placeholder UILabel to make sure it's in the same position as the UITextView text
                      private func resizePlaceholder() {
                          if let placeholderLabel = self.viewWithTag(100) as! UILabel? {
                              let labelX = self.textContainer.lineFragmentPadding
                              let labelY = self.textContainerInset.top - 2
                              let labelWidth = self.frame.width - (labelX * 2)
                              let labelHeight = placeholderLabel.frame.height
                              
                              placeholderLabel.frame = CGRect(x: labelX, y: labelY, width: labelWidth, height: labelHeight)
                          }
                      }
                      
                      /// Adds a placeholder UILabel to this UITextView
                      private func addPlaceholder(_ placeholderText: String) {
                          let placeholderLabel = UILabel()
                          
                          placeholderLabel.text = placeholderText
                          placeholderLabel.sizeToFit()
                          
                          placeholderLabel.font = self.font
                          placeholderLabel.textColor = UIColor.lightGray
                          placeholderLabel.tag = 100
                          
                          placeholderLabel.isHidden = self.text.count > 0
                          
                          self.addSubview(placeholderLabel)
                          self.resizePlaceholder()
                          self.delegate = self
                      }
                  }
                  

                  【讨论】:

                  • 像魅力一样工作!非常感谢你 ! :)
                  • 欢迎亲爱的,一切顺利
                  • 这是一个非常优雅的答案。这应该是投票最高的答案
                  【解决方案18】:

                  上面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
                      }
                  }
                  

                  【讨论】:

                    【解决方案19】:

                    文本视图委托方法

                    使用这两个委托方法并在你的类中编写 UITextViewDelegate

                    func textViewDidBeginEditing(_ textView: UITextView) {
                        if (commentsTextView.text == "Type Your Comments")
                        {
                            commentsTextView.text = nil
                            commentsTextView.textColor = UIColor.darkGray
                        }
                    }
                    
                    func textViewDidEndEditing(_ textView: UITextView) {
                        if commentsTextView.text.isEmpty
                        {
                            commentsTextView.text = "Type Your Comments"
                            commentsTextView.textColor = UIColor.darkGray
                        }
                        textView.resignFirstResponder()
                    }
                    

                    【讨论】:

                      【解决方案20】:

                      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
                          }
                      	

                      【讨论】:

                        【解决方案21】:

                        这里有一些东西可以放入UIStackView,它会使用内部高度约束来调整大小。可能需要调整以适应特定要求。

                        import UIKit
                        
                        public protocol PlaceholderTextViewDelegate: class {
                          func placeholderTextViewTextChanged(_ textView: PlaceholderTextView, text: String)
                        }
                        
                        public class PlaceholderTextView: UIView {
                        
                          public weak var delegate: PlaceholderTextViewDelegate?
                          private var heightConstraint: NSLayoutConstraint?
                        
                          public override init(frame: CGRect) {
                            self.allowsNewLines = true
                        
                            super.init(frame: frame)
                        
                            self.heightConstraint = self.heightAnchor.constraint(equalToConstant: 0)
                            self.heightConstraint?.isActive = true
                        
                            self.addSubview(self.placeholderTextView)
                            self.addSubview(self.textView)
                        
                            self.pinToCorners(self.placeholderTextView)
                            self.pinToCorners(self.textView)
                        
                            self.updateHeight()
                          }
                        
                          public override func didMoveToSuperview() {
                            super.didMoveToSuperview()
                        
                            self.updateHeight()
                          }
                        
                          private func pinToCorners(_ view: UIView) {
                            NSLayoutConstraint.activate([
                              view.leadingAnchor.constraint(equalTo: self.leadingAnchor),
                              view.trailingAnchor.constraint(equalTo: self.trailingAnchor),
                              view.topAnchor.constraint(equalTo: self.topAnchor),
                              view.bottomAnchor.constraint(equalTo: self.bottomAnchor)
                            ])
                          }
                        
                          // Accessors
                          public var text: String? {
                            didSet {
                              self.textView.text = text
                              self.textViewDidChange(self.textView)
                              self.updateHeight()
                            }
                          }
                        
                          public var textColor: UIColor? {
                            didSet {
                              self.textView.textColor = textColor
                              self.updateHeight()
                            }
                          }
                        
                          public var font: UIFont? {
                            didSet {
                              self.textView.font = font
                              self.placeholderTextView.font = font
                              self.updateHeight()
                            }
                          }
                        
                          public override var tintColor: UIColor? {
                            didSet {
                              self.textView.tintColor = tintColor
                              self.placeholderTextView.tintColor = tintColor
                            }
                          }
                        
                          public var placeholderText: String? {
                            didSet {
                              self.placeholderTextView.text = placeholderText
                              self.updateHeight()
                            }
                          }
                        
                          public var placeholderTextColor: UIColor? {
                            didSet {
                              self.placeholderTextView.textColor = placeholderTextColor
                              self.updateHeight()
                            }
                          }
                        
                          public var allowsNewLines: Bool
                        
                          public required init?(coder _: NSCoder) {
                            fatalError("init(coder:) has not been implemented")
                          }
                        
                          private lazy var textView: UITextView = self.newTextView()
                          private lazy var placeholderTextView: UITextView = self.newTextView()
                        
                          private func newTextView() -> UITextView {
                            let textView = UITextView()
                            textView.translatesAutoresizingMaskIntoConstraints = false
                            textView.isScrollEnabled = false
                            textView.delegate = self
                            textView.backgroundColor = .clear
                            return textView
                          }
                        
                          private func updateHeight() {
                            let maxSize = CGSize(width: self.frame.size.width, height: .greatestFiniteMagnitude)
                        
                            let textViewSize = self.textView.sizeThatFits(maxSize)
                            let placeholderSize = self.placeholderTextView.sizeThatFits(maxSize)
                        
                            let maxHeight = ceil(CGFloat.maximum(textViewSize.height, placeholderSize.height))
                        
                            self.heightConstraint?.constant = maxHeight
                          }
                        }
                        
                        extension PlaceholderTextView: UITextViewDelegate {
                          public func textViewDidChangeSelection(_: UITextView) {
                            self.placeholderTextView.alpha = self.textView.text.isEmpty ? 1 : 0
                            self.updateHeight()
                          }
                        
                          public func textViewDidChange(_: UITextView) {
                            self.delegate?.placeholderTextViewTextChanged(self, text: self.textView.text)
                          }
                        
                          public func textView(_: UITextView, shouldChangeTextIn _: NSRange,
                                               replacementText text: String) -> Bool {
                            let containsNewLines = text.rangeOfCharacter(from: .newlines)?.isEmpty == .some(false)
                            guard !containsNewLines || self.allowsNewLines else { return false }
                        
                            return true
                          }
                        }
                        

                        【讨论】:

                          【解决方案22】:

                          斯威夫特 3.2

                          extension EditProfileVC:UITextViewDelegate{
                          
                              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
                                  }
                              }
                          }
                          

                          首先当用户开始编辑 textViewDidBeginEditing 调用,然后检查文本灰色是否表示用户没有写任何内容,然后设置为 textview nil 并将颜色更改为黑色以供用户发短信。

                          当调用用户端编辑 textViewDidEndEditing 并检查用户是否在 textview 中没有写任何内容时,文本设置为灰色并带有文本“PlaceHolder”

                          【讨论】:

                          • 看起来是更短、最简单的答案,代码更少
                          【解决方案23】:

                          编辑完成后,我必须调度队列以使占位符文本重新出现。

                          func textViewDidBeginEditing(_ textView: UITextView) {
                          
                              if textView.text == "Description" {
                                  textView.text = nil
                              }
                          }
                          
                          func textViewDidEndEditing(_ textView: UITextView) {
                          
                              if textView.text.isEmpty {
                                  DispatchQueue.main.async {
                                      textView.text = "Description"
                                  }
                              }
                          }
                          

                          【讨论】:

                          • 如果我希望这个值成为用户输入的内容,我应该输入什么而不是最后一行“textView.text = ”Description”?
                          【解决方案24】:

                          这是我用来完成这项工作的工具。

                          @IBDesignable class UIPlaceholderTextView: UITextView {
                              
                              var placeholderLabel: UILabel?
                              
                              override init(frame: CGRect, textContainer: NSTextContainer?) {
                                  super.init(frame: frame, textContainer: textContainer)
                                  sharedInit()
                              }
                              
                              required init?(coder aDecoder: NSCoder) {
                                  super.init(coder: aDecoder)
                                  sharedInit()
                              }
                              
                              override func prepareForInterfaceBuilder() {
                                  sharedInit()
                              }
                              
                              func sharedInit() {
                                  refreshPlaceholder()
                                  NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil)
                              }
                          
                              @IBInspectable var placeholder: String? {
                                  didSet {
                                      refreshPlaceholder()
                                  }
                              }
                          
                              @IBInspectable var placeholderColor: UIColor? = .darkGray {
                                  didSet {
                                      refreshPlaceholder()
                                  }
                              }
                              
                              @IBInspectable var placeholderFontSize: CGFloat = 14 {
                                  didSet {
                                      refreshPlaceholder()
                                  }
                              }
                              
                              func refreshPlaceholder() {
                                  if placeholderLabel == nil {
                                      placeholderLabel = UILabel()
                                      let contentView = self.subviews.first ?? self
                                      
                                      contentView.addSubview(placeholderLabel!)
                                      placeholderLabel?.translatesAutoresizingMaskIntoConstraints = false
                                      
                                      placeholderLabel?.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: textContainerInset.left + 4).isActive = true
                                      placeholderLabel?.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: textContainerInset.right + 4).isActive = true
                                      placeholderLabel?.topAnchor.constraint(equalTo: contentView.topAnchor, constant: textContainerInset.top).isActive = true
                                      placeholderLabel?.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: textContainerInset.bottom).isActive = true
                                  }
                                  placeholderLabel?.text = placeholder
                                  placeholderLabel?.textColor = placeholderColor
                                  placeholderLabel?.font = UIFont.systemFont(ofSize: placeholderFontSize)
                              }
                              
                              @objc func textChanged() {
                                  if self.placeholder?.isEmpty ?? true {
                                      return
                                  }
                                  
                                  UIView.animate(withDuration: 0.25) {
                                      if self.text.isEmpty {
                                          self.placeholderLabel?.alpha = 1.0
                                      } else {
                                          self.placeholderLabel?.alpha = 0.0
                                      }
                                  }
                              }
                              
                              override var text: String! {
                                  didSet {
                                      textChanged()
                                  }
                              }
                          
                          }
                          

                          我知道有几种类似的方法,但这种方法的好处是它可以:

                          • IB中设置占位符文本、字体大小和颜色。
                          • IB 中不再显示“Scroll View has ambiguous scrollable content”的警告。
                          • 添加动画以显示/隐藏占位符。

                          【讨论】:

                          【解决方案25】:

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

                          但是,使用NSTextStorageDelegatetextStorage(_:didProcessEditing:range:changeInLength:) 方法,您将收到对文本的任何更改的通知,即使它是以编程方式完成的。只需像这样分配它:

                          textView.textStorage.delegate = self
                          

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

                          将它与@clearlight 演示的UILabel 技术相结合,可以轻松地将整个UITextViewplaceholder 实现包装到一个扩展中。

                          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 分配一个占位符字符串,即使在 Interface Builder 中也是如此。

                          为了清楚起见,我省略了 placeholderColor 属性的实现,但它可以通过与 placeholder 类似的计算变量再多几行来实现。

                          【讨论】:

                          • 非常优雅的解决方案。我喜欢它。
                          • textView.textStorage.delegate = self 视图控制器中的 this 将要求我们将该视图控制器与 NSTextStorageDelegate 绑定。真的需要吗?
                          • 简单,就像一个魅力。这是必须接受的答案
                          • @Hemang 是文本视图本身是NSTextStorageDelegate,而不是视图控制器。
                          【解决方案26】:

                          斯威夫特:

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

                          请添加Delegate使用扩展名(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
                                  }
                              }
                          }
                          

                          【讨论】:

                            【解决方案27】:

                            对我有用的简单快速的解决方案是:

                            @IBDesignable
                            class PlaceHolderTextView: UITextView {
                            
                                @IBInspectable var placeholder: String = "" {
                                     didSet{
                                         updatePlaceHolder()
                                    }
                                }
                            
                                @IBInspectable var placeholderColor: UIColor = UIColor.gray {
                                    didSet {
                                        updatePlaceHolder()
                                    }
                                }
                            
                                private var originalTextColor = UIColor.darkText
                                private var originalText: String = ""
                            
                                private func updatePlaceHolder() {
                            
                                    if self.text == "" || self.text == placeholder  {
                            
                                        self.text = placeholder
                                        self.textColor = placeholderColor
                                        if let color = self.textColor {
                            
                                            self.originalTextColor = color
                                        }
                                        self.originalText = ""
                                    } else {
                                        self.textColor = self.originalTextColor
                                        self.originalText = self.text
                                    }
                            
                                }
                            
                                override func becomeFirstResponder() -> Bool {
                                    let result = super.becomeFirstResponder()
                                    self.text = self.originalText
                                    self.textColor = self.originalTextColor
                                    return result
                                }
                                override func resignFirstResponder() -> Bool {
                                    let result = super.resignFirstResponder()
                                    updatePlaceHolder()
                            
                                    return result
                                }
                            }
                            

                            【讨论】:

                              【解决方案28】:

                              另一个解决方案可能是像我一样使用keyboardWillHide 和keyboardWillShow 通知。

                              首先,您需要在viewWillAppearviewWillAppear 方法中分别处理监听和不监听通知(以处理内存泄漏)。

                              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
                                  }
                              }
                              

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

                              【讨论】:

                                【解决方案29】:
                                var placeholderLabel : UILabel!
                                  textviewDescription.delegate = self
                                    placeholderLabel = UILabel()
                                    placeholderLabel.text = "Add a description"
                                
                                   func textViewDidChange(_ textView: UITextView) {
                                             placeholderLabel.isHidden = !textviewDescription.text.isEmpty
                                    }
                                

                                【讨论】:

                                • Textview 占位符在 textview 中仅使用标签作为占位符并隐藏占位符以在 textview 中输入文本
                                【解决方案30】:

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

                                我们的想法是制作最简单的解决方案,允许使用不同颜色的占位符,调整占位符大小,不会覆盖delegate,同时保持所有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) }
                                }
                                

                                【讨论】:

                                • 这是一个伟大而简单的解决方案。
                                猜你喜欢
                                • 1970-01-01
                                • 1970-01-01
                                • 2010-11-22
                                • 1970-01-01
                                • 2013-01-18
                                • 2011-07-17
                                • 2017-02-12
                                • 2015-03-31
                                相关资源
                                最近更新 更多