文本视图占位符Swift

Ste*_*enR 297 placeholder uitextview ios swift

我正在创建一个使用文本视图的应用程序.现在我希望文本视图的占位符类似于您可以为文本字段设置的占位符.你会如何使用swift实现这一目标.

有谁知道如何做到这一点?

Lyn*_*ott 591

针对Swift 4进行了更新

UITextView本身并不具有占位符属性,因此您必须使用UITextViewDelegate方法以编程方式创建和操作.我建议使用下面的解决方案#1或#2,具体取决于所需的行为.

注意:对于任一解决方案,请添加UITextViewDelegate到类并设置textView.delegate = self为使用文本视图的委托方法.


解决方案#1 - 如果您希望占位符在用户选择文本视图后立即消失:

首先设置UITextView包含占位符文本并将其设置为浅灰色以模仿UITextField占位符文本的外观.要么viewDidLoad在文本视图的创建中,要么在文本视图的创建中执行此操作.

textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
Run Code Online (Sandbox Code Playgroud)

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

func textViewDidBeginEditing(_ textView: UITextView) {
    if textView.textColor == UIColor.lightGray {
        textView.text = nil
        textView.textColor = UIColor.black
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

解决方案#2 - 如果您希望占位符在文本视图为空时显示,即使选择了文本视图:

首先在以下位置设置占位符viewDidLoad:

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

textView.becomeFirstResponder()

textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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

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

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

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

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

    // ...otherwise return false since the updates have already
    // been made
    return false
}
Run Code Online (Sandbox Code Playgroud)

并且还实现textViewDidChangeSelection了防止用户在占位符可见时更改光标的位置.(注意:textViewDidChangeSelection在视图加载之前调用,因此只有在窗口可见时才检查文本视图的颜色):

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

  • (iOS)开发者竟然为了如此简单的事情付出如此大的努力,这绝对令人震惊。这是很久以前就应该成为 TextView 控件一部分的基本功能,我不明白为什么 Apple 还没有解决这个问题。 (9认同)
  • 嗨,请确保您将视图控制器设置为textView**的委托.您可以通过从textView到viewController创建一个插座来实现此目的.然后使用`yourTextField.delegate = self`.如果不这样做,`textViewDidBeginEditing`和`textViewDidEndEditing`函数将不起作用. (5认同)
  • 我找到了解决方案,我将附上修改后的代码@iPeter.当前文本必须在NSString formant中:`let currentText = textView.text as NSString?`.将`let updatedText =`行转换为`let updatedText = currentText?.replacingCharacters(in:range,with:text)`.最后,将`if updatedText.isEmpty`行转换为`if(updatedText?.isEmpty)!{`.这应该够了吧! (4认同)
  • @LyndseyScott在`func textViewDidChangeSelection(_ textView:UITextView)中设置`textView.selectedTextRange`会导致无限循环... (3认同)
  • 代码没有编译,我有一个错误,因为`无法将类型'NSRange'(又名'_NSRange')的值转换为预期的参数类型'Range <String.Index>'(又名'Range <String.CharacterView.Index> ")`. (2认同)
  • 并以“线程1:EXC_BAD_ACCESS(code = 2,...)”崩溃。 (2认同)

cle*_*ght 214


浮动占位符


通过跟踪文本视图字符数的更改,将占位符标签放置在文本视图上方,设置其字体,颜色和管理占位符可见性是简单,安全和可靠的.

斯威夫特3:

class NotesViewController : UIViewController, UITextViewDelegate {

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

    override func viewDidLoad() {
        super.viewDidLoad()

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

    func textViewDidChange(_ textView: UITextView) {
        placeholderLabel.isHidden = !textView.text.isEmpty
    }
}
Run Code Online (Sandbox Code Playgroud)

斯威夫特2:一样的,除了:italicSystemFontOfSize(textView.font.pointSize),UIColor.lightGrayColor


  • 不可否认,这种方法是我发现的最简单且最容易出错的方法.极好. (16认同)
  • 这是一个很好的方法,但如果占位符文本很长,标签文本可能会超出范围. (6认同)
  • 易于使用,并与现有行为很好地集成.占位符消息不应该如此冗长,但不会将行设置为0来处理该问题? (5认同)
  • 我通常更喜欢这种方法,但如果文本居中对齐则会出现问题,因为光标将位于占位符顶部而不是左侧. (2认同)

t4n*_*hpt 29

强烈建议使用KMPlaceholderTextView库.使用非常简单.


Jua*_*ero 22

迅速:

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

@IBOutlet weak var yourTextView: UITextView!
Run Code Online (Sandbox Code Playgroud)

请添加委托(UITextViewDelegate):

class ViewController: UIViewController, UITextViewDelegate {
Run Code Online (Sandbox Code Playgroud)

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

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

    yourTextView.delegate = self
    yourTextView.text = "Placeholder text goes right here..."
    yourTextView.textColor = UIColor.lightGray
Run Code Online (Sandbox Code Playgroud)

现在让我介绍一下神奇的部分,添加这个功能:

func textViewDidBeginEditing(_ textView: UITextView) {

    if yourTextView.textColor == UIColor.lightGray {
        yourTextView.text = ""
        yourTextView.textColor = UIColor.black
    }
}
Run Code Online (Sandbox Code Playgroud)

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

现在添加此功能:

func textViewDidEndEditing(_ textView: UITextView) {

    if yourTextView.text == "" {

        yourTextView.text = "Placeholder text ..."
        yourTextView.textColor = UIColor.lightGray
    }
}
Run Code Online (Sandbox Code Playgroud)

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


San*_*ill 15

使用此扩展这是在UITextView中设置占位符的最佳方法.但请确保您已将代理附加到TextView.您可以像这样设置Place holder: -

yourTextView.placeholder = "Placeholder" 

extension UITextView :UITextViewDelegate
{

    /// Resize the placeholder when the UITextView bounds change
    override open var bounds: CGRect {
        didSet {
            self.resizePlaceholder()
        }
    }

    /// The UITextView placeholder text
    public var placeholder: String? {
        get {
            var placeholderText: String?

            if let placeholderLabel = self.viewWithTag(100) as? UILabel {
                placeholderText = placeholderLabel.text
            }

            return placeholderText
        }
        set {
            if let placeholderLabel = self.viewWithTag(100) as! UILabel? {
                placeholderLabel.text = newValue
                placeholderLabel.sizeToFit()
            } else {
                self.addPlaceholder(newValue!)
            }
        }
    }

    /// When the UITextView did change, show or hide the label based on if the UITextView is empty or not
    ///
    /// - Parameter textView: The UITextView that got updated
    public func textViewDidChange(_ textView: UITextView) {
        if let placeholderLabel = self.viewWithTag(100) as? UILabel {
            placeholderLabel.isHidden = self.text.characters.count > 0
        }
    }

    /// Resize the placeholder UILabel to make sure it's in the same position as the UITextView text
    private func resizePlaceholder() {
        if let placeholderLabel = self.viewWithTag(100) as! UILabel? {
            let labelX = self.textContainer.lineFragmentPadding
            let labelY = self.textContainerInset.top - 2
            let labelWidth = self.frame.width - (labelX * 2)
            let labelHeight = placeholderLabel.frame.height

            placeholderLabel.frame = CGRect(x: labelX, y: labelY, width: labelWidth, height: labelHeight)
        }
    }

    /// Adds a placeholder UILabel to this UITextView
    private func addPlaceholder(_ placeholderText: String) {
        let placeholderLabel = UILabel()

        placeholderLabel.text = placeholderText
        placeholderLabel.sizeToFit()

        placeholderLabel.font = self.font
        placeholderLabel.textColor = UIColor.lightGray
        placeholderLabel.tag = 100

        placeholderLabel.isHidden = self.text.characters.count > 0

        self.addSubview(placeholderLabel)
        self.resizePlaceholder()
        self.delegate = self
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 像魅力一样工作!非常感谢你 !:) (2认同)

yes*_*joe 14

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

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

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

这是我用来的代码.请注意,descriptionField是用户键入的字段,descriptionPlaceholder是后台的字段.

func textViewDidChange(descriptionField: UITextView) {
    if descriptionField.text.isEmpty == false {
        descriptionPlaceholder.text = ""
    } else {
        descriptionPlaceholder.text = descriptionPlaceholderText
    }
}
Run Code Online (Sandbox Code Playgroud)


Tru*_*iya 11

迅速:

添加您的TextView @IBOutlet

@IBOutlet weak var txtViewMessage: UITextView!
Run Code Online (Sandbox Code Playgroud)

在该viewWillAppear方法中,添加以下内容:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    txtViewMessage.delegate = self    // Give TextViewMessage delegate Method
    
    txtViewMessage.text = "Place Holder Name"
    txtViewMessage.textColor = UIColor.lightGray
}
Run Code Online (Sandbox Code Playgroud)

请添加Delegate使用扩展(UITextViewDelegate):

// MARK: - UITextViewDelegate
extension ViewController: UITextViewDelegate {

    func textViewDidBeginEditing(_ textView: UITextView) {

        if !txtViewMessage.text!.isEmpty && txtViewMessage.text! == "Place Holder Name" {
            txtViewMessage.text = ""
            txtViewMessage.textColor = UIColor.black
        }
    }

    func textViewDidEndEditing(_ textView: UITextView) {
    
        if txtViewMessage.text.isEmpty {
            txtViewMessage.text = "Place Holder Name"
            txtViewMessage.textColor = UIColor.lightGray
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


yes*_*eon 9

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

但是,使用NSTextStorageDelegatetextStorage(_:didProcessEditing:range:changeInLength:)方法,即使文本已通过编程方式进行了更改,也会通知您。像这样分配它:

textView.textStorage.delegate = self
Run Code Online (Sandbox Code Playgroud)

(在中UITextViewnil默认情况下,此委托属性是默认值,因此它不会影响任何默认行为。)

与结合起来UILabel@clearlight表明,人们可以很容易地包裹整个技术UITextViewplaceholder实现为一个扩展。

extension UITextView {

    private class PlaceholderLabel: UILabel { }

    private var placeholderLabel: PlaceholderLabel {
        if let label = subviews.compactMap( { $0 as? PlaceholderLabel }).first {
            return label
        } else {
            let label = PlaceholderLabel(frame: .zero)
            label.font = font
            addSubview(label)
            return label
        }
    }

    @IBInspectable
    var placeholder: String {
        get {
            return subviews.compactMap( { $0 as? PlaceholderLabel }).first?.text ?? ""
        }
        set {
            let placeholderLabel = self.placeholderLabel
            placeholderLabel.text = newValue
            placeholderLabel.numberOfLines = 0
            let width = frame.width - textContainer.lineFragmentPadding * 2
            let size = placeholderLabel.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
            placeholderLabel.frame.size.height = size.height
            placeholderLabel.frame.size.width = width
            placeholderLabel.frame.origin = CGPoint(x: textContainer.lineFragmentPadding, y: textContainerInset.top)

            textStorage.delegate = self
        }
    }

}

extension UITextView: NSTextStorageDelegate {

    public func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int) {
        if editedMask.contains(.editedCharacters) {
            placeholderLabel.isHidden = !text.isEmpty
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

请注意,使用称为的私有(嵌套)类PlaceholderLabel。它根本没有实现,但是它为我们提供了一种识别占位符标签的方法,它比使用该tag属性要“花哨得多” 。

使用这种方法,您仍然可以将的委托分配UITextView给其他人。

您甚至不必更改文本视图的类。只需添加扩展,就可以为UITextView项目中的每个对象(甚至在Interface Builder中)分配一个占位符字符串。

placeholderColor为了清楚起见,我省略了属性的实现,但是可以再执行几行,并使用与相似的计算变量placeholder


小智 8

我试图从clearlight回答中简化代码。

extension UITextView{

    func setPlaceholder() {

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

        self.addSubview(placeholderLabel)
    }

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

}
Run Code Online (Sandbox Code Playgroud)

用法

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

func textViewDidChange(_ textView: UITextView) {
    textView.checkPlaceholder()
}
Run Code Online (Sandbox Code Playgroud)


Pei*_*Pei 7

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

@IBDesignable class UIPlaceholderTextView: UITextView {
    
    var placeholderLabel: UILabel?
    
    override init(frame: CGRect, textContainer: NSTextContainer?) {
        super.init(frame: frame, textContainer: textContainer)
        sharedInit()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        sharedInit()
    }
    
    override func prepareForInterfaceBuilder() {
        sharedInit()
    }
    
    func sharedInit() {
        refreshPlaceholder()
        NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil)
    }

    @IBInspectable var placeholder: String? {
        didSet {
            refreshPlaceholder()
        }
    }

    @IBInspectable var placeholderColor: UIColor? = .darkGray {
        didSet {
            refreshPlaceholder()
        }
    }
    
    @IBInspectable var placeholderFontSize: CGFloat = 14 {
        didSet {
            refreshPlaceholder()
        }
    }
    
    func refreshPlaceholder() {
        if placeholderLabel == nil {
            placeholderLabel = UILabel()
            let contentView = self.subviews.first ?? self
            
            contentView.addSubview(placeholderLabel!)
            placeholderLabel?.translatesAutoresizingMaskIntoConstraints = false
            
            placeholderLabel?.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: textContainerInset.left + 4).isActive = true
            placeholderLabel?.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: textContainerInset.right + 4).isActive = true
            placeholderLabel?.topAnchor.constraint(equalTo: contentView.topAnchor, constant: textContainerInset.top).isActive = true
            placeholderLabel?.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: textContainerInset.bottom).isActive = true
        }
        placeholderLabel?.text = placeholder
        placeholderLabel?.textColor = placeholderColor
        placeholderLabel?.font = UIFont.systemFont(ofSize: placeholderFontSize)
    }
    
    @objc func textChanged() {
        if self.placeholder?.isEmpty ?? true {
            return
        }
        
        UIView.animate(withDuration: 0.25) {
            if self.text.isEmpty {
                self.placeholderLabel?.alpha = 1.0
            } else {
                self.placeholderLabel?.alpha = 0.0
            }
        }
    }
    
    override var text: String! {
        didSet {
            textChanged()
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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


The*_*eil 7

基于已经在此处提出的一些重要建议,我能够将以下轻量级,与Interface-Builder兼容的子类放在一起UITextView

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

欢迎提出任何改进建议,尤其是如果有任何方法可以通过编程方式拉动iOS的占位符颜色,而不是对其进行硬编码的话。

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 { super.text = newValue }
    }
    @IBInspectable var placeholderText: String = ""
    @IBInspectable var placeholderTextColor: UIColor = UIColor(red: 0.78, green: 0.78, blue: 0.80, alpha: 1.0) // Standard iOS placeholder color (#C7C7CD). See /sf/ask/2174042251/
    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 func becomeFirstResponder() -> Bool {
        // If the current text is the placeholder, remove it
        if showingPlaceholder {
            text = nil
            textColor = nil // Put the text back to the default, unmodified color
            showingPlaceholder = false
        }
        return super.becomeFirstResponder()
    }

    override func resignFirstResponder() -> Bool {
        // If there's no text, put the placeholder back
        if text.isEmpty {
            showPlaceholderText()
        }
        return super.resignFirstResponder()
    }

    private func showPlaceholderText() {
        showingPlaceholder = true
        textColor = placeholderTextColor
        text = placeholderText
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 6

视图加载中的SET值

    txtVw!.autocorrectionType = UITextAutocorrectionType.No
    txtVw!.text = "Write your Placeholder"
    txtVw!.textColor = UIColor.lightGrayColor()



func textViewDidBeginEditing(textView: UITextView) {
    if (txtVw?.text == "Write your Placeholder")

    {
        txtVw!.text = nil
        txtVw!.textColor = UIColor.blackColor()
    }
}

func textViewDidEndEditing(textView: UITextView) {
    if txtVw!.text.isEmpty
    {
        txtVw!.text = "Write your Placeholder"
        txtVw!.textColor = UIColor.lightGrayColor()
    }
    textView.resignFirstResponder()
}
Run Code Online (Sandbox Code Playgroud)


小智 6

如果我们在项目中使用 pod,我们可以很容易地实现 textview PlaceHolder,IQKeyboardManagerSwift只需遵循 4 个步骤

  1. 我们必须将班级分配IQTextView给我们的TextView班级。
  2. 我们必须IQKeyboardManagerSwift在我们的控制器页面中导入
  3. textView最后但并非最不重要的一点是,如果您愿意,请在控制器页面上创建 的出口:)
  4. textView通过情节提要检查一些占位符文本


gbk*_*gbk 5

还有一个解决方案(Swift 3):

import UIKit

protocol PlaceholderTextViewDelegate {
    func placeholderTextViewDidChangeText(_ text:String)
    func placeholderTextViewDidEndEditing(_ text:String)
}

final class PlaceholderTextView: UITextView {

    var notifier:PlaceholderTextViewDelegate?

    var placeholder: String? {
        didSet {
            placeholderLabel?.text = placeholder
        }
    }
    var placeholderColor = UIColor.lightGray
    var placeholderFont = UIFont.appMainFontForSize(14.0) {
        didSet {
            placeholderLabel?.font = placeholderFont
        }
    }

    fileprivate var placeholderLabel: UILabel?

    // MARK: - LifeCycle

    init() {
        super.init(frame: CGRect.zero, textContainer: nil)
        awakeFromNib()
    }

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

    override func awakeFromNib() {
        super.awakeFromNib()

        self.delegate = self
        NotificationCenter.default.addObserver(self, selector: #selector(PlaceholderTextView.textDidChangeHandler(notification:)), name: .UITextViewTextDidChange, object: nil)

        placeholderLabel = UILabel()
        placeholderLabel?.textColor = placeholderColor
        placeholderLabel?.text = placeholder
        placeholderLabel?.textAlignment = .left
        placeholderLabel?.numberOfLines = 0
    }

    override func layoutSubviews() {
        super.layoutSubviews()

        placeholderLabel?.font = placeholderFont

        var height:CGFloat = placeholderFont.lineHeight
        if let data = placeholderLabel?.text {

            let expectedDefaultWidth:CGFloat = bounds.size.width
            let fontSize:CGFloat = placeholderFont.pointSize

            let textView = UITextView()
            textView.text = data
            textView.font = UIFont.appMainFontForSize(fontSize)
            let sizeForTextView = textView.sizeThatFits(CGSize(width: expectedDefaultWidth,
                                                               height: CGFloat.greatestFiniteMagnitude))
            let expectedTextViewHeight = sizeForTextView.height

            if expectedTextViewHeight > height {
                height = expectedTextViewHeight
            }
        }

        placeholderLabel?.frame = CGRect(x: 5, y: 0, width: bounds.size.width - 16, height: height)

        if text.isEmpty {
            addSubview(placeholderLabel!)
            bringSubview(toFront: placeholderLabel!)
        } else {
            placeholderLabel?.removeFromSuperview()
        }
    }

    func textDidChangeHandler(notification: Notification) {
        layoutSubviews()
    }

}

extension PlaceholderTextView : UITextViewDelegate {
    // MARK: - UITextViewDelegate
    func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
        if(text == "\n") {
            textView.resignFirstResponder()
            return false
        }
        return true
    }

    func textViewDidChange(_ textView: UITextView) {
        notifier?.placeholderTextViewDidChangeText(textView.text)
    }

    func textViewDidEndEditing(_ textView: UITextView) {
        notifier?.placeholderTextViewDidEndEditing(textView.text)
    }
}
Run Code Online (Sandbox Code Playgroud)

结果

在此输入图像描述


Sep*_* Y. 5

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

@IBDesignable
class PlaceHolderTextView: UITextView {

    @IBInspectable var placeholder: String = "" {
         didSet{
             updatePlaceHolder()
        }
    }

    @IBInspectable var placeholderColor: UIColor = UIColor.gray {
        didSet {
            updatePlaceHolder()
        }
    }

    private var originalTextColor = UIColor.darkText
    private var originalText: String = ""

    private func updatePlaceHolder() {

        if self.text == "" || self.text == placeholder  {

            self.text = placeholder
            self.textColor = placeholderColor
            if let color = self.textColor {

                self.originalTextColor = color
            }
            self.originalText = ""
        } else {
            self.textColor = self.originalTextColor
            self.originalText = self.text
        }

    }

    override func becomeFirstResponder() -> Bool {
        let result = super.becomeFirstResponder()
        self.text = self.originalText
        self.textColor = self.originalTextColor
        return result
    }
    override func resignFirstResponder() -> Bool {
        let result = super.resignFirstResponder()
        updatePlaceHolder()

        return result
    }
}
Run Code Online (Sandbox Code Playgroud)