模拟原生占位符
一个常见的抱怨是 iOS 没有为文本视图提供原生占位符功能。下面的 UITextView 扩展试图通过提供人们期望从本机功能获得的便利来解决这个问题,只需要 一行 代码来将占位符添加到 textview 实例。
这个解决方案的缺点是,因为它菊花链委托调用,它很容易受到(不太可能)在 iOS 更新中对 UITextViewDelegate 协议的更改。具体来说,如果 iOS 添加了新的协议方法,并且您在代理中为带有占位符的文本视图实现了其中的任何方法,则不会调用这些方法,除非您也更新了扩展以转发这些方法来电。
或者,Inline Placeholder 的答案是坚如磐石,并且尽可能简单。
用法示例:
• 如果获得占位符的文本视图不使用UITextViewDelegate:
/* Swift 3 */
class NoteViewController : UIViewController {
@IBOutlet weak var noteView: UITextView!
override func viewDidLoad() {
noteView.addPlaceholder("Enter some text...", color: UIColor.lightGray)
}
}
-- 或者--
• 如果获得占位符的文本视图确实使用UITextViewDelegate:
/* Swift 3 */
class NoteViewController : UIViewController, UITextViewDelegate {
@IBOutlet weak var noteView: UITextView!
override func viewDidLoad() {
noteView.addPlaceholder("Phone #", color: UIColor.lightGray, delegate: self)
}
}
实现(UITextView 扩展):
/* Swift 3 */
extension UITextView: UITextViewDelegate
{
func addPlaceholder(_ placeholderText : String,
color : UIColor? = UIColor.lightGray,
delegate : UITextViewDelegate? = nil) {
self.delegate = self // Make receiving textview instance a delegate
let placeholder = UITextView() // Need delegate storage ULabel doesn't provide
placeholder.isUserInteractionEnabled = false //... so we *simulate* UILabel
self.addSubview(placeholder) // Add to text view instance's view tree
placeholder.sizeToFit() // Constrain to fit inside parent text view
placeholder.tintColor = UIColor.clear // Unused in textviews. Can host our 'tag'
placeholder.frame.origin = CGPoint(x: 5, y: 0) // Don't cover I-beam cursor
placeholder.delegate = delegate // Use as cache for caller's delegate
placeholder.font = UIFont.italicSystemFont(ofSize: (self.font?.pointSize)!)
placeholder.text = placeholderText
placeholder.textColor = color
}
func findPlaceholder() -> UITextView? { // find placeholder by its tag
for subview in self.subviews {
if let textview = subview as? UITextView {
if textview.tintColor == UIColor.clear { // sneaky tagging scheme
return textview
}
}
}
return nil
}
/*
* Safely daisychain to caller delegate methods as appropriate...
*/
public func textViewDidChange(_ textView: UITextView) { // ← need this delegate method
if let placeholder = findPlaceholder() {
placeholder.isHidden = !self.text.isEmpty // ← ... to do this
placeholder.delegate?.textViewDidChange?(textView)
}
}
/*
* Since we're becoming a delegate on behalf of this placeholder-enabled
* text view instance, we must forward *all* that protocol's activity expected
* by the instance, not just the particular optional protocol method we need to
* intercept, above.
*/
public func textViewDidEndEditing(_ textView: UITextView) {
if let placeholder = findPlaceholder() {
placeholder.delegate?.textViewDidEndEditing?(textView)
}
}
public func textViewDidBeginEditing(_ textView: UITextView) {
if let placeholder = findPlaceholder() {
placeholder.delegate?.textViewDidBeginEditing?(textView)
}
}
public func textViewDidChangeSelection(_ textView: UITextView) {
if let placeholder = findPlaceholder() {
placeholder.delegate?.textViewDidChangeSelection?(textView)
}
}
public func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
if let placeholder = findPlaceholder() {
guard let retval = placeholder.delegate?.textViewShouldEndEditing?(textView) else {
return true
}
return retval
}
return true
}
public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool {
if let placeholder = findPlaceholder() {
guard let retval = placeholder.delegate?.textViewShouldBeginEditing?(textView) else {
return true
}
return retval
}
return true
}
public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
if let placeholder = findPlaceholder() {
guard let retval = placeholder.delegate?.textView?(textView, shouldChangeTextIn: range, replacementText: text) else {
return true
}
return retval
}
return true
}
public func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if let placeholder = findPlaceholder() {
guard let retval = placeholder.delegate?.textView?(textView, shouldInteractWith: URL, in: characterRange, interaction:
interaction) else {
return true
}
return retval
}
return true
}
public func textView(_ textView: UITextView, shouldInteractWith textAttachment: NSTextAttachment, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
if let placeholder = findPlaceholder() {
guard let retval = placeholder.delegate?.textView?(textView, shouldInteractWith: textAttachment, in: characterRange, interaction: interaction) else {
return true
}
return retval
}
return true
}
}
1. 作为基本 iOS 类(如 UITextView)的扩展,重要的是要知道这段代码与任何文本视图没有交互不激活占位符,例如尚未通过调用 addPlaceholder()
初始化的 textview 实例
2. 启用占位符的文本视图透明地变为 UITextViewDelegate 以跟踪字符数,在为了控制占位符的可见性。如果将委托传递给 addPlaceholder(),则此代码菊花链(即转发)将回调委托给该委托。
3.作者正在研究检查UITextViewDelegate协议并自动代理它的方法,而无需必须对每种方法进行硬编码。这将使代码免受方法签名更改和添加到协议中的新方法的影响。