在 Swift 中的 UITextView 中添加占位符文本?
Posted
技术标签:
【中文标题】在 Swift 中的 UITextView 中添加占位符文本?【英文标题】:Add placeholder text inside UITextView in Swift? 【发布时间】:2015-02-23 11:26:06 【问题描述】:如何在UITextView
中添加一个占位符,类似于在Swift
中为UITextField
设置的占位符?
【问题讨论】:
这是 ios 开发中使用 UITextView 的一个古老问题。我已经编写了像这里提到的子类:***.com/a/1704469/1403046。好处是您仍然可以拥有一个委托,以及在多个地方使用该类,而无需重新实现逻辑。 我将如何使用您的子类,同时为项目使用 swift。使用桥接文件? 你可以这样做,或者在 Swift 中重新实现它。答案中的代码比实际需要的要长。要点是显示/隐藏您在文本更改时收到通知的方法中添加的标签。 您可以使用来自 GitHub 的 UIFloatLabelTextView 示例。这个位置占位符在写的时候在上面。真的很有趣的一个! github.com/ArtSabintsev/UIFloatLabelTextView 老实说,实现这一点的最简单方法是使用自定义 textView 并在没有文本时添加在 textView 上绘制的占位符文本......到目前为止,其他答案一直是这个版本过于复杂,涉及有问题的状态管理(包括文本应该/不应该存在/不存在时的误报) 【参考方案1】:为 Swift 4 更新
UITextView
本身没有占位符属性,因此您必须使用UITextViewDelegate
方法以编程方式创建和操作一个。我建议根据所需的行为使用下面的解决方案 #1 或 #2。
注意:对于任一解决方案,将UITextViewDelegate
添加到类并设置textView.delegate = self
以使用文本视图的委托方法。
解决方案 #1 - 如果您希望占位符在用户选择文本视图后立即消失:
首先将UITextView
设置为包含占位符文本并将其设置为浅灰色以模仿UITextField
的占位符文本的外观。在viewDidLoad
中或在创建文本视图时这样做。
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
然后当用户开始编辑文本视图时,如果文本视图包含占位符(即如果其文本颜色为浅灰色),则清除占位符文本并将文本颜色设置为黑色以适应用户的输入。
func textViewDidBeginEditing(_ textView: UITextView)
if textView.textColor == UIColor.lightGray
textView.text = nil
textView.textColor = UIColor.black
然后,当用户完成对文本视图的编辑并辞去第一响应者的职务时,如果文本视图为空,则通过重新添加占位符文本并将其颜色设置为浅灰色来重置其占位符。
func textViewDidEndEditing(_ textView: UITextView)
if textView.text.isEmpty
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
解决方案 #2 - 如果您希望在文本视图为空时显示占位符,即使文本视图已被选中:
先在viewDidLoad
中设置占位符:
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
textView.becomeFirstResponder()
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
(注意:由于 OP 希望在视图加载后立即选择文本视图,因此我将文本视图选择合并到上述代码中。如果这不是您想要的行为并且您不希望选择文本视图查看加载,从上面的代码块中删除最后两行。)
然后使用shouldChangeTextInRange
UITextViewDelegate
方法,如下所示:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
// Combine the textView text and the replacement text to
// create the updated text string
let currentText:String = textView.text
let updatedText = (currentText as NSString).replacingCharacters(in: range, with: text)
// If updated text view will be empty, add the placeholder
// and set the cursor to the beginning of the text view
if updatedText.isEmpty
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
// Else if the text view's placeholder is showing and the
// length of the replacement string is greater than 0, set
// the text color to black then set its text to the
// replacement string
else if textView.textColor == UIColor.lightGray && !text.isEmpty
textView.textColor = UIColor.black
textView.text = text
// For every other case, the text should change with the usual
// behavior...
else
return true
// ...otherwise return false since the updates have already
// been made
return false
并且还实现textViewDidChangeSelection
以防止用户在占位符可见时更改光标的位置。 (注意:textViewDidChangeSelection
在视图加载之前被调用,所以只有在窗口可见时才检查文本视图的颜色):
func textViewDidChangeSelection(_ textView: UITextView)
if self.view.window != nil
if textView.textColor == UIColor.lightGray
textView.selectedTextRange = textView.textRange(from: textView.beginningOfDocument, to: textView.beginningOfDocument)
【讨论】:
您好,请确保您将您的视图控制器设置为您的 textView 的委托。你可以通过创建一个从你的 textView 到你的 viewController 的出口来实现这一点。然后使用yourTextField.delegate = self
。如果不这样做,textViewDidBeginEditing
和 textViewDidEndEditing
函数将不起作用。
代码没有编译,我遇到了Cannot convert value of type 'NSRange' (aka '_NSRange') to expected argument type 'Range<String.Index>' (aka 'Range<String.CharacterView.Index>')
的错误。
我找到了解决方案,我将附上修改后的代码@iPeter。当前文本必须是 NSString 共振峰:let currentText = textView.text as NSString?
。将let updatedText =
行转换为let updatedText = currentText?.replacingCharacters(in: range, with: text)
。最后,将if updatedText.isEmpty
行转换为if (updatedText?.isEmpty)!
。这应该可以解决问题!
@LyndseyScott 在func textViewDidChangeSelection(_ textView: UITextView)
中设置func textViewDidChangeSelection(_ textView: UITextView)
会导致无限循环...
对于如此简单的事情,(iOS) 开发人员竟需要竭尽全力,这绝对令人震惊。这是很久以前应该是 TextView 控件的一部分的基本功能,我不明白为什么 Apple 还没有解决这个问题。【参考方案2】:
我通过使用两个不同的文本视图来做到这一点:
-
背景中的一个用作占位符。
在用户实际输入的前景中(具有透明背景)。
这个想法是,一旦用户开始在前景视图中输入内容,后台的占位符就会消失(如果用户删除所有内容,则会重新出现)。所以它的行为与单行文本字段的占位符完全一样。
这是我使用的代码。请注意,descriptionField 是用户输入的字段,descriptionPlaceholder 是后台的字段。
func textViewDidChange(descriptionField: UITextView)
if descriptionField.text.isEmpty == false
descriptionPlaceholder.text = ""
else
descriptionPlaceholder.text = descriptionPlaceholderText
【讨论】:
这种方式有点hacky,但它绝对是最简单的,并且可以准确地生成您想要的结果。好主意【参考方案3】:浮动占位符
在文本视图上方放置占位符标签、设置其字体、颜色并通过跟踪文本视图字符数的变化来管理占位符可见性是简单、安全和可靠的。
斯威夫特 3:
class NotesViewController : UIViewController, UITextViewDelegate
@IBOutlet var textView : UITextView!
var placeholderLabel : UILabel!
override func viewDidLoad()
super.viewDidLoad()
textView.delegate = self
placeholderLabel = UILabel()
placeholderLabel.text = "Enter some text..."
placeholderLabel.font = UIFont.italicSystemFont(ofSize: (textView.font?.pointSize)!)
placeholderLabel.sizeToFit()
textView.addSubview(placeholderLabel)
placeholderLabel.frame.origin = CGPoint(x: 5, y: (textView.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !textView.text.isEmpty
func textViewDidChange(_ textView: UITextView)
placeholderLabel.isHidden = !textView.text.isEmpty
Swift 2: 相同,除了:italicSystemFontOfSize(textView.font.pointSize)
、UIColor.lightGrayColor
【讨论】:
这种方法无疑是我发现的最简单、最不容易出错的方法。太棒了。 这是一个很好的方法,但是如果占位符文本很长,标签文本可能会超出范围.. 简单易用,并与现有行为很好地集成。无论如何,占位符消息不应该那么冗长,但是将行设置为 0 不会解决这个问题吗? 我通常更喜欢这种方法,但如果文本居中对齐会出现问题,因为光标将位于占位符的顶部而不是左侧。 @blwinters - 这将是一个非常不寻常的极端情况,不是吗?但是假设在这种情况下它是一个多行输入字段(我必须假设),您不能调整垂直偏移计算以将占位符文本从光标移开一点吗?【参考方案4】:斯威夫特:
以编程方式或通过 Interface Builder 添加您的文本视图,如果是最后一个,请创建插座:
@IBOutlet weak var yourTextView: UITextView!
请添加委托(UITextViewDelegate):
class ViewController: UIViewController, UITextViewDelegate
在 viewDidLoad 方法中,添加以下内容:
override func viewDidLoad()
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
yourTextView.delegate = self
yourTextView.text = "Placeholder text goes right here..."
yourTextView.textColor = UIColor.lightGray
现在让我介绍一下神奇的部分,添加这个功能:
func textViewDidBeginEditing(_ textView: UITextView)
if yourTextView.textColor == UIColor.lightGray
yourTextView.text = ""
yourTextView.textColor = UIColor.black
请注意,这将在编辑开始时执行,我们将使用 color 属性检查条件以告知状态。
我不推荐将文本设置为nil
。之后,我们将文本颜色设置为所需的颜色,在本例中为黑色。
现在也添加这个功能:
func textViewDidEndEditing(_ textView: UITextView)
if yourTextView.text == ""
yourTextView.text = "Placeholder text ..."
yourTextView.textColor = UIColor.lightGray
让我坚持,不要与nil
比较,我已经尝试过了,但它不起作用。然后我们将值设置回占位符样式,并将颜色设置回占位符颜色,因为这是签入textViewDidBeginEditing
的条件。
【讨论】:
【参考方案5】:我不知道为什么人们将这个问题如此复杂化.... 这相当简单明了。这是提供请求功能的 UITextView 的子类。
- (void)customInit
self.contentMode = UIViewContentModeRedraw;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textChanged:) name:UITextViewTextDidChangeNotification object:nil];
- (void)textChanged:(NSNotification *)notification
if (notification.object == self)
if(self.textStorage.length != 0 || !self.textStorage.length)
[self setNeedsDisplay];
#pragma mark - Setters
- (void)setPlaceholderText:(NSString *)placeholderText withFont:(UIFont *)font
self.placeholderText = placeholderText;
self.placeholderTextFont = font;
- (void)drawRect:(CGRect)rect
[super drawRect:rect];
[[UIColor lightGrayColor] setFill];
if (self.textStorage.length != 0)
return;
CGRect inset = CGRectInset(rect, 8, 8);//Default rect insets for textView
NSDictionary *attributes = @NSFontAttributeName: self.placeholderTextFont, NSForegroundColorAttributeName: [UIColor grayColor];
[self.placeholderText drawInRect:inset withAttributes:attributes];
`
【讨论】:
仅供参考,在有人打败我之前......这可以直接翻译成 Swift。这里没有什么复杂的。【参考方案6】:在视图加载中设置值
txtVw!.autocorrectionType = UITextAutocorrectionType.No
txtVw!.text = "Write your Placeholder"
txtVw!.textColor = UIColor.lightGrayColor()
func textViewDidBeginEditing(textView: UITextView)
if (txtVw?.text == "Write your Placeholder")
txtVw!.text = nil
txtVw!.textColor = UIColor.blackColor()
func textViewDidEndEditing(textView: UITextView)
if txtVw!.text.isEmpty
txtVw!.text = "Write your Placeholder"
txtVw!.textColor = UIColor.lightGrayColor()
textView.resignFirstResponder()
【讨论】:
【参考方案7】:在 ios 中没有这样的属性可以直接在 TextView 中添加占位符,而是您可以添加标签并在 textView 中显示/隐藏更改。 SWIFT 2.0 并确保实现 textviewdelegate
func textViewDidChange(TextView: UITextView)
if txtShortDescription.text == ""
self.lblShortDescription.hidden = false
else
self.lblShortDescription.hidden = true
【讨论】:
【参考方案8】:如果您使用多个文本视图,这是我准备使用的解决方案
func textViewShouldBeginEditing(textView: UITextView) -> Bool
// Set cursor to the beginning if placeholder is set
if textView.textColor == UIColor.lightGrayColor()
textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
return true
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool
// Remove placeholder
if textView.textColor == UIColor.lightGrayColor() && text.characters.count > 0
textView.text = ""
textView.textColor = UIColor.blackColor()
if text == "\n"
textView.resignFirstResponder()
return false
return true
func textViewDidChange(textView: UITextView)
// Set placeholder if text is empty
if textView.text.isEmpty
textView.text = NSLocalizedString("Hint", comment: "hint")
textView.textColor = UIColor.lightGrayColor()
textView.selectedTextRange = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
func textViewDidChangeSelection(textView: UITextView)
// Set cursor to the beginning if placeholder is set
let firstPosition = textView.textRangeFromPosition(textView.beginningOfDocument, toPosition: textView.beginningOfDocument)
// Do not change position recursively
if textView.textColor == UIColor.lightGrayColor() && textView.selectedTextRange != firstPosition
textView.selectedTextRange = firstPosition
【讨论】:
非常好!【参考方案9】:Swift - 我编写了一个继承 UITextView 的类,并添加了一个 UILabel 作为子视图来充当占位符。
import UIKit
@IBDesignable
class HintedTextView: UITextView
@IBInspectable var hintText: String = "hintText"
didSet
hintLabel.text = hintText
private lazy var hintLabel: UILabel =
let label = UILabel()
label.font = UIFont.systemFontOfSize(16)
label.textColor = UIColor.lightGrayColor()
label.translatesAutoresizingMaskIntoConstraints = false
return label
()
override init(frame: CGRect, textContainer: NSTextContainer?)
super.init(frame: frame, textContainer: textContainer)
setupView()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
setupView()
override func prepareForInterfaceBuilder()
super.prepareForInterfaceBuilder()
setupView()
private func setupView()
translatesAutoresizingMaskIntoConstraints = false
delegate = self
font = UIFont.systemFontOfSize(16)
addSubview(hintLabel)
NSLayoutConstraint.activateConstraints([
hintLabel.leftAnchor.constraintEqualToAnchor(leftAnchor, constant: 4),
hintLabel.rightAnchor.constraintEqualToAnchor(rightAnchor, constant: 8),
hintLabel.topAnchor.constraintEqualToAnchor(topAnchor, constant: 4),
hintLabel.heightAnchor.constraintEqualToConstant(30)
])
override func layoutSubviews()
super.layoutSubviews()
setupView()
【讨论】:
使用约束来定位占位符的好例子,但是占位符的理想位置正好在文本的第一个字符的正上方,因此根据文本视图的位置计算该位置字体更好,因为它适应为文本视图设置的任何字体大小。【参考方案10】:我喜欢@nerdist 的解决方案。基于此,我为UITextView
创建了一个扩展:
import Foundation
import UIKit
extension UITextView
private func add(_ placeholder: UILabel)
for view in self.subviews
if let lbl = view as? UILabel
if lbl.text == placeholder.text
lbl.removeFromSuperview()
self.addSubview(placeholder)
func addPlaceholder(_ placeholder: UILabel?)
if let ph = placeholder
ph.numberOfLines = 0 // support for multiple lines
ph.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
ph.sizeToFit()
self.add(ph)
ph.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
ph.textColor = UIColor(white: 0, alpha: 0.3)
updateVisibility(ph)
func updateVisibility(_ placeHolder: UILabel?)
if let ph = placeHolder
ph.isHidden = !self.text.isEmpty
例如,在 ViewController 类中,我是这样使用它的:
class MyViewController: UIViewController, UITextViewDelegate
private var notePlaceholder: UILabel!
@IBOutlet weak var txtNote: UITextView!
...
// UIViewController
override func viewDidLoad()
notePlaceholder = UILabel()
notePlaceholder.text = "title\nsubtitle\nmore..."
txtNote.addPlaceholder(notePlaceholder)
...
// UITextViewDelegate
func textViewDidChange(_ textView: UITextView)
txtNote.updateVisbility(notePlaceholder)
...
UITextview 上的占位符!
更新:
如果你在代码中改变了textview的文本,记得调用updateVisibitly方法隐藏占位符:
txtNote.text = "something in code"
txtNote.updateVisibility(self.notePlaceholder) // hide placeholder if text is not empty.
为防止多次添加占位符,在extension
中添加了私有add()
函数。
【讨论】:
再次感谢您对原版的改进。这个周末我花了太多时间,但我想你会喜欢我最终想出的 EZ Placeholder 极端变体:***.com/a/41081244/2079103 (我感谢你为占位符解决方案的发展做出了贡献) 【参考方案11】:在 swift2.2 中:
public class CustomTextView: UITextView
private struct Constants
static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
private let placeholderLabel: UILabel = UILabel()
private var placeholderLabelConstraints = [NSLayoutConstraint]()
@IBInspectable public var placeholder: String = ""
didSet
placeholderLabel.text = placeholder
@IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor
didSet
placeholderLabel.textColor = placeholderColor
override public var font: UIFont!
didSet
placeholderLabel.font = font
override public var textAlignment: NSTextAlignment
didSet
placeholderLabel.textAlignment = textAlignment
override public var text: String!
didSet
textDidChange()
override public var attributedText: NSAttributedString!
didSet
textDidChange()
override public var textContainerInset: UIEdgeInsets
didSet
updateConstraintsForPlaceholderLabel()
override public init(frame: CGRect, textContainer: NSTextContainer?)
super.init(frame: frame, textContainer: textContainer)
commonInit()
required public init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
commonInit()
private func commonInit()
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(textDidChange),
name: UITextViewTextDidChangeNotification,
object: nil)
placeholderLabel.font = font
placeholderLabel.textColor = placeholderColor
placeholderLabel.textAlignment = textAlignment
placeholderLabel.text = placeholder
placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = UIColor.clearColor()
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(placeholderLabel)
updateConstraintsForPlaceholderLabel()
private func updateConstraintsForPlaceholderLabel()
var newConstraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|-(\(textContainerInset.top))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints.append(NSLayoutConstraint(
item: placeholderLabel,
attribute: .Width,
relatedBy: .Equal,
toItem: self,
attribute: .Width,
multiplier: 1.0,
constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
))
removeConstraints(placeholderLabelConstraints)
addConstraints(newConstraints)
placeholderLabelConstraints = newConstraints
@objc private func textDidChange()
placeholderLabel.hidden = !text.isEmpty
public override func layoutSubviews()
super.layoutSubviews()
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
deinit
NSNotificationCenter.defaultCenter().removeObserver(self,
name: UITextViewTextDidChangeNotification,
object: nil)
在 swift3 中:
import UIKit
类 CustomTextView: UITextView
private struct Constants
static let defaultiOSPlaceholderColor = UIColor(red: 0.0, green: 0.0, blue: 0.0980392, alpha: 0.22)
private let placeholderLabel: UILabel = UILabel()
private var placeholderLabelConstraints = [NSLayoutConstraint]()
@IBInspectable public var placeholder: String = ""
didSet
placeholderLabel.text = placeholder
@IBInspectable public var placeholderColor: UIColor = CustomTextView.Constants.defaultiOSPlaceholderColor
didSet
placeholderLabel.textColor = placeholderColor
override public var font: UIFont!
didSet
placeholderLabel.font = font
override public var textAlignment: NSTextAlignment
didSet
placeholderLabel.textAlignment = textAlignment
override public var text: String!
didSet
textDidChange()
override public var attributedText: NSAttributedString!
didSet
textDidChange()
override public var textContainerInset: UIEdgeInsets
didSet
updateConstraintsForPlaceholderLabel()
override public init(frame: CGRect, textContainer: NSTextContainer?)
super.init(frame: frame, textContainer: textContainer)
commonInit()
required public init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
commonInit()
private func commonInit()
NotificationCenter.default.addObserver(self,
selector: #selector(textDidChange),
name: NSNotification.Name.UITextViewTextDidChange,
object: nil)
placeholderLabel.font = font
placeholderLabel.textColor = placeholderColor
placeholderLabel.textAlignment = textAlignment
placeholderLabel.text = placeholder
placeholderLabel.numberOfLines = 0
placeholderLabel.backgroundColor = UIColor.clear
placeholderLabel.translatesAutoresizingMaskIntoConstraints = false
addSubview(placeholderLabel)
updateConstraintsForPlaceholderLabel()
private func updateConstraintsForPlaceholderLabel()
var newConstraints = NSLayoutConstraint.constraints(withVisualFormat: "H:|-(\(textContainerInset.left + textContainer.lineFragmentPadding))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints += NSLayoutConstraint.constraints(withVisualFormat: "V:|-(\(textContainerInset.top))-[placeholder]",
options: [],
metrics: nil,
views: ["placeholder": placeholderLabel])
newConstraints.append(NSLayoutConstraint(
item: placeholderLabel,
attribute: .width,
relatedBy: .equal,
toItem: self,
attribute: .width,
multiplier: 1.0,
constant: -(textContainerInset.left + textContainerInset.right + textContainer.lineFragmentPadding * 2.0)
))
removeConstraints(placeholderLabelConstraints)
addConstraints(newConstraints)
placeholderLabelConstraints = newConstraints
@objc private func textDidChange()
placeholderLabel.isHidden = !text.isEmpty
public override func layoutSubviews()
super.layoutSubviews()
placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2.0
deinit
NotificationCenter.default.removeObserver(self,
name: NSNotification.Name.UITextViewTextDidChange,
object: nil)
我用swift写了一个类。您需要在需要时导入此类。
【讨论】:
【参考方案12】:我试图通过clearlight 的answer 简化代码。
extension UITextView
func setPlaceholder()
let placeholderLabel = UILabel()
placeholderLabel.text = "Enter some text..."
placeholderLabel.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
placeholderLabel.sizeToFit()
placeholderLabel.tag = 222
placeholderLabel.frame.origin = CGPoint(x: 5, y: (self.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !self.text.isEmpty
self.addSubview(placeholderLabel)
func checkPlaceholder()
let placeholderLabel = self.viewWithTag(222) as! UILabel
placeholderLabel.isHidden = !self.text.isEmpty
用法
override func viewDidLoad()
textView.delegate = self
textView.setPlaceholder()
func textViewDidChange(_ textView: UITextView)
textView.checkPlaceholder()
【讨论】:
几个问题。 (1) 作为一个假设和“借用” UIView 标签属性值的扩展,有人可能会在他们自己的视图层次结构中使用相同的标签,而不知道扩展的用法,从而产生极难诊断的错误。类似的东西不属于库代码或扩展。 (2) 它仍然需要调用者声明一个委托。 Floating Placeholder 避免了黑客攻击,占用空间小,简单且完全本地化,这是一个安全的选择。【参考方案13】:另一种解决方案(Swift 3):
import UIKit
protocol PlaceholderTextViewDelegate
func placeholderTextViewDidChangeText(_ text:String)
func placeholderTextViewDidEndEditing(_ text:String)
final class PlaceholderTextView: UITextView
var notifier:PlaceholderTextViewDelegate?
var placeholder: String?
didSet
placeholderLabel?.text = placeholder
var placeholderColor = UIColor.lightGray
var placeholderFont = UIFont.appMainFontForSize(14.0)
didSet
placeholderLabel?.font = placeholderFont
fileprivate var placeholderLabel: UILabel?
// MARK: - LifeCycle
init()
super.init(frame: CGRect.zero, textContainer: nil)
awakeFromNib()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
override func awakeFromNib()
super.awakeFromNib()
self.delegate = self
NotificationCenter.default.addObserver(self, selector: #selector(PlaceholderTextView.textDidChangeHandler(notification:)), name: .UITextViewTextDidChange, object: nil)
placeholderLabel = UILabel()
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.text = placeholder
placeholderLabel?.textAlignment = .left
placeholderLabel?.numberOfLines = 0
override func layoutSubviews()
super.layoutSubviews()
placeholderLabel?.font = placeholderFont
var height:CGFloat = placeholderFont.lineHeight
if let data = placeholderLabel?.text
let expectedDefaultWidth:CGFloat = bounds.size.width
let fontSize:CGFloat = placeholderFont.pointSize
let textView = UITextView()
textView.text = data
textView.font = UIFont.appMainFontForSize(fontSize)
let sizeForTextView = textView.sizeThatFits(CGSize(width: expectedDefaultWidth,
height: CGFloat.greatestFiniteMagnitude))
let expectedTextViewHeight = sizeForTextView.height
if expectedTextViewHeight > height
height = expectedTextViewHeight
placeholderLabel?.frame = CGRect(x: 5, y: 0, width: bounds.size.width - 16, height: height)
if text.isEmpty
addSubview(placeholderLabel!)
bringSubview(toFront: placeholderLabel!)
else
placeholderLabel?.removeFromSuperview()
func textDidChangeHandler(notification: Notification)
layoutSubviews()
extension PlaceholderTextView : UITextViewDelegate
// MARK: - UITextViewDelegate
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool
if(text == "\n")
textView.resignFirstResponder()
return false
return true
func textViewDidChange(_ textView: UITextView)
notifier?.placeholderTextViewDidChangeText(textView.text)
func textViewDidEndEditing(_ textView: UITextView)
notifier?.placeholderTextViewDidEndEditing(textView.text)
结果
【讨论】:
【参考方案14】:由于声誉问题,我无法添加评论。在@clearlight 答案中再添加一个代表需求。
func textViewDidBeginEditing(_ textView: UITextView)
cell.placeholderLabel.isHidden = !textView.text.isEmpty
需要
因为textViewDidChange
不是第一次调用
【讨论】:
【参考方案15】:斯威夫特 3.1
这个扩展对我很有效:https://github.com/devxoul/UITextView-Placeholder
这是一个代码sn-p:
通过 pod 安装:
pod 'UITextView+Placeholder', '~> 1.2'
将其导入您的班级
import UITextView_Placeholder
并将placeholder
属性添加到您已经创建的UITextView
textView.placeholder = "Put some detail"
就是这样...
这是它的外观(第三个框是UITextView
)
【讨论】:
【参考方案16】:没有可用于 textview 的占位符。当用户在 textview 中输入时,您必须在其上方放置标签,然后在用户输入时将其隐藏或设置为默认值删除所有值。
【讨论】:
【参考方案17】:使用此扩展这是在 UITextView 中设置占位符的最佳方式。 但请确保您已将委托附加到 TextView。您可以像这样设置占位符:-
yourTextView.placeholder = "Placeholder"
extension UITextView :UITextViewDelegate
/// Resize the placeholder when the UITextView bounds change
override open var bounds: CGRect
didSet
self.resizePlaceholder()
/// The UITextView placeholder text
public var placeholder: String?
get
var placeholderText: String?
if let placeholderLabel = self.viewWithTag(100) as? UILabel
placeholderText = placeholderLabel.text
return placeholderText
set
if let placeholderLabel = self.viewWithTag(100) as! UILabel?
placeholderLabel.text = newValue
placeholderLabel.sizeToFit()
else
self.addPlaceholder(newValue!)
/// When the UITextView did change, show or hide the label based on if the UITextView is empty or not
///
/// - Parameter textView: The UITextView that got updated
public func textViewDidChange(_ textView: UITextView)
if let placeholderLabel = self.viewWithTag(100) as? UILabel
placeholderLabel.isHidden = self.text.count > 0
/// Resize the placeholder UILabel to make sure it's in the same position as the UITextView text
private func resizePlaceholder()
if let placeholderLabel = self.viewWithTag(100) as! UILabel?
let labelX = self.textContainer.lineFragmentPadding
let labelY = self.textContainerInset.top - 2
let labelWidth = self.frame.width - (labelX * 2)
let labelHeight = placeholderLabel.frame.height
placeholderLabel.frame = CGRect(x: labelX, y: labelY, width: labelWidth, height: labelHeight)
/// Adds a placeholder UILabel to this UITextView
private func addPlaceholder(_ placeholderText: String)
let placeholderLabel = UILabel()
placeholderLabel.text = placeholderText
placeholderLabel.sizeToFit()
placeholderLabel.font = self.font
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.tag = 100
placeholderLabel.isHidden = self.text.count > 0
self.addSubview(placeholderLabel)
self.resizePlaceholder()
self.delegate = self
【讨论】:
像魅力一样工作!非常感谢你 ! :) 欢迎亲爱的,一切顺利 这是一个非常优雅的答案。这应该是投票最高的答案【参考方案18】:上面clearlight的答案的协议版本,因为协议很棒。随心所欲地弹出它。灌篮!
extension UITextViewPlaceholder where Self: UIViewController
// Use this in ViewController's ViewDidLoad method.
func addPlaceholder(text: String, toTextView: UITextView, font: UIFont? = nil)
placeholderLabel = UILabel()
placeholderLabel.text = text
placeholderLabel.font = font ?? UIFont.italicSystemFont(ofSize: (toTextView.font?.pointSize)!)
placeholderLabel.sizeToFit()
toTextView.addSubview(placeholderLabel)
placeholderLabel.frame.origin = CGPoint(x: 5, y: (toTextView.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !toTextView.text.isEmpty
// Use this function in the ViewController's textViewDidChange delegate method.
func textViewWithPlaceholderDidChange(_ textView: UITextView)
placeholderLabel.isHidden = !textView.text.isEmpty
【讨论】:
【参考方案19】:文本视图委托方法
使用这两个委托方法并在你的类中编写 UITextViewDelegate
func textViewDidBeginEditing(_ textView: UITextView)
if (commentsTextView.text == "Type Your Comments")
commentsTextView.text = nil
commentsTextView.textColor = UIColor.darkGray
func textViewDidEndEditing(_ textView: UITextView)
if commentsTextView.text.isEmpty
commentsTextView.text = "Type Your Comments"
commentsTextView.textColor = UIColor.darkGray
textView.resignFirstResponder()
【讨论】:
【参考方案20】:func setPlaceholder()
var placeholderLabel = UILabel()
placeholderLabel.text = "Describe your need..."
placeholderLabel.font = UIFont.init(name: "Lato-Regular", size: 15.0) ?? UIFont.boldSystemFont(ofSize: 14.0)
placeholderLabel.sizeToFit()
descriptionTextView.addSubview(placeholderLabel)
placeholderLabel.frame.origin = CGPoint(x: 5, y: (descriptionTextView.font?.pointSize)! / 2)
placeholderLabel.textColor = UIColor.lightGray
placeholderLabel.isHidden = !descriptionTextView.text.isEmpty
//Delegate Method.
func textViewDidChange(_ textView: UITextView)
placeholderLabel.isHidden = !textView.text.isEmpty
【讨论】:
【参考方案21】:这里有一些东西可以放入UIStackView
,它会使用内部高度约束来调整大小。可能需要调整以适应特定要求。
import UIKit
public protocol PlaceholderTextViewDelegate: class
func placeholderTextViewTextChanged(_ textView: PlaceholderTextView, text: String)
public class PlaceholderTextView: UIView
public weak var delegate: PlaceholderTextViewDelegate?
private var heightConstraint: NSLayoutConstraint?
public override init(frame: CGRect)
self.allowsNewLines = true
super.init(frame: frame)
self.heightConstraint = self.heightAnchor.constraint(equalToConstant: 0)
self.heightConstraint?.isActive = true
self.addSubview(self.placeholderTextView)
self.addSubview(self.textView)
self.pinToCorners(self.placeholderTextView)
self.pinToCorners(self.textView)
self.updateHeight()
public override func didMoveToSuperview()
super.didMoveToSuperview()
self.updateHeight()
private func pinToCorners(_ view: UIView)
NSLayoutConstraint.activate([
view.leadingAnchor.constraint(equalTo: self.leadingAnchor),
view.trailingAnchor.constraint(equalTo: self.trailingAnchor),
view.topAnchor.constraint(equalTo: self.topAnchor),
view.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
// Accessors
public var text: String?
didSet
self.textView.text = text
self.textViewDidChange(self.textView)
self.updateHeight()
public var textColor: UIColor?
didSet
self.textView.textColor = textColor
self.updateHeight()
public var font: UIFont?
didSet
self.textView.font = font
self.placeholderTextView.font = font
self.updateHeight()
public override var tintColor: UIColor?
didSet
self.textView.tintColor = tintColor
self.placeholderTextView.tintColor = tintColor
public var placeholderText: String?
didSet
self.placeholderTextView.text = placeholderText
self.updateHeight()
public var placeholderTextColor: UIColor?
didSet
self.placeholderTextView.textColor = placeholderTextColor
self.updateHeight()
public var allowsNewLines: Bool
public required init?(coder _: NSCoder)
fatalError("init(coder:) has not been implemented")
private lazy var textView: UITextView = self.newTextView()
private lazy var placeholderTextView: UITextView = self.newTextView()
private func newTextView() -> UITextView
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isScrollEnabled = false
textView.delegate = self
textView.backgroundColor = .clear
return textView
private func updateHeight()
let maxSize = CGSize(width: self.frame.size.width, height: .greatestFiniteMagnitude)
let textViewSize = self.textView.sizeThatFits(maxSize)
let placeholderSize = self.placeholderTextView.sizeThatFits(maxSize)
let maxHeight = ceil(CGFloat.maximum(textViewSize.height, placeholderSize.height))
self.heightConstraint?.constant = maxHeight
extension PlaceholderTextView: UITextViewDelegate
public func textViewDidChangeSelection(_: UITextView)
self.placeholderTextView.alpha = self.textView.text.isEmpty ? 1 : 0
self.updateHeight()
public func textViewDidChange(_: UITextView)
self.delegate?.placeholderTextViewTextChanged(self, text: self.textView.text)
public func textView(_: UITextView, shouldChangeTextIn _: NSRange,
replacementText text: String) -> Bool
let containsNewLines = text.rangeOfCharacter(from: .newlines)?.isEmpty == .some(false)
guard !containsNewLines || self.allowsNewLines else return false
return true
【讨论】:
【参考方案22】:斯威夫特 3.2
extension EditProfileVC:UITextViewDelegate
func textViewDidBeginEditing(_ textView: UITextView)
if textView.textColor == UIColor.lightGray
textView.text = nil
textView.textColor = UIColor.black
func textViewDidEndEditing(_ textView: UITextView)
if textView.text.isEmpty
textView.text = "Placeholder"
textView.textColor = UIColor.lightGray
首先当用户开始编辑 textViewDidBeginEditing 调用,然后检查文本灰色是否表示用户没有写任何内容,然后设置为 textview nil 并将颜色更改为黑色以供用户发短信。
当调用用户端编辑 textViewDidEndEditing 并检查用户是否在 textview 中没有写任何内容时,文本设置为灰色并带有文本“PlaceHolder”
【讨论】:
看起来是更短、最简单的答案,代码更少【参考方案23】:编辑完成后,我必须调度队列以使占位符文本重新出现。
func textViewDidBeginEditing(_ textView: UITextView)
if textView.text == "Description"
textView.text = nil
func textViewDidEndEditing(_ textView: UITextView)
if textView.text.isEmpty
DispatchQueue.main.async
textView.text = "Description"
【讨论】:
如果我希望这个值成为用户输入的内容,我应该输入什么而不是最后一行“textView.text = ”Description”?【参考方案24】:这是我用来完成这项工作的工具。
@IBDesignable class UIPlaceholderTextView: UITextView
var placeholderLabel: UILabel?
override init(frame: CGRect, textContainer: NSTextContainer?)
super.init(frame: frame, textContainer: textContainer)
sharedInit()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
sharedInit()
override func prepareForInterfaceBuilder()
sharedInit()
func sharedInit()
refreshPlaceholder()
NotificationCenter.default.addObserver(self, selector: #selector(textChanged), name: UITextView.textDidChangeNotification, object: nil)
@IBInspectable var placeholder: String?
didSet
refreshPlaceholder()
@IBInspectable var placeholderColor: UIColor? = .darkGray
didSet
refreshPlaceholder()
@IBInspectable var placeholderFontSize: CGFloat = 14
didSet
refreshPlaceholder()
func refreshPlaceholder()
if placeholderLabel == nil
placeholderLabel = UILabel()
let contentView = self.subviews.first ?? self
contentView.addSubview(placeholderLabel!)
placeholderLabel?.translatesAutoresizingMaskIntoConstraints = false
placeholderLabel?.leftAnchor.constraint(equalTo: contentView.leftAnchor, constant: textContainerInset.left + 4).isActive = true
placeholderLabel?.rightAnchor.constraint(equalTo: contentView.rightAnchor, constant: textContainerInset.right + 4).isActive = true
placeholderLabel?.topAnchor.constraint(equalTo: contentView.topAnchor, constant: textContainerInset.top).isActive = true
placeholderLabel?.bottomAnchor.constraint(lessThanOrEqualTo: contentView.bottomAnchor, constant: textContainerInset.bottom).isActive = true
placeholderLabel?.text = placeholder
placeholderLabel?.textColor = placeholderColor
placeholderLabel?.font = UIFont.systemFont(ofSize: placeholderFontSize)
@objc func textChanged()
if self.placeholder?.isEmpty ?? true
return
UIView.animate(withDuration: 0.25)
if self.text.isEmpty
self.placeholderLabel?.alpha = 1.0
else
self.placeholderLabel?.alpha = 0.0
override var text: String!
didSet
textChanged()
我知道有几种类似的方法,但这种方法的好处是它可以:
在IB中设置占位符文本、字体大小和颜色。 IB 中不再显示“Scroll View has ambiguous scrollable content”的警告。 添加动画以显示/隐藏占位符。【讨论】:
在哪里移除观察者? 你是什么观察者? 每一行NotificationCenter.default.addObserver
必须有一个计数器行与NotificationCenter.default.removeObserver
从 iOS 9 及更高版本开始,您无需显式删除观察者:developer.apple.com/documentation/foundation/notificationcenter/…【参考方案25】:
我很惊讶没有人提到NSTextStorageDelegate
。 UITextViewDelegate
的方法只会由用户交互触发,而不会以编程方式触发。例如。当您以编程方式设置文本视图的 text
属性时,您必须自己设置占位符的可见性,因为不会调用委托方法。
但是,使用NSTextStorageDelegate
的textStorage(_:didProcessEditing:range:changeInLength:)
方法,您将收到对文本的任何更改的通知,即使它是以编程方式完成的。只需像这样分配它:
textView.textStorage.delegate = self
(在UITextView
中,这个委托属性默认为nil
,所以它不会影响任何默认行为。)
将它与@clearlight 演示的UILabel
技术相结合,可以轻松地将整个UITextView
的placeholder
实现包装到一个扩展中。
extension UITextView
private class PlaceholderLabel: UILabel
private var placeholderLabel: PlaceholderLabel
if let label = subviews.compactMap( $0 as? PlaceholderLabel ).first
return label
else
let label = PlaceholderLabel(frame: .zero)
label.font = font
addSubview(label)
return label
@IBInspectable
var placeholder: String
get
return subviews.compactMap( $0 as? PlaceholderLabel ).first?.text ?? ""
set
let placeholderLabel = self.placeholderLabel
placeholderLabel.text = newValue
placeholderLabel.numberOfLines = 0
let width = frame.width - textContainer.lineFragmentPadding * 2
let size = placeholderLabel.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
placeholderLabel.frame.size.height = size.height
placeholderLabel.frame.size.width = width
placeholderLabel.frame.origin = CGPoint(x: textContainer.lineFragmentPadding, y: textContainerInset.top)
textStorage.delegate = self
extension UITextView: NSTextStorageDelegate
public func textStorage(_ textStorage: NSTextStorage, didProcessEditing editedMask: NSTextStorageEditActions, range editedRange: NSRange, changeInLength delta: Int)
if editedMask.contains(.editedCharacters)
placeholderLabel.isHidden = !text.isEmpty
请注意,使用名为PlaceholderLabel
的私有(嵌套)类。它根本没有实现,但它为我们提供了一种识别占位符标签的方法,这比使用tag
属性要“迅速”得多。
使用这种方法,您仍然可以将UITextView
的代表分配给其他人。
您甚至不必更改文本视图的类。只需添加扩展名,您就可以为项目中的每个 UITextView
分配一个占位符字符串,即使在 Interface Builder 中也是如此。
为了清楚起见,我省略了 placeholderColor
属性的实现,但它可以通过与 placeholder
类似的计算变量再多几行来实现。
【讨论】:
非常优雅的解决方案。我喜欢它。textView.textStorage.delegate = self
视图控制器中的 this 将要求我们将该视图控制器与 NSTextStorageDelegate
绑定。真的需要吗?
简单,就像一个魅力。这是必须接受的答案
@Hemang 是文本视图本身是NSTextStorageDelegate
,而不是视图控制器。【参考方案26】:
斯威夫特:
添加你的TextView
@IBOutlet
:
@IBOutlet weak var txtViewMessage: UITextView!
在viewWillAppear
方法中,添加以下内容:
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
txtViewMessage.delegate = self // Give TextViewMessage delegate Method
txtViewMessage.text = "Place Holder Name"
txtViewMessage.textColor = UIColor.lightGray
请添加Delegate
使用扩展名(UITextViewDelegate):
// MARK: - UITextViewDelegate
extension ViewController: UITextViewDelegate
func textViewDidBeginEditing(_ textView: UITextView)
if !txtViewMessage.text!.isEmpty && txtViewMessage.text! == "Place Holder Name"
txtViewMessage.text = ""
txtViewMessage.textColor = UIColor.black
func textViewDidEndEditing(_ textView: UITextView)
if txtViewMessage.text.isEmpty
txtViewMessage.text = "Place Holder Name"
txtViewMessage.textColor = UIColor.lightGray
【讨论】:
【参考方案27】:对我有用的简单快速的解决方案是:
@IBDesignable
class PlaceHolderTextView: UITextView
@IBInspectable var placeholder: String = ""
didSet
updatePlaceHolder()
@IBInspectable var placeholderColor: UIColor = UIColor.gray
didSet
updatePlaceHolder()
private var originalTextColor = UIColor.darkText
private var originalText: String = ""
private func updatePlaceHolder()
if self.text == "" || self.text == placeholder
self.text = placeholder
self.textColor = placeholderColor
if let color = self.textColor
self.originalTextColor = color
self.originalText = ""
else
self.textColor = self.originalTextColor
self.originalText = self.text
override func becomeFirstResponder() -> Bool
let result = super.becomeFirstResponder()
self.text = self.originalText
self.textColor = self.originalTextColor
return result
override func resignFirstResponder() -> Bool
let result = super.resignFirstResponder()
updatePlaceHolder()
return result
【讨论】:
【参考方案28】:另一个解决方案可能是像我一样使用keyboardWillHide 和keyboardWillShow 通知。
首先,您需要在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
我发现这个解决方案是迄今为止最好的解决方案,因为文本将在键盘出现时被删除,而不是在用户开始输入时被删除,这可能会导致混淆。
【讨论】:
【参考方案29】:var placeholderLabel : UILabel!
textviewDescription.delegate = self
placeholderLabel = UILabel()
placeholderLabel.text = "Add a description"
func textViewDidChange(_ textView: UITextView)
placeholderLabel.isHidden = !textviewDescription.text.isEmpty
【讨论】:
Textview 占位符在 textview 中仅使用标签作为占位符并隐藏占位符以在 textview 中输入文本【参考方案30】:这是我解决这个问题的方法(Swift 4):
我们的想法是制作最简单的解决方案,允许使用不同颜色的占位符,调整占位符大小,不会覆盖delegate
,同时保持所有UITextView
函数按预期工作。
import UIKit
class PlaceholderTextView: UITextView
var placeholderColor: UIColor = .lightGray
var defaultTextColor: UIColor = .black
private var isShowingPlaceholder = false
didSet
if isShowingPlaceholder
text = placeholder
textColor = placeholderColor
else
textColor = defaultTextColor
var placeholder: String?
didSet
isShowingPlaceholder = !hasText
@objc private func textViewDidBeginEditing(notification: Notification)
textColor = defaultTextColor
if isShowingPlaceholder text = nil
@objc private func textViewDidEndEditing(notification: Notification)
isShowingPlaceholder = !hasText
// MARK: - Construction -
override init(frame: CGRect, textContainer: NSTextContainer?)
super.init(frame: frame, textContainer: textContainer)
setup()
required init?(coder aDecoder: NSCoder)
super.init(coder: aDecoder)
setup()
private func setup()
NotificationCenter.default.addObserver(self, selector: #selector(textViewDidBeginEditing(notification:)), name: UITextView.textDidBeginEditingNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(textViewDidEndEditing(notification:)), name: UITextView.textDidEndEditingNotification, object: nil)
// MARK: - Destruction -
deinit NotificationCenter.default.removeObserver(self)
【讨论】:
这是一个伟大而简单的解决方案。以上是关于在 Swift 中的 UITextView 中添加占位符文本?的主要内容,如果未能解决你的问题,请参考以下文章
如何防止用户在 Swift 5 的 UITextView 中添加太多换行符?
如何在 Swift 3 中将 NSConstraints 添加到 UITextView