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


当前回答

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

很简单:

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

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

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

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

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

其他回答

不,没有任何占位符可用的textview。你必须把标签上面,当用户进入textview,然后隐藏它或设置默认值时,用户输入删除所有值。

我喜欢@nerdist的解决方案。在此基础上,我创建了UITextView的扩展:

import Foundation
import UIKit

extension UITextView
{
  private func add(_ placeholder: UILabel) {
    for view in self.subviews {
        if let lbl = view as? UILabel  {
            if lbl.text == placeholder.text {
                lbl.removeFromSuperview()
            }
        }
    }
    self.addSubview(placeholder)
  }

  func addPlaceholder(_ placeholder: UILabel?) {
    if let ph = placeholder {
      ph.numberOfLines = 0  // support for multiple lines
      ph.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
      ph.sizeToFit()
      self.add(ph)
      ph.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
      ph.textColor = UIColor(white: 0, alpha: 0.3)
      updateVisibility(ph)
    }
  }

  func updateVisibility(_ placeHolder: UILabel?) {
    if let ph = placeHolder {
      ph.isHidden = !self.text.isEmpty
    }
  }
}

例如,在ViewController类中,我是这样使用它的:

class MyViewController: UIViewController, UITextViewDelegate {
  private var notePlaceholder: UILabel!
  @IBOutlet weak var txtNote: UITextView!
  ...
  // UIViewController
  override func viewDidLoad() {
    notePlaceholder = UILabel()
    notePlaceholder.text = "title\nsubtitle\nmore..."
    txtNote.addPlaceholder(notePlaceholder)
    ...
  }

  // UITextViewDelegate
  func textViewDidChange(_ textView: UITextView) {
    txtNote.updateVisbility(notePlaceholder)
    ...
  }

UITextview的占位符!

更新:

如果你在代码中改变textview的文本,记得调用updateVisibitly方法来隐藏占位符:

txtNote.text = "something in code"
txtNote.updateVisibility(self.notePlaceholder) // hide placeholder if text is not empty.

为了防止占位符被添加多次,在扩展中添加了一个私有add()函数。

斯威夫特

这是一个Swiftui TextView使用UIVIewRepresentable,具有占位符功能和边界颜色

struct TextView: UIViewRepresentable {

@Binding var text: String
var placeholderText: String
var textStyle: UIFont.TextStyle

func makeUIView(context: Context) -> UITextView {
    let textView = UITextView()

    textView.font = UIFont.preferredFont(forTextStyle: textStyle)
    textView.autocapitalizationType = .sentences
    textView.isSelectable = true
    textView.isUserInteractionEnabled = true
    textView.delegate = context.coordinator
    textView.layer.borderWidth = 0.6
    textView.layer.borderColor = UIColor.lightGray.cgColor
    textView.layer.cornerRadius = 10
    textView.text = placeholderText
    textView.textColor = UIColor.lightGray
    return textView
}

func updateUIView(_ uiView: UITextView, context: Context) {
    uiView.text = text
    uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
}

func makeCoordinator() -> Coordinator {
    Coordinator(self)
}
 
class Coordinator: NSObject, UITextViewDelegate {
    var parent: TextView
 
    init(_ parent: TextView) {
        self.parent = parent
    }
 
    func textViewDidChange(_ textView: UITextView) {
        self.parent.text = textView.text
    }
    
    func textViewDidBeginEditing(_ textView: UITextView) {
        if textView.textColor == UIColor.lightGray {
            textView.text = nil
            textView.textColor = UIColor.black
        }
    }
    
    func textViewDidEndEditing(_ textView: UITextView) {
        if textView.text.isEmpty {
            textView.text = self.parent.placeholderText
            textView.textColor = UIColor.lightGray
        }
    }
}

}

然后在你的视图中你可以这样使用它

TextView(text: self.$viewModel.addPostCommentText, placeholderText: "Share your story about this cash", textStyle: .body)
                .padding()
                .frame(height: 150)

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

import UIKit

public protocol PlaceholderTextViewDelegate: class {
  func placeholderTextViewTextChanged(_ textView: PlaceholderTextView, text: String)
}

public class PlaceholderTextView: UIView {

  public weak var delegate: PlaceholderTextViewDelegate?
  private var heightConstraint: NSLayoutConstraint?

