【问题标题】:Placeholder in UITextViewUITextView 中的占位符
【发布时间】:2010-11-22 15:41:02
【问题描述】:

我的应用程序使用UITextView。现在我希望UITextView 有一个类似于您可以为UITextField 设置的占位符。

如何做到这一点?

【问题讨论】:

  • Three20 的 TTTextEditor(本身使用 UITextField)支持占位符文本以及按高度增长(变成 UITextView)。
  • 使用UITextView+Placeholder类怎么样? github.com/devxoul/UITextView-Placeholder
  • 我喜欢@devxoul 的解决方案,因为它使用的是类别,而不是子类。它还在 IB 的检查器中为“占位符”选项(占位符文本和文本颜色)创建了一个字段。它使用了一些绑定技术。多么棒的代码
  • 如果您使用UITextView,则解决方案将变得完全不同。这里有几个解决方案。 Floating PlaceholderFake Native Placeholders

标签: ios objective-c cocoa-touch uitextview placeholder


【解决方案1】:

一个更简单的答案,使用CATextLayer.

CATextLayer 添加到UITextView's 层。 使用UITextViewDelegate 方法,只需更改CATextLayer 的颜色即可。

func txtViewPlaceholder() {
    let textlayer = CATextLayer()

    textlayer.frame = CGRect(x: 5, y: 5, width: 200, height: 18)
    textlayer.contentsScale = UIScreen.main.scale
    textlayer.fontSize = 12
    textlayer.alignmentMode = kCAAlignmentLeft
    textlayer.string = "Enter here"
    textlayer.isWrapped = true
    textlayer.name = "placeholder"
    textlayer.backgroundColor = UIColor.white.cgColor
    textlayer.foregroundColor = UIColor.black.cgColor

    yourTxtVw.layer.insertSublayer(textlayer, at: 0)
}

func removeAddPlaceholder(remove: Bool, textView: UITextView) {
    for layers in textView.layer.sublayers! where layers.name == "placeholder" {
        
        if remove {
            (layers as! CATextLayer).foregroundColor = UIColor.white.cgColor
        } else {
            (layers as! CATextLayer).foregroundColor = UIColor.black.cgColor
        }
        
    }
}


extension YourViewController : UITextViewDelegate {

    func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
    
        removeAddPlaceholder(remove: true, textView: textView)
    
        return true
    }

    func textViewDidEndEditing(_ textView: UITextView) {
    
        if textView.text.count <= 0 {
            removeAddPlaceholder(remove: false, textView: textView)
        }
    }

}

