在UITextView内添加占位符文本在Swift?

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

348392 次浏览

Swift 4更新

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

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


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

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

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

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

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

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

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

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

首先在viewDidLoad中设置占位符:

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


textView.becomeFirstResponder()


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

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

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

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


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


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


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


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


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


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


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

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

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

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

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

其思想是,一旦用户开始在前景视图中输入内容,后台的占位符就会消失(如果用户删除了所有内容,则会重新出现)。因此,它的行为完全类似于单行文本字段的占位符。

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

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

浮动的占位符


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

斯威夫特5:

class NotesViewController : UIViewController {


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

override func viewDidLoad() {
super.viewDidLoad()
    

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


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

迅速:

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

@IBOutlet weak var yourTextView: UITextView!

请添加委托(UITextViewDelegate):

class ViewController: UIViewController, UITextViewDelegate {

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

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


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

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

func textViewDidBeginEditing(_ textView: UITextView) {


if yourTextView.textColor == UIColor.lightGray {
yourTextView.text = ""
yourTextView.textColor = UIColor.black
}
}
注意,这将在编辑开始时执行,在那里我们将检查条件来告诉状态,使用颜色属性。 我不建议将text设置为nil。在此之后,我们将文本颜色设置为所需颜色,在本例中为黑色。

现在也添加这个函数:

func textViewDidEndEditing(_ textView: UITextView) {


if yourTextView.text == "" {


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

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

我不知道为什么人们会把这个问题复杂化....这是相当直接和简单的。下面是UITextView的一个子类,它提供了所请求的功能。

- (void)customInit
{
self.contentMode = UIViewContentModeRedraw;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
}


- (void)textChanged:(NSNotification *)notification
{
if (notification.object == self) {
if(self.textStorage.length != 0 || !self.textStorage.length) {
[self setNeedsDisplay];
}
}
}




#pragma mark - Setters


- (void)setPlaceholderText:(NSString *)placeholderText withFont:(UIFont *)font
{
self.placeholderText = placeholderText;
self.placeholderTextFont = font;


}






- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
[[UIColor lightGrayColor] setFill];


if (self.textStorage.length != 0) {
return;
}


CGRect inset = CGRectInset(rect, 8, 8);//Default rect insets for textView
NSDictionary *attributes =  @{NSFontAttributeName: self.placeholderTextFont, NSForegroundColorAttributeName: [UIColor grayColor]};
[self.placeholderText drawInRect:inset withAttributes:attributes];
}`

在视图加载中设置值

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

没有这样的属性在ios添加占位符直接在TextView,而你可以添加一个标签和显示/隐藏在TextView的变化。SWIFT 2.0,确保实现textviewdelegate

func textViewDidChange(TextView: UITextView)
{


if  txtShortDescription.text == ""
{
self.lblShortDescription.hidden = false
}
else
{
self.lblShortDescription.hidden = true
}


}

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

func textViewShouldBeginEditing(textView: UITextView) -> Bool {
// Set cursor to the beginning if placeholder is set
if textView.textColor == UIColor.lightGrayColor() {
textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
}


return true
}


func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
// Remove placeholder
if textView.textColor == UIColor.lightGrayColor() && text.characters.count > 0 {
textView.text = ""
textView.textColor = UIColor.blackColor()
}


if text == "\n" {
textView.resignFirstResponder()
return false
}


return true
}


func textViewDidChange(textView: UITextView) {
// Set placeholder if text is empty
if textView.text.isEmpty {
textView.text = NSLocalizedString("Hint", comment: "hint")
textView.textColor = UIColor.lightGrayColor()
textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
}
}


func textViewDidChangeSelection(textView: UITextView) {
// Set cursor to the beginning if placeholder is set
let firstPosition = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)


// Do not change position recursively
if textView.textColor == UIColor.lightGrayColor() && textView.selectedTextRange != firstPosition {
textView.selectedTextRange = firstPosition
}
}

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

  import UIKit
@IBDesignable
class HintedTextView: UITextView {


@IBInspectable var hintText: String = "hintText" {
didSet{
hintLabel.text = hintText
}
}


private lazy var hintLabel: UILabel = {
let label = UILabel()
label.font = UIFont.systemFontOfSize(16)
label.textColor = UIColor.lightGrayColor()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()




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


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


override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
setupView()
}


private func setupView() {


translatesAutoresizingMaskIntoConstraints = false
delegate = self
font = UIFont.systemFontOfSize(16)


addSubview(hintLabel)


NSLayoutConstraint.activateConstraints([


hintLabel.leftAnchor.constraintEqualToAnchor(leftAnchor, constant: 4),
hintLabel.rightAnchor.constraintEqualToAnchor(rightAnchor, constant: 8),
hintLabel.topAnchor.constraintEqualToAnchor(topAnchor, constant: 4),
hintLabel.heightAnchor.constraintEqualToConstant(30)
])
}


override func layoutSubviews() {
super.layoutSubviews()
setupView()
}


}

我喜欢@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的占位符!

enter image description here

更新:

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

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

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

在swift2.2:

public class CustomTextView: UITextView {


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


private var placeholderLabelConstraints = [NSLayoutConstraint]()


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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

在swift3:

import UIKit

类CustomTextView: UITextView

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


private var placeholderLabelConstraints = [NSLayoutConstraint]()


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


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


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


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


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


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


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


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


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


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


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


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


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


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


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

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

我尝试从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
}


}

使用

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


func textViewDidChange(_ textView: UITextView) {
textView.checkPlaceholder()
}

还有一个解决方案(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)
}
}

结果

enter image description here

由于声誉问题,我不能发表评论。在@clearlight answer中增加一个委托需求。

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

是需要

因为textViewDidChange不是第一次调用的

斯威夫特3.1

这个扩展工作得很好为我:https://github.com/devxoul/UITextView-Placeholder

下面是一个代码片段:

通过pod安装:

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

将它导入到类中

import UITextView_Placeholder

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

textView.placeholder = "Put some detail"
< p >这… 下面是它的外观(第三个方框是UITextView) enter image description here < / p >

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

上面clearlight答案的协议版本,因为协议很棒。随便放哪都行。扣篮!

extension UITextViewPlaceholder where Self: UIViewController {


// Use this in ViewController's ViewDidLoad method.
func addPlaceholder(text: String, toTextView: UITextView, font: UIFont? = nil) {
placeholderLabel = UILabel()
placeholderLabel.text = text
placeholderLabel.font = font ?? UIFont.italicSystemFont(ofSize: (toTextView.font?.pointSize)!)
placeholderLabel.sizeToFit()
toTextView.addSubview(placeholderLabel)
placeholderLabel.frame.origin = CGPoint(x: 5, y: (toTextView.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !toTextView.text.isEmpty
}


// Use this function in the ViewController's textViewDidChange delegate method.
func textViewWithPlaceholderDidChange(_ textView: UITextView) {
placeholderLabel.isHidden = !textView.text.isEmpty
}
}

文本视图委托方法

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

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


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

func setPlaceholder(){
var placeholderLabel = UILabel()
placeholderLabel.text = "Describe your need..."
placeholderLabel.font = UIFont.init(name: "Lato-Regular", size: 15.0) ?? UIFont.boldSystemFont(ofSize: 14.0)
placeholderLabel.sizeToFit()
descriptionTextView.addSubview(placeholderLabel)
placeholderLabel.frame.origin = CGPoint(x: 5, y: (descriptionTextView.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !descriptionTextView.text.isEmpty
}






//Delegate Method.


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

这里有一些可以放入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
}
}

斯威夫特3.2

extension EditProfileVC:UITextViewDelegate{


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

首先,当用户开始编辑textViewDidBeginEditing调用,然后检查如果文本灰色的颜色意味着用户没有写任何东西,然后设置为textview nil,并将用户文本的颜色更改为黑色。

当用户端编辑textViewDidEndEditing是调用和检查如果用户没有写任何在textview然后文本设置为灰色与文本“占位符”

我不得不调度队列,让我的占位符文本重新出现,一旦编辑完成。

func textViewDidBeginEditing(_ textView: UITextView) {


if textView.text == "Description" {
textView.text = nil
}
}


func textViewDidEndEditing(_ textView: UITextView) {


if textView.text.isEmpty {
DispatchQueue.main.async {
textView.text = "Description"
}
}
}

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

@IBDesignable class UIPlaceholderTextView: UITextView {
    

var placeholderLabel: UILabel?
    

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

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

override func prepareForInterfaceBuilder() {
sharedInit()
}
    

func sharedInit() {
refreshPlaceholder()
NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil)
}


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


@IBInspectable var placeholderColor: UIColor? = .darkGray {
didSet {
refreshPlaceholder()
}
}
    

@IBInspectable var placeholderFontSize: CGFloat = 14 {
didSet {
refreshPlaceholder()
}
}
    

func refreshPlaceholder() {
if placeholderLabel == nil {
placeholderLabel = UILabel()
let contentView = self.subviews.first ?? self
            

contentView.addSubview(placeholderLabel!)
placeholderLabel?.translatesAutoresizingMaskIntoConstraints = false
            

placeholderLabel?.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: textContainerInset.left + 4).isActive = true
placeholderLabel?.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: textContainerInset.right + 4).isActive = true
placeholderLabel?.topAnchor.constraint(equalTo: contentView.topAnchor, constant: textContainerInset.top).isActive = true
placeholderLabel?.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: textContainerInset.bottom).isActive = true
}
placeholderLabel?.text = placeholder
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.font = UIFont.systemFont(ofSize: placeholderFontSize)
}
    

@objc func textChanged() {
if self.placeholder?.isEmpty ?? true {
return
}
        

UIView.animate(withDuration: 0.25) {
if self.text.isEmpty {
self.placeholderLabel?.alpha = 1.0
} else {
self.placeholderLabel?.alpha = 0.0
}
}
}
    

override var text: String! {
didSet {
textChanged()
}
}


}

我知道有很多类似的方法,但这个方法的好处是:

  • IB中设置占位符文本、字体大小和颜色。
  • 不再显示“滚动视图具有模糊的可滚动内容"在盈透。
  • 添加动画来显示/隐藏占位符。

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

然而,使用NSTextStorageDelegatetextStorage(_:didProcessEditing:range:changeInLength:)方法,你会收到任何文本更改的通知,即使它是通过编程方式完成的。就像这样分配它:

textView.textStorage.delegate = self

(在UITextView中,此委托属性默认为nil,因此它不会影响任何默认行为。)

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

extension UITextView {


private class PlaceholderLabel: UILabel { }


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


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


textStorage.delegate = self
}
}


}


extension UITextView: NSTextStorageDelegate {


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


}

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

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

你甚至不需要改变文本视图的类。只要添加扩展名,你就可以为项目中的每个UITextView分配一个占位符字符串,即使在接口生成器中也是如此。

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

迅速:

添加你的TextView @IBOutlet:

@IBOutlet weak var txtViewMessage: UITextView!

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

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


txtViewMessage.delegate = self    // Give TextViewMessage delegate Method
    

txtViewMessage.text = "Place Holder Name"
txtViewMessage.textColor = UIColor.lightGray
}

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

对我来说,一个简单而快速的解决方法是:

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

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

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

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


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

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

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

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

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


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

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

var placeholderLabel : UILabel!
textviewDescription.delegate = self
placeholderLabel = UILabel()
placeholderLabel.text = "Add a description"


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

这是我解决这个问题的方法(斯威夫特4):

我们的想法是做出最简单的解决方案,允许使用不同颜色的占位符,将大小调整为占位符大小,不会覆盖delegate,同时保持所有UITextView函数正常工作。

import UIKit


class PlaceholderTextView: UITextView {
var placeholderColor: UIColor = .lightGray
var defaultTextColor: UIColor = .black


private var isShowingPlaceholder = false {
didSet {
if isShowingPlaceholder {
text = placeholder
textColor = placeholderColor
} else {
textColor = defaultTextColor
}
}
}


var placeholder: String? {
didSet {
isShowingPlaceholder = !hasText
}
}


@objc private func textViewDidBeginEditing(notification: Notification) {
textColor = defaultTextColor
if isShowingPlaceholder { text = nil }
}


@objc private func textViewDidEndEditing(notification: Notification) {
isShowingPlaceholder = !hasText
}


// MARK: - Construction -
override init(frame: CGRect, textContainer: NSTextContainer?) {
super.init(frame: frame, textContainer: textContainer)
setup()
}


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


private func setup() {
NotificationCenter.default.addObserver(self, selector: #selector(textViewDidBeginEditing(notification:)), name: UITextView.textDidBeginEditingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(textViewDidEndEditing(notification:)), name: UITextView.textDidEndEditingNotification, object: nil)
}


// MARK: - Destruction -
deinit { NotificationCenter.default.removeObserver(self) }
}

我们的解决方案避免了混淆UITextView texttextColor属性,这在维护字符计数器时非常方便。

很简单:

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

2)对齐两个UITextViews.的上、左、右边缘

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

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

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

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

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

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

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

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

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

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

更新:

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

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

基于这里已经给出的一些很好的建议,我能够将以下轻量级的、与接口生成器兼容的UITextView子类组合在一起,它是:

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

任何改进建议都是受欢迎的,特别是如果有任何方法通过编程来获取iOS的占位符颜色,而不是硬编码它。

斯威夫特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 https://stackoverflow.com/questions/31057746/whats-the-default-color-for-placeholder-text-in-uitextfield
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
}
}

Swift 4、4.2和5

[![@IBOutlet var detailTextView: UITextView!


override func viewDidLoad() {
super.viewDidLoad()
detailTextView.delegate = self
}


extension ContactUsViewController : UITextViewDelegate {


public func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == "Write your message here..." {
detailTextView.text = ""
detailTextView.textColor = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.86)


}
textView.becomeFirstResponder()
}


public func textViewDidEndEditing(_ textView: UITextView) {


if textView.text == "" {
detailTextView.text = "Write your message here..."
detailTextView.textColor = UIColor.init(red: 0/255, green: 0/255, blue: 0/255, alpha: 0.30)
}
textView.resignFirstResponder()
}
[![}][1]][1]

迅速的回答

这是一个自定义类,用于动画占位符。

class CustomTextView: UITextView {
    

//   MARK: - public
    

public var placeHolderText: String? = "Enter Reason.."
    

public lazy var placeHolderLabel: UILabel! = {
let placeHolderLabel = UILabel(frame: .zero)
placeHolderLabel.numberOfLines = 0
placeHolderLabel.backgroundColor = .clear
placeHolderLabel.alpha = 0.5
return placeHolderLabel
}()
    

//   MARK: - Init
    

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

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

func setup() {
placeHolderLabel.frame = CGRect(x: 8, y: 8, width: self.bounds.size.width - 16, height: 15)
placeHolderLabel.sizeToFit()
}
    

//   MARK: - Cycle
    

override func awakeFromNib() {
super.awakeFromNib()
        

textContainerInset = UIEdgeInsets(top: 8, left: 5, bottom: 8, right: 8)
returnKeyType = .done
addSubview(placeHolderLabel)
placeHolderLabel.frame = CGRect(x: 8, y: 8, width: self.bounds.size.width - 16, height: 15)
placeHolderLabel.textColor = textColor
placeHolderLabel.font = font
placeHolderLabel.text = placeHolderText
bringSubviewToFront(placeHolderLabel)
}
    

override func layoutSubviews() {
super.layoutSubviews()
setup()
}
    

//   MARK: - Notifications
    

private func enableNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(textDidChangeNotification(_:)), name: UITextView.textDidChangeNotification , object: nil)
}
    

@objc func textDidChangeNotification(_ notify: Notification) {
guard self == notify.object as? UITextView else { return }
guard placeHolderText != nil else { return }
        

UIView.animate(withDuration: 0.25, animations: {
self.placeHolderLabel.alpha = (self.text.count == 0) ? 0.5 : 0
}, completion: nil)
}
    

}

我知道这是一个老问题,但想分享我认为是一个有用的方式扩展UITextView有placeholderText和placeholderColor字段。基本上你将UITextView转换为UITextField,然后设置attributedPlaceholder字段。PlaceholderText和placeholderColor是IBInspectable字段,所以它们的值可以在IB中设置,并与UITextField占位符功能完全相同。

UITextView + Extend.h

#import <UIKit/UIKit.h>


@interface UITextView (Extend)


@property (nonatomic) IBInspectable NSString *placeholderText;
@property (nonatomic) IBInspectable UIColor *placeholderColor;


@end

UITextView + Extend.m

#import "UITextView+Extend.h"
#import "objc/runtime.h"


@implementation UITextView (Extend)


- (void)setPlaceholderText:(NSString *)placeholderText
{
objc_setAssociatedObject(self, @selector(placeholderText), placeholderText, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self updatePlaceholderText];
}


- (NSString*)placeholderText
{
return objc_getAssociatedObject(self, @selector(placeholderText));
}


- (void)setPlaceholderColor:(UIColor *)placeholderColor
{
objc_setAssociatedObject(self, @selector(placeholderColor), placeholderColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self updatePlaceholderText];
}


- (UIColor*)placeholderColor
{
return objc_getAssociatedObject(self, @selector(placeholderColor));
}


- (void)updatePlaceholderText
{
NSString *text = self.placeholderText;
UIColor *color = self.placeholderColor;
if(text && color)
{
UITextField *textField = (UITextField*)self;
textField.attributedPlaceholder = [[NSAttributedString alloc] initWithString:text attributes:@{NSForegroundColorAttributeName:color}];
}
}


@end

有我的简单版本的UITextView占位符。主要思想是:

  • 隐藏占位符,如果用户开始编辑和占位符是可见的
  • 如果用户结束编辑并且文本视图的text为空,则显示占位符。
class PlaceholderTextView: UITextView {


var placeholder = "" {
didSet {
if isPlaceholderVisible {
showPlaceholder()
}
}
}


var isPlaceholderVisible = true {
didSet {
isPlaceholderVisible ? showPlaceholder() : hidePlaceholder()
}
}


init() {
super.init(frame: .zero, textContainer: nil)
delegate = self
}


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


private func showPlaceholder() {
text = placeholder
// Set other things like color of text for placeholder, ...
}


private func hidePlaceholder() {
text = ""
// Set other things like color of text for normal input, ...
}


}


extension PlaceholderTextView: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
if isPlaceholderVisible {
isPlaceholderVisible = false
}
}


func textViewDidEndEditing(_ textView: UITextView) {
if text.isEmpty {
isPlaceholderVisible = true
}
}
}

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

import Foundation
import UIKit


class TextViewWithPlaceholder: UITextView {


private var placeholderTextView: UITextView = UITextView()


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


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


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


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


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


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


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


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


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


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


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


}

斯威夫特5.2

独立的类

如果你想要一个类,你可以在任何地方使用,因为它是自包含的

import UIKit
class PlaceHolderTextView:UITextView, UITextViewDelegate{
var placeholderText = "placeholderText"


override func willMove(toSuperview newSuperview: UIView?) {
textColor = .lightText
delegate = self
}


func textViewDidBeginEditing(_ textView: UITextView) {
if textView.text == placeholderText{
placeholderText = textView.text
textView.text = ""
textView.textColor = .darkText
}
}


func textViewDidEndEditing(_ textView: UITextView) {
if textView.text == ""{
textView.text = placeholderText
textColor = .lightText
}
}
}

这里的关键是willMove(toSuperView:)函数,因为它允许你在添加到另一个视图的层次结构之前设置视图(类似于ViewControllers中的viewDidLoad/viewWillAppear)

这就是我所做的。倾向于代码清晰和简单。我需要添加一个textView,将获得一些额外的笔记在我的应用程序。这额外的笔记可以创建或保存后修改。见下文。HTH。:)

class NotesTextView: UITextView {


var placeholder = "" {
didSet {
showPlaceholder()
}
}
    

// if the text is the placeholder, then assign a color fitting for a
// placeholder text, else, assign it your color of choice.
override var text: String! {
didSet {
textColor = text == placeholder ? .tertiaryLabel : .systemBlue
}
}
    

    

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

delegate = self
//config your font and translateAutoResizingMaskIntoConstraints here
}
    

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

private func showPlaceholder() {
text = placeholder
}
    

private func hidePlaceholder() {
text = ""
}
}


extension NotesTextView: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
if text == placeholder {
hidePlaceholder()
}
}
    

func textViewDidEndEditing(_ textView: UITextView) {
if text.isEmpty {
showPlaceholder()
}
}
}

SWIFTUI

这是一个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)

无需添加任何第三方库。只需使用下面的代码…

class SubmitReviewVC : UIViewController, UITextViewDelegate {


@IBOutlet var txtMessage : UITextView!
var lblPlaceHolder : UILabel!


override func viewDidLoad() {
super.viewDidLoad()


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


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

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

  1. 我们必须将类IQTextView分配给我们的TextView类。
  2. 我们必须在Controller页面中导入IQKeyboardManagerSwift
  3. 最后但并非最不重要的是,如果你想,将textView的出口设置在Controller页面//:)
  4. 通过故事板inspectable给textView一些占位符文本
import UIKit
import RxSwift


@IBDesignable class TextViewWithPlaceholder: UITextView {
    

//MARK: - Propertise
@IBInspectable var placeholderText: String = ""
    

let placeholderLabel = LocalizedUILabel()
private let hidePlaceholderObserver = PublishSubject<Bool>()
let disposeBag = DisposeBag()
    

    

//MARK: - Did Move To Window
override func didMoveToWindow() {
super.didMoveToWindow()
observeOnTextViewEditing()
configurePlaceholder()
}
    

    

//MARK: - Observe On Text View Editing
private func observeOnTextViewEditing() {
rx.text.subscribe(onNext: { [weak self] selectedText in
guard let self = self else { return }
self.hidePlaceholderObserver.onNext((selectedText?.isEmpty ?? true) ? false : true)
            

}).disposed(by: disposeBag)
}
    

    

//MARK: - Observe On Show Hide Placeholder
private func configurePlaceholder() {
hidePlaceholderObserver
.bind(to: placeholderLabel.rx.isHidden)
.disposed(by: disposeBag)
        

placeholderLabel.text = placeholderText
placeholderLabel.font = UIFont(name: "Poppins-Semibold", size: 16) ?? UIFont()
placeholderLabel.textColor = .lightGray
        

placeholderLabel.sizeToFit()
placeholderLabel.frame.origin = CGPoint(x: 8, y: 8)
addSubview(placeholderLabel)
}
    

}

可能是UITextView占位符实现的最简单的开箱即用的解决方案,它不会遭受:

  • 使用UILabel而不是UITextView,这可能会有不同的性能
  • 切换到占位符“UITextView”副本,该副本将捕获主UITextView控件中遗漏的第一个键入字符
  • 打乱主要UITextView控件text内容替换占位符为空字符串或第一个键入字符。边界情况是,如果用户输入占位符文本,一些建议的实现将把它作为占位符本身。

斯威夫特5:

import UIKit
import SnapKit
import RxSwift
import RxCocoa


class TextAreaView: UIView {
let textArea = UITextView()
let textAreaPlaceholder = UITextView()
    

override init(frame: CGRect) {
super.init(frame: frame)
commonSetup()
}
    

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

private func commonSetup() {
        

addSubview(textAreaPlaceholder)
addSubview(textArea)
        

textArea.isScrollEnabled = false
textArea.delegate = self


textAreaPlaceholder.isScrollEnabled = false
textAreaPlaceholder.textColor = UIColor.lightGray
        

textArea.snp.makeConstraints { make in
make.top.bottom.leading.trailing.equalToSuperview()
}


textAreaPlaceholder.snp.makeConstraints { make in
make.top.bottom.leading.trailing.equalTo(textArea.snp.top)
}


textAreaPlaceholder.text = "Placeholder"
        

updatePlaceholder()
}
    

func updatePlaceholder() {
if textArea.text.count > 0 {
textArea.alpha = 1.0
} else {
textArea.alpha = 0.0
}
}
}


extension TextAreaView: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
updatePlaceholder()
}
}