  public override init(frame: CGRect) {
    self.allowsNewLines = true

    super.init(frame: frame)

    self.heightConstraint = self.heightAnchor.constraint(equalToConstant: 0)
    self.heightConstraint?.isActive = true

    self.addSubview(self.placeholderTextView)
    self.addSubview(self.textView)

    self.pinToCorners(self.placeholderTextView)
    self.pinToCorners(self.textView)

    self.updateHeight()
  }

  public override func didMoveToSuperview() {
    super.didMoveToSuperview()

    self.updateHeight()
  }

  private func pinToCorners(_ view: UIView) {
    NSLayoutConstraint.activate([
      view.leadingAnchor.constraint(equalTo: self.leadingAnchor),
      view.trailingAnchor.constraint(equalTo: self.trailingAnchor),
      view.topAnchor.constraint(equalTo: self.topAnchor),
      view.bottomAnchor.constraint(equalTo: self.bottomAnchor)
    ])
  }

  // Accessors
  public var text: String? {
    didSet {
      self.textView.text = text
      self.textViewDidChange(self.textView)
      self.updateHeight()
    }
  }

  public var textColor: UIColor? {
    didSet {
      self.textView.textColor = textColor
      self.updateHeight()
    }
  }

  public var font: UIFont? {
    didSet {
      self.textView.font = font
      self.placeholderTextView.font = font
      self.updateHeight()
    }
  }

  public override var tintColor: UIColor? {
    didSet {
      self.textView.tintColor = tintColor
      self.placeholderTextView.tintColor = tintColor
    }
  }

  public var placeholderText: String? {
    didSet {
      self.placeholderTextView.text = placeholderText
      self.updateHeight()
    }
  }

  public var placeholderTextColor: UIColor? {
    didSet {
      self.placeholderTextView.textColor = placeholderTextColor
      self.updateHeight()
    }
  }

  public var allowsNewLines: Bool

  public required init?(coder _: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  private lazy var textView: UITextView = self.newTextView()
  private lazy var placeholderTextView: UITextView = self.newTextView()

  private func newTextView() -> UITextView {
    let textView = UITextView()
    textView.translatesAutoresizingMaskIntoConstraints = false
    textView.isScrollEnabled = false
    textView.delegate = self
    textView.backgroundColor = .clear
    return textView
  }

  private func updateHeight() {
    let maxSize = CGSize(width: self.frame.size.width, height: .greatestFiniteMagnitude)

    let textViewSize = self.textView.sizeThatFits(maxSize)
    let placeholderSize = self.placeholderTextView.sizeThatFits(maxSize)

    let maxHeight = ceil(CGFloat.maximum(textViewSize.height, placeholderSize.height))

    self.heightConstraint?.constant = maxHeight
  }
}

extension PlaceholderTextView: UITextViewDelegate {
  public func textViewDidChangeSelection(_: UITextView) {
    self.placeholderTextView.alpha = self.textView.text.isEmpty ? 1 : 0
    self.updateHeight()
  }

  public func textViewDidChange(_: UITextView) {
    self.delegate?.placeholderTextViewTextChanged(self, text: self.textView.text)
  }

  public func textView(_: UITextView, shouldChangeTextIn _: NSRange,
                       replacementText text: String) -> Bool {
    let containsNewLines = text.rangeOfCharacter(from: .newlines)?.isEmpty == .some(false)
    guard !containsNewLines || self.allowsNewLines else { return false }

    return true
  }
}

另一个解决方案是使用keyboardWillHide和keyboardWillShow通知,就像我做的那样。

首先,您需要分别处理viewWillAppear和viewWillAppear方法中的侦听和取消侦听通知(以处理内存泄漏)。

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    setupKeyboardNotificationListeners(enable: true)
}

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    setupKeyboardNotificationListeners(enable: false)
}

然后是处理监听/取消监听通知的方法:

private func setupKeyboardNotificationListeners(enable: Bool) {
        if enable {
            NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillShow), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
            NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
        } else {
            NotificationCenter.default.removeObserver(self)
        }
    }

然后在keyboardWillHide和keyboardWillShow的两个方法中,你处理文本的占位符和颜色变化。

@objc func keyboardWillShow(notification: NSNotification) {
    if self.textView.text == self.placeholder {
        self.textView.text = ""
        self.textView.textColor = .black
    }
}

@objc func keyboardWillHide(notification: NSNotification) {
    if self.textView.text.isEmpty {
        self.textView.text = self.placeholder
        self.textView.textColor = .lightGrey
    }
}

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