【讨论】:

    【解决方案2】:

    根据这里的一些很好的建议,我能够将UITextView 的以下轻量级、Interface-Builder 兼容的子类组合在一起,其中:

    • 包括可配置的占位符文本,其样式与 UITextField 的样式类似。
    • 不需要任何额外的子视图或约束。
    • 不需要来自 ViewController 的任何委托或其他行为。
    • 不需要任何通知。
    • 将该文本与查看该字段的text 属性的任何外部类完全分开。

    欢迎提出改进建议。

    编辑 1:如果实际文本以编程方式设置,则更新为重置占位符格式。

    编辑 2:现在可以通过编程方式检索占位符文本颜色。

    Swift v5:

    import UIKit
    @IBDesignable class TextViewWithPlaceholder: UITextView {
        
        override var text: String! { // Ensures that the placeholder text is never returned as the field's text
            get {
                if showingPlaceholder {
                    return "" // When showing the placeholder, there's no real text to return
                } else { return super.text }
            }
            set {
                if showingPlaceholder {
                    removePlaceholderFormatting() // If the placeholder text is what's being changed, it's no longer the placeholder
                }
                super.text = newValue
            }
        }
        @IBInspectable var placeholderText: String = ""
        @IBInspectable var placeholderTextColor: UIColor = .placeholderText
        private var showingPlaceholder: Bool = true // Keeps track of whether the field is currently showing a placeholder
        
        override func didMoveToWindow() {
            super.didMoveToWindow()
            if text.isEmpty {
                showPlaceholderText() // Load up the placeholder text when first appearing, but not if coming back to a view where text was already entered
            }
        }
        
        override public func becomeFirstResponder() -> Bool {
            
            // If the current text is the placeholder, remove it
            if showingPlaceholder {
                text = nil
                removePlaceholderFormatting()
            }
            return super.becomeFirstResponder()
        }
        
        override public func resignFirstResponder() -> Bool {
            
            // If there's no text, put the placeholder back
            if text.isEmpty {
                showPlaceholderText()
            }
            return super.resignFirstResponder()
        }
        
        private func showPlaceholderText() {
            
            text = placeholderText
            showingPlaceholder = true
            textColor = placeholderTextColor
        }
        
        private func removePlaceholderFormatting() {
            
            showingPlaceholder = false
            textColor = nil // Put the text back to the default, unmodified color
        }
    }
    

    【讨论】:

    • 手动设置 uitextview 的文本时出现问题,即我的 TextViewWithPlaceholder.text = "some text" 不会将此字符串显示为 txt,而是显示为占位符文本。
    • @AfnanAhmad 要以编程方式设置text,请将showingPlaceHolder 变量设为公开,然后在设置文本之前,将showingPlaceHolder 设置为false 并设置文本。它应该会出现。
    • 感谢@AfnanAhmad 的反馈。我现在进行了一些编辑以更好地支持以编程方式设置的文本。最好不要制作设计为私有的公共组件。
    【解决方案3】:

    TextView PlaceHolder In swift

    import UIKit
    
    @IBDesignable
    open class KMPlaceholderTextView: UITextView {
    
        private struct Constants {
            static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
        }
    
        public let placeholderLabel: UILabel = UILabel()
    
        private var placeholderLabelConstraints = [NSLayoutConstraint]()
    
        @IBInspectable open var placeholder: String = "" {
            didSet {
                placeholderLabel.text = placeholder
            }
        }
    
        @IBInspectable open var placeholderColor: UIColor = KMPlaceholderTextView.Constants.defaultiOSPlaceholderColor {
            didSet {
                placeholderLabel.textColor = placeholderColor
            }
        }
    
        override open var font: UIFont! {
            didSet {
                if placeholderFont == nil {
                    placeholderLabel.font = font
                }
            }
        }
    
        open var placeholderFont: UIFont? {
            didSet {
                let font = (placeholderFont != nil) ? placeholderFont : self.font
                placeholderLabel.font = font
            }
        }
    
        override open var textAlignment: NSTextAlignment {
            didSet {
                placeholderLabel.textAlignment = textAlignment
            }
        }
    
        override open var text: String! {
            didSet {
                textDidChange()
            }
        }
    
        override open var attributedText: NSAttributedString! {
            didSet {
                textDidChange()
            }
        }
    
        override open 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() {
            #if swift(>=4.2)
            let notificationName = UITextView.textDidChangeNotification
            #else
            let notificationName = NSNotification.Name.UITextView.textDidChangeNotification
            #endif
    
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(textDidChange),
                                                   name: notificationName,
                                                   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
            self.layoutIfNeeded()
        }
    
        open override func layoutSubviews() {
            super.layoutSubviews()
            placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
        }
    
        deinit {
            #if swift(>=4.2)
            let notificationName = UITextView.textDidChangeNotification
            #else
            let notificationName = NSNotification.Name.UITextView.textDidChangeNotification
            #endif
    
            NotificationCenter.default.removeObserver(self,
                                                      name: notificationName,
                                                      object: nil)
        }
    
    }
    

    用法

    【讨论】:

      【解决方案4】:

      在 swift 5 中。工作正常。

      class BaseTextView: UITextView {
      
          // MARK: - Views
          private var placeholderLabel: UIlabel!
      
          // MARK: - Init
          override init(frame: CGRect, textContainer: NSTextContainer?) {
              super.init(frame: frame, textContainer: textContainer)
              setupUI()
              startupSetup()
          }
      
          required init?(coder aDecoder: NSCoder) {
              super.init(coder: aDecoder)
              setupUI()
              startupSetup()
          }
      
          deinit {
              NotificationCenter.default.removeObserver(self)
          }    
      }
      
      // MARK: - Setup UI
      private extension BaseTextView {
          func setupUI() {
              addPlaceholderLabel()
      
              textColor = .textColor
          }
      
          func addPlaceholderLabel() {
              placeholderLabel = BaseLabel(frame: .zero)
              placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
              insertSubview(placeholderLabel, at: 0)
      
              placeholderLabel.alpha = 0
              placeholderLabel.numberOfLines = 0
              placeholderLabel.backgroundColor = .clear
              placeholderLabel.textColor = .lightTextColor
              placeholderLabel.lineBreakMode = .byWordWrapping
              placeholderLabel.isUserInteractionEnabled = false
              placeholderLabel.font = UIFont.openSansSemibold.withSize(12)
      
              placeholderLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8).isActive = true
              placeholderLabel.leftAnchor.constraint(equalTo: leftAnchor, constant: 5).isActive = true
              placeholderLabel.rightAnchor.constraint(lessThanOrEqualTo: rightAnchor, constant: -8).isActive = true
              placeholderLabel.bottomAnchor.constraint(lessThanOrEqualTo: bottomAnchor, constant: -8).isActive = true
          }
      }
      
      // MARK: - Startup
      private extension BaseTextView {
          func startupSetup() {
              addObservers()
              textChanged(nil)
              font = UIFont.openSansSemibold.withSize(12)
          }    
      
          func addObservers() {
              NotificationCenter.default.addObserver(self, selector: #selector(textChanged(_:)), name: UITextView.textDidChangeNotification, object: nil)
          }
      }
      
      // MARK: - Actions
      private extension BaseTextView {
          @objc func textChanged(_ sender: Notification?) {
              UIView.animate(withDuration: 0.2) {
                  self.placeholderLabel.alpha = self.text.count == 0 ? 1 : 0
              }    
          }
      }
      
      // MARK: - Public methods
      extension BaseTextView {
          public func setPlaceholder(_ placeholder: String) {
              placeholderLabel.text = placeholder
          }
      }
      

      【讨论】:

        【解决方案5】:

        我发现自己可以很容易地模仿占位符

        1. 在 NIB 或代码中将 textView 的 textColor 设置为 lightGrayColor(大部分时间)
        2. 确保你的 textView 的委托链接到文件的所有者并在你的头文件中实现 UITextViewDelegate
        3. 将文本视图的默认文本设置为(例如:“Foobar 占位符”)
        4. 实现:(BOOL) textViewShouldBeginEditing:(UITextView *)textView

        编辑:

        将 if 语句更改为比较标签而不是文本。如果用户删除了他们的文本,也有可能意外删除占位符@"Foobar placeholder" 的一部分。这意味着如果用户重新输入 textView 以下委托方法-(BOOL) textViewShouldBeginEditing:(UITextView *) textView,它将无法按预期工作。我尝试通过 if 语句中文本的颜色进行比较,但发现界面生成器中设置的浅灰色与 [UIColor lightGreyColor] 代码中设置的浅灰色不同

        - (BOOL) textViewShouldBeginEditing:(UITextView *)textView
        {
            if(textView.tag == 0) {
                textView.text = @"";
                textView.textColor = [UIColor blackColor];
                textView.tag = 1;
            }
            return YES;
        }
        

        也可以在键盘返回且[textView长度] == 0时重置占位符文本

        编辑:

        只是为了让最后一部分更清楚 - 以下是如何设置占位符文本:

        - (void)textViewDidChange:(UITextView *)textView
        {
           if([textView.text length] == 0)
           {
               textView.text = @"Foobar placeholder";
               textView.textColor = [UIColor lightGrayColor];
               textView.tag = 0;
           }
        }
        

        【讨论】:

        • 我非常喜欢这种方法!我对上面的编辑唯一要做的就是将实现从 textViewDidChange: 方法移到 textViewDidEndEditing: 方法中,以便占位符文本仅在您完成对象处理后返回。
        【解决方案6】:

        这是我的 UITextView 版本,支持占位符。斯威夫特 4.2 https://gist.github.com/hlung/c5dda3a0c2087e5ae6c1fce8822c4713

        具有占位符文本支持的 UITextView 子类。它使用另一个 UILabel 显示占位符,当文本为空时显示。

        【讨论】:

          【解决方案7】:

          简单的Swift 3解决方案

          UITextViewDelegate 添加到您的班级

          设置yourTextView.delegate = self

          创建placeholderLabel 并将其放置在yourTextView

          现在只需在textViewDidChange 上为placeholderLabel.alpha 设置动画:

            func textViewDidChange(_ textView: UITextView) {
              let newAlpha: CGFloat = textView.text.isEmpty ? 1 : 0
              if placeholderLabel.alpha != newAlpha {
                UIView.animate(withDuration: 0.3) {
                  self.placeholderLabel.alpha = newAlpha
                }
              }
            }
          

          您可能需要使用placeholderLabel 的位置来正确设置它,但这应该不会太难

          【讨论】:

          • 很好的答案,简单的解决方案。我添加了一个小改进,仅在 alpha 应该更改时进行动画处理:let alpha = CGFloat(textView.text.isEmpty ? 1.0 : 0.0) if alpha != lblPlaceholder.alpha { UIView.animate(withDuration: 0.3) { self.lblPlaceholder.alpha = alpha } }
          【解决方案8】:

          简单的方法,只需使用以下UITextViewDelegate 方法在UITextView 中创建占位符文本:

          - (void)textViewDidBeginEditing:(UITextView *)textView
          {
              if ([textView.text isEqualToString:@"placeholder text here..."]) {
                   textView.text = @"";
                   textView.textColor = [UIColor blackColor]; //optional
              }
              [textView becomeFirstResponder];
          }
          
          - (void)textViewDidEndEditing:(UITextView *)textView
          {
              if ([textView.text isEqualToString:@""]) {
                  textView.text = @"placeholder text here...";
                  textView.textColor = [UIColor lightGrayColor]; //optional
              }
              [textView resignFirstResponder];
          }
          

          只需记住将myUITextView 设置为创建时的确切文本,例如

          UITextView *myUITextView = [[UITextView alloc] init];
          myUITextView.delegate = self;
          myUITextView.text = @"placeholder text here...";
          myUITextView.textColor = [UIColor lightGrayColor]; //optional
          

          并在包含这些方法之前将父类设为UITextViewDelegate,例如

          @interface MyClass () <UITextViewDelegate>
          @end
          

          Swift 3.1 代码

          func textViewDidBeginEditing(_ textView: UITextView) 
          {
              if (textView.text == "placeholder text here..." && textView.textColor == .lightGray)
              {
                  textView.text = ""
                  textView.textColor = .black
              }
              textView.becomeFirstResponder() //Optional
          }
          
          func textViewDidEndEditing(_ textView: UITextView)
          {
              if (textView.text == "")
              {
                  textView.text = "placeholder text here..."
                  textView.textColor = .lightGray
              }
              textView.resignFirstResponder()
          }
          

          只需记住将myUITextView 设置为创建时的确切文本,例如

           let myUITextView = UITextView.init()
           myUITextView.delegate = self
           myUITextView.text = "placeholder text here..."
           myUITextView.textColor = .lightGray
          

          并在包含这些方法之前将父类设为UITextViewDelegate,例如

          class MyClass: UITextViewDelegate
          {
          
          }
          

          【讨论】:

          • 这对于 1 个屏幕和 1 个 UITextView 来说很棒(我喜欢简单)。更复杂的解决方案的原因是,如果您有一个具有许多屏幕和许多 UITextViews 的较大应用程序,您不希望一遍又一遍地这样做。您可能希望继承 UITextView 以满足您的需求,然后使用它。
          • 如果有人在文本框中键入“占位符文本...”,它的行为也类似于占位符文本。此外,在提交期间,您需要检查所有这些标准。
          • 即使字段成为响应者也应该显示占位符文本,此方法不适用于此。
          • @jklp 我认为“过度设计”的方式更干净,更可重用......而且看起来它不会篡改 textview 的 text 属性有点不错..而这个方法修改了它
          • 为什么在委托方法中调用 becomeFirstResponder 和 resignFirstResponder?
          【解决方案9】:

          以下是“SAMTextView”ObjC 代码的 Swift 端口,作为对该问题的首批回复之一。我在 iOS 8 上对其进行了测试。我调整了一些东西,包括用于放置占位符文本的边界偏移,因为原件太高而且太靠右(在该帖子的一个 cmets 中使用了建议)。

          我知道有很多简单的解决方案,但我喜欢子类化 UITextView 的方法,因为它是可重用的,而且我不必将使用它的类与机制混为一谈。

          Swift 2.2:

          import UIKit
          
          class PlaceholderTextView: UITextView {
          
              @IBInspectable var placeholderColor: UIColor = UIColor.lightGrayColor()
              @IBInspectable var placeholderText: String = ""
          
              override var font: UIFont? {
                  didSet {
                      setNeedsDisplay()
                  }
              }
          
              override var contentInset: UIEdgeInsets {
                  didSet {
                      setNeedsDisplay()
                  }
              }
          
              override var textAlignment: NSTextAlignment {
                  didSet {
                      setNeedsDisplay()
                  }
              }
          
              override var text: String? {
                  didSet {
                      setNeedsDisplay()
                  }
              }
          
              override var attributedText: NSAttributedString? {
                  didSet {
                      setNeedsDisplay()
                  }
              }
          
              required init?(coder aDecoder: NSCoder) {
                  super.init(coder: aDecoder)
                  setUp()
              }
          
              override init(frame: CGRect, textContainer: NSTextContainer?) {
                  super.init(frame: frame, textContainer: textContainer)
              }
          
              private func setUp() {
                  NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(PlaceholderTextView.textChanged(_:)),
                                                                   name: UITextViewTextDidChangeNotification, object: self)
              }
          
              func textChanged(notification: NSNotification) {
                  setNeedsDisplay()
              }
          
              func placeholderRectForBounds(bounds: CGRect) -> CGRect {
                  var x = contentInset.left + 4.0
                  var y = contentInset.top  + 9.0
                  let w = frame.size.width - contentInset.left - contentInset.right - 16.0
                  let h = frame.size.height - contentInset.top - contentInset.bottom - 16.0
          
                  if let style = self.typingAttributes[NSParagraphStyleAttributeName] as? NSParagraphStyle {
                      x += style.headIndent
                      y += style.firstLineHeadIndent
                  }
                  return CGRect(x: x, y: y, width: w, height: h)
              }
          
              override func drawRect(rect: CGRect) {
                  if text!.isEmpty && !placeholderText.isEmpty {
                      let paragraphStyle = NSMutableParagraphStyle()
                      paragraphStyle.alignment = textAlignment
                      let attributes: [ String: AnyObject ] = [
                          NSFontAttributeName : font!,
                          NSForegroundColorAttributeName : placeholderColor,
                          NSParagraphStyleAttributeName  : paragraphStyle]
          
                      placeholderText.drawInRect(placeholderRectForBounds(bounds), withAttributes: attributes)
                  }
                  super.drawRect(rect)
              }
          }
          

          Swift 4.2:

          import UIKit
          
          class PlaceholderTextView: UITextView {
          
              @IBInspectable var placeholderColor: UIColor = UIColor.lightGray
              @IBInspectable var placeholderText: String = ""
          
              override var font: UIFont? {
                  didSet {
                      setNeedsDisplay()
                  }
              }
          
              override var contentInset: UIEdgeInsets {
                  didSet {
                      setNeedsDisplay()
                  }
              }
          
              override var textAlignment: NSTextAlignment {
                  didSet {
                      setNeedsDisplay()
                  }
              }
          
              override var text: String? {
                  didSet {
                      setNeedsDisplay()
                  }
              }
          
              override var attributedText: NSAttributedString? {
                  didSet {
                      setNeedsDisplay()
                  }
              }
          
              required init?(coder aDecoder: NSCoder) {
                  super.init(coder: aDecoder)
                  setUp()
              }
          
              override init(frame: CGRect, textContainer: NSTextContainer?) {
                  super.init(frame: frame, textContainer: textContainer)
              }
          
              private func setUp() {
                  NotificationCenter.default.addObserver(self,
                   selector: #selector(self.textChanged(notification:)),
                   name: Notification.Name("UITextViewTextDidChangeNotification"),
                   object: nil)
              }
          
              @objc func textChanged(notification: NSNotification) {
                  setNeedsDisplay()
              }
          
              func placeholderRectForBounds(bounds: CGRect) -> CGRect {
                  var x = contentInset.left + 4.0
                  var y = contentInset.top  + 9.0
                  let w = frame.size.width - contentInset.left - contentInset.right - 16.0
                  let h = frame.size.height - contentInset.top - contentInset.bottom - 16.0
          
                  if let style = self.typingAttributes[NSAttributedString.Key.paragraphStyle] as? NSParagraphStyle {
                      x += style.headIndent
                      y += style.firstLineHeadIndent
                  }
                  return CGRect(x: x, y: y, width: w, height: h)
              }
          
              override func draw(_ rect: CGRect) {
                  if text!.isEmpty && !placeholderText.isEmpty {
                      let paragraphStyle = NSMutableParagraphStyle()
                      paragraphStyle.alignment = textAlignment
                      let attributes: [NSAttributedString.Key: Any] = [
                      NSAttributedString.Key(rawValue: NSAttributedString.Key.font.rawValue) : font!,
                      NSAttributedString.Key(rawValue: NSAttributedString.Key.foregroundColor.rawValue) : placeholderColor,
                      NSAttributedString.Key(rawValue: NSAttributedString.Key.paragraphStyle.rawValue)  : paragraphStyle]
          
                      placeholderText.draw(in: placeholderRectForBounds(bounds: bounds), withAttributes: attributes)
                  }
                  super.draw(rect)
              }
          }
          

          【讨论】:

          • 感谢做 swift 版本,你能解释一下有一个只调用 super 的 awakeFromNib 方法有什么意义吗?
          • 它设置占位符,但是一旦你开始输入就不会更新。
          • 没关系,我将通知调用放在 awakeFromNib 中,它现在可以工作了。
          【解决方案10】:

          我关注了code from this link。只有7个简单的步骤。它向 textView 添加一个 UILabel 并在通过 textView 的 textViewDidChangeSelection(_ textView: UITextView) 委托方法从 textView 输入或删除文本时隐藏/显示标签。我把步骤放在代码上面的cmets中。

          // 1. make sure to include the UITextViewDelegate
          class YourClass: UITextViewDelegate {
          
              @IBOutlet weak var textView : UITextView!
          
              // 2. create placeholder textLabel
              let placeHolderTextLabel: UILabel = {
                  let placeholderLabel = UILabel()
                  placeholderLabel.text = "Placeholder text..."
                  placeholderLabel.sizeToFit()
                  placeholderLabel.textColor = UIColor.lightGray
                  return placeholderLabel
              }()
          
              override func viewDidLoad() {
                  super.viewDidLoad()
          
                  // 3. set textView delegate
                  textView.delegate = self
          
                  configurePlaceholderTextLabel()
              }
          
          
              func configurePlaceholderTextLabel() {
          
                  // 4. add placeholder label to textView, set it's frame and font
                  textView.addSubview(placeHolderTextLabel)
                  placeHolderTextLabel.frame.origin = CGPoint(x: 5, y: (textView.font?.pointSize)! / 2)
                  placeHolderTextLabel.font = UIFont.systemFont(ofSize: (textView.font?.pointSize)!)
          
                  // 5. decide wether the placeHolderTextLabel is hidden or not depending on if there is or isn't text inside the textView
                  placeHolderTextLabel.isHidden = !textView.text.isEmpty
          
              }
          
              // 6. implement textView delegate method to update the placeHolderTextLabel when the text is changed
              func textViewDidChangeSelection(_ textView: UITextView) {
          
                  // 7. decide wether the placeHolderTextLabel is hidden or not depending on if there is or isn't text inside the textView when text in textView is changed
                  placeHolderTextLabel.isHidden = !textView.text.isEmpty
              }
          
          }
          

          【讨论】:

            【解决方案11】:

            如果有人需要 Swift 解决方案:

            将 UITextViewDelegate 添加到您的课程中

            var placeHolderText = "Placeholder Text..."
            
            override func viewDidLoad() {
                super.viewDidLoad()
                textView.delegate = self
            }
            
            func textViewShouldBeginEditing(textView: UITextView) -> Bool {
            
                self.textView.textColor = .black
            
                if(self.textView.text == placeHolderText) {
                    self.textView.text = ""
                }
            
                return true
            }
            
            func textViewDidEndEditing(textView: UITextView) {
                if(textView.text == "") {
                    self.textView.text = placeHolderText
                    self.textView.textColor = .lightGray
                }
            }
            
            override func viewWillAppear(animated: Bool) {
            
                if(currentQuestion.answerDisplayValue == "") {
                    self.textView.text = placeHolderText
                    self.textView.textColor = .lightGray
                } else {
                    self.textView.text = "xxx" // load default text / or stored 
                    self.textView.textColor = .black
                }
            }
            

            【讨论】:

            • 这还可以,但还不够好。如果用户键入“占位符文本...”(显然是边缘情况),它会破坏您的逻辑
            【解决方案12】:

            另一种解决方案

            import UIKit
            
            protocol PlaceholderTextViewDelegate: class {
            
                func placeholderTextViewDidChangeText(_ text: String)
                func placeholderTextViewDidEndEditing(_ text: String)
            }
            
            final class PlaceholderTextView: UITextView {
            
                weak var notifier: PlaceholderTextViewDelegate?
                var ignoreEnterAction: Bool = true
            
                var placeholder: String? {
                    didSet {
                        text = placeholder
                        selectedRange = NSRange(location: 0, length: 0)
                    }
                }
            
                var placeholderColor = UIColor.lightGray {
                    didSet {
                        if text == placeholder {
                            textColor = placeholderColor
                        }
                    }
                }
                var normalTextColor = UIColor.lightGray
            
                var placeholderFont = UIFont.sfProRegular(28)
            
                fileprivate var placeholderLabel: UILabel?
            
                // MARK: - LifeCycle
            
                override var text: String? {
                    didSet {
                        if text == placeholder {
                            textColor = placeholderColor
                        } else {
                            textColor = normalTextColor
                        }
                    }
                }
            
                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
                }
            }
            
            extension PlaceholderTextView: UITextViewDelegate {
            
                // MARK: - UITextViewDelegate
                func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
            
                    if text == "" && textView.text == placeholder {
                        return false
                    }
            
                    if let placeholder = placeholder,
                        textView.text == placeholder,
                        range.location <= placeholder.count {
                        textView.text = ""
                    }
            
                    if ignoreEnterAction && text == "\n" {
                        textView.resignFirstResponder()
                        return false
                    }
                    return true
                }
            
                func textViewDidChange(_ textView: UITextView) {
                    if let placeholder = placeholder {
                        textView.text = textView.text.replacingOccurrences(of: placeholder, with: "")
                    }
            
                    if let placeholder = placeholder,
                        text?.isEmpty == true {
                        text = placeholder
                        textColor = placeholderColor
            
                        selectedRange = NSRange(location: 0, length: 0)
                    } else {
                        textColor = normalTextColor
                    }
            
                    notifier?.placeholderTextViewDidChangeText(textView.text)
                }
            
                func textViewDidChangeSelection(_ textView: UITextView) {
                    if let placeholder = placeholder,
                        text == placeholder {
                        selectedRange = NSRange(location: 0, length: 0)
                    }
                }
            
                func textViewDidEndEditing(_ textView: UITextView) {
                    notifier?.placeholderTextViewDidEndEditing(textView.text)
            
                    if let placeholder = placeholder,
                        text?.isEmpty == true {
                        text = placeholder
                        textColor = placeholderColor
                        selectedRange = NSRange(location: 0, length: 0)
                    } else {
                        textColor = normalTextColor
                    }
                }
            }
            

            结果:

            【讨论】:

            • 占位符在输入文本后没有改变。
            【解决方案13】:

            我推荐使用 pod 'UITextView+Placeholder'

            pod 'UITextView+Placeholder'
            

            在您的代码上

            #import "UITextView+Placeholder.h"
            
            ////    
            
            UITextView *textView = [[UITextView alloc] init];
            textView.placeholder = @"How are you?";
            textView.placeholderColor = [UIColor lightGrayColor];
            

            【讨论】:

              【解决方案14】:

              我创建了一个 swift 3 版本的highest ranked answer

              你只需要做 UITextView 的子类化。

              import UIKit
              
               class UIPlaceHolderTextView: UITextView {
              
              
              //MARK: - Properties
              @IBInspectable var placeholder: String?
              @IBInspectable var placeholderColor: UIColor?
              var placeholderLabel: UILabel?
              
              
              //MARK: - Initializers
              override func awakeFromNib() {
                  super.awakeFromNib()
              
              
              }
              
              required init?(coder aDecoder: NSCoder) {
                  super.init(coder: aDecoder)
              
                  // Use Interface Builder User Defined Runtime Attributes to set
                  // placeholder and placeholderColor in Interface Builder.
                  if self.placeholder == nil {
                      self.placeholder = ""
                  }
              
                  if self.placeholderColor == nil {
                      self.placeholderColor = UIColor.black
                  }
              
                  NotificationCenter.default.addObserver(self, selector: #selector(textChanged(_:)), name: NSNotification.Name.UITextViewTextDidChange, object: nil)
              
              }
              
              func textChanged(_ notification: Notification) -> Void {
                  if self.placeholder?.count == 0 {
                      return
                  }
              
                  UIView.animate(withDuration: 0.25) {
                      if self.text.count == 0 {
                          self.viewWithTag(999)?.alpha = 1
                      }
                      else {
                          self.viewWithTag(999)?.alpha = 0
                      }
                  }
              }
              
              // Only override draw() if you perform custom drawing.
              // An empty implementation adversely affects performance during animation.
              override func draw(_ rect: CGRect) {
                  super.draw(rect)
              
                  if (self.placeholder?.count ?? 0) > 0 {
                      if placeholderLabel == nil {
                          placeholderLabel = UILabel.init()
                          placeholderLabel?.lineBreakMode = .byWordWrapping
                          placeholderLabel?.numberOfLines = 0
                          placeholderLabel?.font = self.font
                          placeholderLabel?.backgroundColor = self.backgroundColor
                          placeholderLabel?.textColor = self.placeholderColor
                          placeholderLabel?.alpha = 0
                          placeholderLabel?.tag = 999
                          self.addSubview(placeholderLabel!)
              
                          placeholderLabel?.translatesAutoresizingMaskIntoConstraints = false
                          placeholderLabel?.topAnchor.constraint(equalTo: self.topAnchor, constant: 7).isActive = true
                          placeholderLabel?.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 4).isActive = true
                          placeholderLabel?.bottomAnchor.constraint(equalTo: self.bottomAnchor).isActive = true
                          placeholderLabel?.rightAnchor.constraint(equalTo: self.rightAnchor).isActive = true
                      }
              
                      placeholderLabel?.text = self.placeholder
                      placeholderLabel?.sizeToFit()
                      self.sendSubview(toBack: self.placeholderLabel!)
                  }
              
                  if self.text.count == 0 && (self.placeholder?.count ?? 0) > 0 {
                      self.viewWithTag(999)?.alpha = 1
                  }
               }
              }
              

              【讨论】:

              • 占位符根本不显示。使用 Swift 5。有什么想法吗?
              【解决方案15】:

              这是 swift 3.1 的代码

              Jason George 在第一个答案中的原始代码。

              不要忘记在界面构建器中将 TextView 的自定义类设置为 UIPlaceHolderTextView,然后设置 placeholder 和 placeHolder 属性。

              import UIKit
              
              @IBDesignable
              class UIPlaceHolderTextView: UITextView {
              
              @IBInspectable var placeholder: String = ""
              @IBInspectable var placeholderColor: UIColor = UIColor.lightGray
              
              private let uiPlaceholderTextChangedAnimationDuration: Double = 0.05
              private let defaultTagValue = 999
              
              private var placeHolderLabel: UILabel?
              
              override func awakeFromNib() {
                  super.awakeFromNib()
                  NotificationCenter.default.addObserver(
                      self,
                      selector: #selector(textChanged),
                      name: NSNotification.Name.UITextViewTextDidChange,
                      object: nil
                  )
              }
              
              override init(frame: CGRect, textContainer: NSTextContainer?) {
                  super.init(frame: frame, textContainer: textContainer)
                  NotificationCenter.default.addObserver(
                      self,
                      selector: #selector(textChanged),
                      name: NSNotification.Name.UITextViewTextDidChange,
                      object: nil
                  )
              }
              
              required init?(coder aDecoder: NSCoder) {
                  super.init(coder: aDecoder)
                  NotificationCenter.default.addObserver(
                      self,
                      selector: #selector(textChanged),
                      name: NSNotification.Name.UITextViewTextDidChange,
                      object: nil
                  )
              }
              
              deinit {
                  NotificationCenter.default.removeObserver(
                      self,
                      name: NSNotification.Name.UITextViewTextDidChange,
                      object: nil
                  )
              }
              
              @objc private func textChanged() {
                  guard !placeholder.isEmpty else {
                      return
                  }
                  UIView.animate(withDuration: uiPlaceholderTextChangedAnimationDuration) {
                      if self.text.isEmpty {
                          self.viewWithTag(self.defaultTagValue)?.alpha = CGFloat(1.0)
                      }
                      else {
                          self.viewWithTag(self.defaultTagValue)?.alpha = CGFloat(0.0)
                      }
                  }
              }
              
              override var text: String! {
                  didSet{
                      super.text = text
                      textChanged()
                  }
              }
              
              override func draw(_ rect: CGRect) {
                  if !placeholder.isEmpty {
                      if placeHolderLabel == nil {
                          placeHolderLabel = UILabel.init(frame: CGRect(x: 0, y: 8, width: bounds.size.width - 16, height: 0))
                          placeHolderLabel!.lineBreakMode = .byWordWrapping
                          placeHolderLabel!.numberOfLines = 0
                          placeHolderLabel!.font = font
                          placeHolderLabel!.backgroundColor = UIColor.clear
                          placeHolderLabel!.textColor = placeholderColor
                          placeHolderLabel!.alpha = 0
                          placeHolderLabel!.tag = defaultTagValue
                          self.addSubview(placeHolderLabel!)
                      }
              
                      placeHolderLabel!.text = placeholder
                      placeHolderLabel!.sizeToFit()
                      self.sendSubview(toBack: placeHolderLabel!)
              
                      if text.isEmpty && !placeholder.isEmpty {
                          viewWithTag(defaultTagValue)?.alpha = 1.0
                      }
                  }
              
                  super.draw(rect)
              }
              }
              

              【讨论】:

                【解决方案16】:

                我知道这个问题已经有很多答案了,但我并没有真正找到足够的答案(至少在 Swift 中)。我需要UITextView 中的UITextField 的“占位符”功能(我想要确切的行为,包括文本显示属性、动画等,并且不想随着时间的推移维护它)。我还想要一个解决方案,它提供与UITextField 相同的确切边框(不是一个看起来有点像现在的近似值,而是一个看起来完全像它并且永远看起来完全像它的近似值)。因此,虽然我最初并不喜欢在混音中加入额外的控制,但似乎为了实现我的目标,我必须使用实际的 UITextField 并让它完成工作。

                此解决方案处理占位符的定位并在两个控件之间保持字体同步,以便占位符文本与输入到控件中的文本的字体和位置完全相同(许多其他解决方案未解决的问题)。

                // This class is necessary to support "inset" (required to position placeholder 
                // appropriately in TextView)
                //
                class TextField: UITextField
                {
                    var inset: UIEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0);
                
                    override func textRectForBounds(bounds: CGRect) -> CGRect
                    {
                        return UIEdgeInsetsInsetRect(bounds, inset);
                    }
                
                    override func placeholderRectForBounds(bounds: CGRect) -> CGRect
                    {
                        return UIEdgeInsetsInsetRect(bounds, inset);
                    }
                }
                
                // This class implements a UITextView that has a UITextField behind it, where the 
                // UITextField provides the border and the placeholder text functionality (so that the
                // TextView looks and works like a UITextField).
                //
                class TextView : UITextView, UITextViewDelegate
                {
                    var textField = TextField();
                
                    required init?(coder: NSCoder)
                    {
                        super.init(coder: coder);
                    }
                
                    override init(frame: CGRect, textContainer: NSTextContainer?)
                    {
                        super.init(frame: frame, textContainer: textContainer);
                
                        self.delegate = self;
                
                        // Create a background TextField with clear (invisible) text and disabled
                        self.textField.borderStyle = UITextBorderStyle.RoundedRect;
                        self.textField.textColor = UIColor.clearColor();
                        self.textField.userInteractionEnabled = false;
                
                        // Align the background TextView to where text appears in the TextField, so
                        // that any placeholder will be in the correct position.
                        self.textField.contentVerticalAlignment = UIControlContentVerticalAlignment.Top;
                        self.textField.inset = UIEdgeInsets(
                            top: self.textContainerInset.top,
                            left: self.textContainerInset.left + self.textContainer.lineFragmentPadding,
                            bottom: self.textContainerInset.bottom,
                            right: self.textContainerInset.right
                        );
                
                        // The background TextField should use the same font (for the placeholder)
                        self.textField.font = self.font;
                
                        self.addSubview(textField);
                        self.sendSubviewToBack(textField);
                    }
                
                    convenience init()
                    {
                        self.init(frame: CGRectZero, textContainer: nil)
                    }
                
                    override var font: UIFont?
                    {
                        didSet
                        {
                            // Keep the font of the TextView and background textField in sync
                            self.textField.font = self.font;
                        }
                    }
                
                    var placeholder: String? = nil
                    {
                        didSet
                        {
                            self.textField.placeholder = self.placeholder;
                        }
                    }
                
                    override func layoutSubviews()
                    {
                        super.layoutSubviews()
                        // Do not scroll the background textView
                        self.textField.frame = CGRectMake(0, self.contentOffset.y, self.frame.width, self.frame.height);
                    }
                
                    // UITextViewDelegate - Note: If you replace delegate, your delegate must call this
                    func scrollViewDidScroll(scrollView: UIScrollView)
                    {
                        // Do not scroll the background textView
                        self.textField.frame = CGRectMake(0, self.contentOffset.y, self.frame.width, self.frame.height);
                    }
                
                    // UITextViewDelegate - Note: If you replace delegate, your delegate must call this
                    func textViewDidChange(textView: UITextView)
                    {
                        // Updating the text in the background textView will cause the placeholder to 
                        // appear/disappear (including any animations of that behavior - since the
                        // textView is doing this itself).
                        self.textField.text = self.text;
                    }
                }
                

                【讨论】:

                • 我修复了它(让我知道它是否有效)。让初始化程序工作过去很痛苦,所以我不支持 Storyboard/XIB 初始化(我们不在我们的应用程序中使用它们 - 我们以编程方式创建所有控件)。我对“崩溃”表示例外。致命错误的意图是表明它不支持从 Storyboard/XIB 初始化(尽管从消息中承认这不是很清楚)。
                【解决方案17】:

                很抱歉添加另一个答案,但我刚刚取消了类似的内容,这创建了最接近 UITextField 类型的占位符。

                希望这对某人有所帮助。

                -(void)textViewDidChange:(UITextView *)textView{
                    if(textView.textColor == [UIColor lightGrayColor]){
                        textView.textColor  = [UIColor blackColor]; // look at the comment section in this answer
                        textView.text       = [textView.text substringToIndex: 0];// look at the comment section in this answer
                    }else if(textView.text.length == 0){
                        textView.text       = @"This is some placeholder text.";
                        textView.textColor  = [UIColor lightGrayColor];
                        textView.selectedRange = NSMakeRange(0, 0);
                    }
                }
                
                -(void)textViewDidChangeSelection:(UITextView *)textView{
                    if(textView.textColor == [UIColor lightGrayColor] && (textView.selectedRange.location != 0 || textView.selectedRange.length != 0)){
                        textView.selectedRange = NSMakeRange(0, 0);
                    }
                }
                

                【讨论】:

                • 我必须在第一个 if 语句 if(textView.textColor == [UIColor lightGrayColor]){ textView.textColor = [UIColor blackColor]; textView.text = [textView.text substringToIndex: 1]; 中更改命令的顺序,否则在 textview 中输入的第一个字符放在文本的末尾
                【解决方案18】:

                斯威夫特 3.1

                在尝试了所有快速的答案后,这个答案可以为我节省 3 个小时的研究时间。希望这会有所帮助。

                1. 确保您的 textField(无论您有什么自定义名称)在 Storyboard 中指向它的委托,并且有一个 @IBOutlet 和 yourCustomTextField

                2. 添加到viewDidLoad()下面,加载视图时会出现:

                告诉我什么是占位符:

                yourCustomTextField = "Start typing..." 
                yourCustomTextField.textColor = .lightGray
                
                1. 在 viewDidLoad 之外但在同一个类中添加以下声明:UIViewController, UITextViewDelegate, UINavigationControllerDelegate

                此代码将使您的CustomTextField 在输入文本字段时消失:

                func textViewDidBeginEditing (_ textView: UITextView) { 
                
                    if (textView.text == "Start typing...") {
                
                        textView.text = ""
                        textView.textColor = .black
                    }
                
                    textView.becomeFirstResponder()
                }
                
                func textViewDidEndEditing(_ textView: UITextView) {
                    if (textView.text == "") {
                
                        textView.text = "Start typing..."
                        textView.textColor = .lightGray
                    }
                
                    textView.resignFirstResponder()
                }
                

                【讨论】:

                  【解决方案19】:

                  模拟原生占位符


                  一个常见的抱怨是 iOS 没有为文本视图提供原生占位符功能。下面的 UITextView 扩展试图通过提供人们期望从本机功能获得的便利来解决这个问题,只需要 一行 代码来将占位符添加到 textview 实例。

                  这个解决方案的缺点是,因为它菊花链委托调用,它很容易受到(不太可能)在 iOS 更新中对 UITextViewDelegate 协议的更改。具体来说,如果 iOS 添加了新的协议方法,并且您在代理中为带有占位符的文本视图实现了其中的任何方法,则不会调用这些方法,除非您更新了扩展以转发这些方法来电。

                  或者,Inline Placeholder 的答案是坚如磐石,并且尽可能简单。


                  用法示例:


                     • 如果获得占位符的文本视图使用UITextViewDelegate

                      /* Swift 3 */
                  
                      class NoteViewController : UIViewController {
                          @IBOutlet weak var noteView: UITextView!
                          override func viewDidLoad() {
                              noteView.addPlaceholder("Enter some text...",  color: UIColor.lightGray)
                          }
                      }
                  

                                                            -- 或者--

                     • 如果获得占位符的文本视图确实使用UITextViewDelegate

                      /* Swift 3 */
                  
                      class NoteViewController : UIViewController, UITextViewDelegate {
                          @IBOutlet weak var noteView: UITextView!
                          override func viewDidLoad() {
                              noteView.addPlaceholder("Phone #", color: UIColor.lightGray, delegate: self)
                          }
                      }
                  

                  实现(UITextView 扩展):


                  /* Swift 3 */
                  
                  extension UITextView: UITextViewDelegate
                  {
                  
                      func addPlaceholder(_ placeholderText : String, 
                                        color : UIColor? = UIColor.lightGray,
                                        delegate : UITextViewDelegate? = nil) {
                  
                          self.delegate = self             // Make receiving textview instance a delegate
                          let placeholder = UITextView()   // Need delegate storage ULabel doesn't provide
                          placeholder.isUserInteractionEnabled = false  //... so we *simulate* UILabel
                          self.addSubview(placeholder)     // Add to text view instance's view tree               
                          placeholder.sizeToFit()          // Constrain to fit inside parent text view
                          placeholder.tintColor = UIColor.clear // Unused in textviews. Can host our 'tag'
                          placeholder.frame.origin = CGPoint(x: 5, y: 0) // Don't cover I-beam cursor
                          placeholder.delegate = delegate  // Use as cache for caller's delegate 
                          placeholder.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
                          placeholder.text = placeholderText
                          placeholder.textColor = color
                      }
                  
                        
                      func findPlaceholder() -> UITextView? { // find placeholder by its tag 
                          for subview in self.subviews {
                              if let textview = subview as? UITextView {
                                  if textview.tintColor == UIColor.clear { // sneaky tagging scheme
                                      return textview
                                  }
                              }
                          }
                          return nil
                      }
                       
                      /* 
                       * Safely daisychain to caller delegate methods as appropriate...
                       */
                  
                      public func textViewDidChange(_ textView: UITextView) { // ←  need this delegate method
                          if let placeholder = findPlaceholder() {
                              placeholder.isHidden = !self.text.isEmpty // ← ... to do this
                              placeholder.delegate?.textViewDidChange?(textView)
                          } 
                      }
                  
                      /* 
                       * Since we're becoming a delegate on behalf of this placeholder-enabled
                       * text view instance, we must forward *all* that protocol's activity expected
                       * by the instance, not just the particular optional protocol method we need to
                       * intercept, above.
                       */
                  
                      public func textViewDidEndEditing(_ textView: UITextView) {
                          if let placeholder = findPlaceholder() {
                              placeholder.delegate?.textViewDidEndEditing?(textView)
                          } 
                      }
                  
                      public func textViewDidBeginEditing(_ textView: UITextView) {
                          if let placeholder = findPlaceholder() {
                              placeholder.delegate?.textViewDidBeginEditing?(textView)
                          } 
                      }
                  
                      public  func textViewDidChangeSelection(_ textView: UITextView) {
                          if let placeholder = findPlaceholder() {
                              placeholder.delegate?.textViewDidChangeSelection?(textView)
                          } 
                      }
                  
                      public func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
                          if let placeholder = findPlaceholder() {
                              guard let retval = placeholder.delegate?.textViewShouldEndEditing?(textView) else {
                                  return true
                              }
                              return retval
                          }
                          return true
                      }
                  
                      public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
                          if let placeholder = findPlaceholder() {
                              guard let retval = placeholder.delegate?.textViewShouldBeginEditing?(textView) else {
                                  return true
                              }
                              return retval
                          } 
                          return true
                      }
                  
                      public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
                          if let placeholder = findPlaceholder() {
                              guard let retval = placeholder.delegate?.textView?(textView, shouldChangeTextIn: range, replacementText: text) else {
                                  return true
                              }
                              return retval
                          } 
                          return true
                      }
                  
                      public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
                          if let placeholder = findPlaceholder() {
                                  guard let retval = placeholder.delegate?.textView?(textView, shouldInteractWith: URL, in: characterRange, interaction:
                                      interaction) else {
                                          return true
                              }
                              return retval
                          }
                          return true
                      }
                  
                      public func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
                          if let placeholder = findPlaceholder() {
                              guard let retval = placeholder.delegate?.textView?(textView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) else {
                                  return true
                              }
                              return retval
                          }
                          return true
                      }
                  }
                  

                  1. 作为基本 iOS 类(如 UITextView)的扩展,重要的是要知道这段代码与任何文本视图没有交互不激活占位符,例如尚未通过调用 addPlaceholder()

                  初始化的 textview 实例

                  2. 启用占位符的文本视图透明地变为 UITextViewDelegate 以跟踪字符数,在为了控制占位符的可见性。如果将委托传递给 addPlaceholder(),则此代码菊花链(即转发)将回调委托给该委托。

                  3.作者正在研究检查UITextViewDelegate协议并自动代理它的方法,而无需必须对每种方法进行硬编码。这将使代码免受方法签名更改和添加到协议中的新方法的影响。

                  【讨论】:

                    【解决方案20】:

                    我用swift写了一个类。您可以在需要时导入此类。

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

                    }

                    【讨论】:

                    • 这实际上是我能找到的最干净的解决方案之一,我最终使用了它。特别是考虑到插入和对标签使用约束是一个很好的接触,我没见过许多(任何?)其他解决方案负责更改边界、字体、RTL 等。
                    【解决方案21】:

                    修改占位符文本颜色的最简单方法是通过 XCode 故事板界面构建器。选择感兴趣的 UITextField 并打开右侧的身份检查器。单击 User Defined Runtime Attributes 中的加号并添加一个新行,其中 Key Path 为 _placeholderLabel.textColor,Type 为 Color,Value 为您想要的颜色。

                    【讨论】:

                    • 与问题无关,甚至与UITextView无关。
                    【解决方案22】:

                    这完美地模仿了 UITextField 的占位符,占位符文本一直保留到您实际输入内容为止。

                    private let placeholder = "Type here"
                    
                    @IBOutlet weak var textView: UITextView! {
                        didSet {
                            textView.textColor = UIColor.lightGray
                            textView.text = placeholder
                            textView.selectedRange = NSRange(location: 0, length: 0)
                        }
                    }
                    
                    extension ViewController: UITextViewDelegate {
                    
                        func textViewDidChangeSelection(_ textView: UITextView) {
                            // Move cursor to beginning on first tap
                            if textView.text == placeholder {
                                textView.selectedRange = NSRange(location: 0, length: 0)
                            }
                        }
                    
                        func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
                            if textView.text == placeholder && !text.isEmpty {
                                textView.text = nil
                                textView.textColor = UIColor.black
                                textView.selectedRange = NSRange(location: 0, length: 0)
                            }
                            return true
                        }
                    
                        func textViewDidChange(_ textView: UITextView) {
                            if textView.text.isEmpty {
                                textView.textColor = UIColor.lightGray
                                textView.text = placeholder
                            }
                        }
                    }
                    

                    【讨论】:

                    • 需要添加几行才能让我的 SwiftUI 应用程序与 UITextView SwuftUI 包装器一起使用,但它看起来是最简单的解决方案,不包含任何 ObjC 代码、第 3 方框架或专门调配的方法。
                    【解决方案23】:

                    只需创建 UITextView@IBDesignable 子类:

                    @IBDesignable class AttributedTextView: UITextView {
                    
                        private let placeholderLabel = UILabel()
                    
                        @IBInspectable var placeholder: String = "" {
                    
                            didSet {
                    
                                setupPlaceholderLabelIfNeeded()
                                textViewDidChange()
                            }
                        }
                    
                        override var text: String! {
                    
                            didSet {
                                textViewDidChange()
                            }
                        }
                    
                        //MARK: - Initialization
                    
                        override func awakeFromNib() {
                            super.awakeFromNib()
                    
                            setupPlaceholderLabelIfNeeded()
                            NotificationCenter.default.addObserver(self, selector: #selector(textViewDidChange), name: .UITextViewTextDidChange, object: nil)
                        }
                    
                        //MARK: - Deinitialization
                    
                        deinit {
                            NotificationCenter.default.removeObserver(self)
                        }
                    
                        //MARK: - Internal
                    
                        func textViewDidChange() {
                    
                            placeholderLabel.isHidden = !text.isEmpty
                            layoutIfNeeded()
                        }
                    
                        //MARK: - Private
                    
                        private func setupPlaceholderLabelIfNeeded() {
                    
                            placeholderLabel.removeFromSuperview()
                            placeholderLabel.frame = CGRect(x: 0, y: 8, width: frame.size.width, height: 0)
                            placeholderLabel.textColor = UIColor.lightGray
                            placeholderLabel.text = placeholder
                    
                            placeholderLabel.sizeToFit()
                    
                            insertSubview(placeholderLabel, at: 0)
                        }
                    }
                    

                    然后只需在身份检查器中设置您的占位符:

                    【讨论】:

                      【解决方案24】:

                      我通读了所有这些,但想出了一个非常简短的 Swift 3 解决方案,它在我的所有测试中都有效。它可以更具普遍性,但过程很简单。这是我称之为“TextViewWithPlaceholder”的全部内容。

                      import UIKit
                      
                      class TextViewWithPlaceholder: UITextView {
                      
                          public var placeholder: String?
                          public var placeholderColor = UIColor.lightGray
                      
                          private var placeholderLabel: UILabel?
                      
                          // Set up notification listener when created from a XIB or storyboard.
                          // You can also set up init() functions if you plan on creating
                          // these programmatically.
                          override func awakeFromNib() {
                              super.awakeFromNib()
                      
                              NotificationCenter.default.addObserver(self,
                                                                 selector: #selector(TextViewWithPlaceholder.textDidChangeHandler(notification:)),
                                                                 name: .UITextViewTextDidChange,
                                                                 object: self)
                      
                              placeholderLabel = UILabel()
                              placeholderLabel?.alpha = 0.85
                              placeholderLabel?.textColor = placeholderColor
                          }
                      
                          // By using layoutSubviews, you can size and position the placeholder
                          // more accurately. I chose to hard-code the size of the placeholder
                          // but you can combine this with other techniques shown in previous replies.
                          override func layoutSubviews() {
                              super.layoutSubviews()
                      
                              placeholderLabel?.textColor = placeholderColor
                              placeholderLabel?.text = placeholder
                      
                              placeholderLabel?.frame = CGRect(x: 6, y: 4, width: self.bounds.size.width-16, height: 24)
                      
                              if text.isEmpty {
                                  addSubview(placeholderLabel!)
                                  bringSubview(toFront: placeholderLabel!)
                              } else {
                                  placeholderLabel?.removeFromSuperview()
                              }
                          }
                      
                          // Whenever the text changes, just trigger a new layout pass.
                          func textDidChangeHandler(notification: Notification) {
                              layoutSubviews()
                          }
                      }
                      

                      【讨论】:

                      • 这里有一些问题。你不应该直接打电话给layoutSubviews()。而且您没有删除 NotificationCenter 观察者。
                      【解决方案25】:

                      在查看(并尝试)大多数针对 UITextView 这个看似明显但缺失的功能的建议解决方案之后,我发现最接近的“最佳”解决方案来自 BobDickinson。但我不喜欢求助于一个全新的子类[我更喜欢插入式类别来添加这样简单的功能],也不喜欢它拦截 UITextViewDelegate 方法,这可能会弄乱你现有的 UITextView 处理代码。因此,这是我对适用于任何现有 UITextView 实例的插入类别的看法...

                      #import <objc/runtime.h>
                      
                      // Private subclass needed to override placeholderRectForBounds: to correctly position placeholder
                      @interface _TextField : UITextField
                      @property UIEdgeInsets insets;
                      @end
                      @implementation _TextField
                      - (CGRect)placeholderRectForBounds:(CGRect)bounds
                      {
                          CGRect rect = [super placeholderRectForBounds:bounds];
                          return UIEdgeInsetsInsetRect(rect, _insets);
                      }
                      @end
                      
                      @implementation UITextView (Placeholder)
                      
                      static const void *KEY;
                      
                      - (void)setPlaceholder:(NSString *)placeholder
                      {
                          _TextField *textField = objc_getAssociatedObject(self, &KEY);
                          if (!textField) {
                              textField = [_TextField.alloc initWithFrame:self.bounds];
                              textField.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
                              textField.userInteractionEnabled = NO;
                              textField.font = self.font;
                      
                              textField.contentVerticalAlignment = UIControlContentVerticalAlignmentTop;
                              textField.insets = UIEdgeInsetsMake(self.textContainerInset.top,
                                                                  self.textContainerInset.left + self.textContainer.lineFragmentPadding,
                                                                  self.textContainerInset.bottom,
                                                                  self.textContainerInset.right);
                              [self addSubview:textField];
                              [self sendSubviewToBack:textField];
                      
                              objc_setAssociatedObject(self, &KEY, textField, OBJC_ASSOCIATION_RETAIN);
                      
                              [NSNotificationCenter.defaultCenter addObserver:self selector:@selector(updatePlaceholder:) name:UITextViewTextDidChangeNotification object:nil];
                          }
                          textField.placeholder = placeholder;
                      }
                      
                      - (NSString*)placeholder
                      {
                          UITextField *textField = objc_getAssociatedObject(self, &KEY);
                          return textField.placeholder;
                      }
                      
                      - (void)updatePlaceholder:(NSNotification *)notification
                      {
                          UITextField *textField = objc_getAssociatedObject(self, &KEY);
                          textField.font = self.font;
                          [textField setAlpha:self.text.length? 0 : 1];
                      }
                      
                      @end
                      

                      简单易用,一目了然

                      UITextView *myTextView = UITextView.new;
                      ...
                      myTextView.placeholder = @"enter text here";
                      

                      它的工作原理是在正确的位置添加一个 UITextField - 在您的 UITextView 后面,并利用 it's 占位符代替(因此您不必担心颜色是否正确等),然后在您的 UITextView 更改为显示/隐藏此 UITextField 时侦听通知(因此它不会干扰您现有的 UITextViewDelegate 调用)。而且没有任何神奇的数字... :-)

                      objc_setAssociatedObject()/objc_getAssociatedObject() 是为了避免继承 UITextView。 [不幸的是,为了正确定位 UITextField,有必要引入一个“私有”子类来覆盖 placeholderRectForBounds:]

                      改编自 BobDickinson 的 Swift 回答。

                      【讨论】:

                        【解决方案26】:

                        在 UITextView 中支持图标属性占位符的简单类PlaceholderTextView

                        @IBOutlet weak var tvMessage: PlaceholderTextView!
                        //  TODO: - Create Icon Text Attachment
                        let icon: NSTextAttachment = NSTextAttachment()
                        icon.image = UIImage(named: "paper-plane")
                        let iconString = NSMutableAttributedString(attributedString: NSAttributedString(attachment: icon))
                        
                        tvMessage.icon = icon
                        
                        //  TODO: - Attributes
                        let textColor = UIColor.gray
                        let lightFont = UIFont(name: "Helvetica-Light", size: tvMessage.font!.pointSize)
                        let italicFont = UIFont(name: "Helvetica-LightOblique", size: tvMessage.font!.pointSize)
                        
                        //  TODO: - Placeholder Attributed String
                        let message = NSAttributedString(string: " " + "Personal Message", attributes: [ NSFontAttributeName: lightFont!,   NSForegroundColorAttributeName: textColor])
                        iconString.append(message)
                        // TODO: - Italic Placeholder Part
                        let option = NSAttributedString(string: " " + "Optional", attributes: [ NSFontAttributeName: italicFont!, NSForegroundColorAttributeName: textColor])
                        iconString.append(option)
                        
                        tvMessage.attributedPlaceHolder = iconString
                        
                        tvMessage.layoutSubviews()
                        

                        【讨论】:

                          【解决方案27】:

                          我刚刚发现,从iOS 10 开始,您现在实际上可以将UITextView 作为UITextField 转换为方法,并在方法内设置占位符。刚刚尝试过,它无需继承UITextView 就可以工作。

                          这是一个对我有用的例子:

                          -(void)customizeTextField:(UITextField *)textField placeholder:(NSString *)pText withColor:(UIColor *)pTextColor{
                          
                                  textField.attributedPlaceholder = [[NSAttributedString alloc]
                                                                    initWithString:pText
                                                                    attributes:@{NSForegroundColorAttributeName:pTextColor}];
                              }
                          

                          要将它用于UITextView,您只需使用这样的强制转换将其传递给方法:

                          [self customizeTextField:(UITextField*)_myTextView placeholder:@"Placeholder" withColor:[UIColor blackColor]];
                          

                          注意:经过测试,我发现该解决方案在 iOS9.x 上也可以正常工作,但在 iOS8.x 上会导致崩溃

                          【讨论】:

                            【解决方案28】:

                            首先在 .h 文件中取一个标签。

                            我来了

                            UILabel * lbl;
                            

                            然后在.m下viewDidLoad声明一下

                            lbl = [[UILabel alloc] initWithFrame:CGRectMake(8.0, 0.0,250, 34.0)];
                            
                            lbl.font=[UIFont systemFontOfSize:14.0];
                            
                            [lbl setText:@"Write a message..."];
                            
                            [lbl setBackgroundColor:[UIColor clearColor]];
                            
                            [lbl setTextColor:[UIColor lightGrayColor]];
                            
                            [textview addSubview:lbl];
                            

                            textview 是我的 TextView。

                            现在声明

                            -(void)textViewDidChange:(UITextView *)textView {
                            
                             if (![textView hasText]){
                            
                                lbl.hidden = NO;
                            
                             }
                             else{
                                lbl.hidden = YES;
                             }
                            
                            }
                            

                            您的 Textview 占位符已准备就绪!

                            【讨论】:

                              【解决方案29】:

                              在.h类中

                              @interface RateCommentViewController : UIViewController<UITextViewDelegate>{IBoutlet UITextview *commentTxtView;}
                              

                              在.m类中

                              - (void)viewDidLoad{      
                                  commentTxtView.text = @"Comment";
                                  commentTxtView.textColor = [UIColor lightGrayColor];
                                  commentTxtView.delegate = self;
                              }
                              
                              - (BOOL) textViewShouldBeginEditing:(UITextView *)textView
                              {
                                  commentTxtView.text = @"";
                                  commentTxtView.textColor = [UIColor blackColor];
                                  return YES;
                              }
                              
                              -(void) textViewDidChange:(UITextView *)textView
                              {
                                  if(commentTxtView.text.length == 0){
                                      commentTxtView.textColor = [UIColor lightGrayColor];
                                      commentTxtView.text = @"Comment";
                                      [commentTxtView resignFirstResponder];
                                  }
                              }
                              

                              【讨论】:

                                【解决方案30】:

                                我对 bcd 的解决方案做了一些小的修改,以允许从 Xib 文件进行初始化、文本换行并保持背景颜色。希望它可以为其他人省去麻烦。

                                UIPlaceHolderTextView.h:

                                #import <Foundation/Foundation.h>
                                IB_DESIGNABLE
                                @interface UIPlaceHolderTextView : UITextView
                                
                                @property (nonatomic, retain) IBInspectable NSString *placeholder;
                                @property (nonatomic, retain) IBInspectable UIColor *placeholderColor;
                                
                                -(void)textChanged:(NSNotification*)notification;
                                
                                @end
                                

                                UIPlaceHolderTextView.m:

                                #import "UIPlaceHolderTextView.h"
                                
                                @interface UIPlaceHolderTextView ()
                                
                                @property (nonatomic, retain) UILabel *placeHolderLabel;
                                
                                @end
                                
                                @implementation UIPlaceHolderTextView
                                
                                CGFloat const UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION = 0.25;
                                
                                - (void)dealloc
                                {
                                    [[NSNotificationCenter defaultCenter] removeObserver:self];
                                #if __has_feature(objc_arc)
                                #else
                                    [_placeHolderLabel release]; _placeHolderLabel = nil;
                                    [_placeholderColor release]; _placeholderColor = nil;
                                    [_placeholder release]; _placeholder = nil;
                                    [super dealloc];
                                #endif
                                }
                                
                                - (void)awakeFromNib
                                {
                                    [super awakeFromNib];
                                
                                    // Use Interface Builder User Defined Runtime Attributes to set
                                    // placeholder and placeholderColor in Interface Builder.
                                    if (!self.placeholder) {
                                        [self setPlaceholder:@""];
                                    }
                                
                                    if (!self.placeholderColor) {
                                        [self setPlaceholderColor:[UIColor lightGrayColor]];
                                    }
                                
                                    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
                                }
                                
                                - (id)initWithFrame:(CGRect)frame
                                {
                                    if( (self = [super initWithFrame:frame]) )
                                    {
                                        [self setPlaceholder:@""];
                                        [self setPlaceholderColor:[UIColor lightGrayColor]];
                                        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
                                    }
                                    return self;
                                }
                                
                                - (void)textChanged:(NSNotification *)notification
                                {
                                    if([[self placeholder] length] == 0)
                                    {
                                        return;
                                    }
                                
                                    [UIView animateWithDuration:UI_PLACEHOLDER_TEXT_CHANGED_ANIMATION_DURATION animations:^{
                                    if([[self text] length] == 0)
                                    {
                                        [[self viewWithTag:999] setAlpha:1];
                                    }
                                    else
                                    {
                                        [[self viewWithTag:999] setAlpha:0];
                                    }
                                    }];
                                }
                                
                                - (void)setText:(NSString *)text {
                                    [super setText:text];
                                    [self textChanged:nil];
                                }
                                
                                - (void)drawRect:(CGRect)rect
                                {
                                    if( [[self placeholder] length] > 0 )
                                    {
                                        if (_placeHolderLabel == nil )
                                        {
                                            _placeHolderLabel = [[UILabel alloc] initWithFrame:CGRectMake(8,8,self.bounds.size.width - 16,0)];
                                            _placeHolderLabel.lineBreakMode = NSLineBreakByWordWrapping;
                                            _placeHolderLabel.numberOfLines = 0;
                                            _placeHolderLabel.font = self.font;
                                            _placeHolderLabel.backgroundColor = [UIColor clearColor];
                                            _placeHolderLabel.textColor = self.placeholderColor;
                                            _placeHolderLabel.alpha = 0;
                                            _placeHolderLabel.tag = 999;
                                            [self addSubview:_placeHolderLabel];
                                        }
                                
                                        _placeHolderLabel.text = self.placeholder;
                                        [_placeHolderLabel sizeToFit];
                                        [self sendSubviewToBack:_placeHolderLabel];
                                    }
                                
                                    if( [[self text] length] == 0 && [[self placeholder] length] > 0 )
                                    {
                                        [[self viewWithTag:999] setAlpha:1];
                                    }
                                
                                    [super drawRect:rect];
                                }
                                
                                @end
                                

                                【讨论】:

                                • 在某些情况下(尤其是 iOS 5 兼容性)需要覆盖粘贴: - (void)paste:(id)sender { [super paste:sender]; [self textChanged:nil]; }
                                • 好东西!提醒有关 NSString(或具有 NSMutableXXX 等效项的任何类)的最佳实践,属性应该是“复制”而不是“保留”。
                                • 如何实例化这段代码?当我开始输入时,我没有看到任何占位符文本,也没有清除任何内容。
                                • 这是一个非常非常糟糕的实现。这是一个非常干净的版本,它还可以监视听写的变化:github.com/cbowns/MPTextView
                                • 不要修改drawRect中的视图层次结构。
                                猜你喜欢
                                • 2011-07-17
                                • 2016-06-21
                                • 1970-01-01
                                • 2023-03-27
                                • 2015-02-23
                                • 2014-10-11
                                • 2011-10-25
                                • 2012-08-31
                                相关资源
                                最近更新 更多