Имитация родных заполнителей
Распространенным недостатком является то, что iOS не предоставляет встроенную функцию заполнителя для текстовых представлений. Расширение UITextView
, приведенное ниже, пытается решить эту проблему, предлагая удобство, которое можно ожидать от нативной функции, требующей только одну строку кода для добавления заполнителя к экземпляру textview.
Недостатком этого решения является то, что он последовательно соединяет вызовы делегатов, он уязвим (маловероятно) к изменениям протокола UITextViewDelegate в обновлении iOS. В частности, если 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, важно знать, что этот код не взаимодействует с любыми текстовыми представлениями, которые не t активировать заполнитель, например, экземпляры textview, которые не были инициализированы вызовом addPlaceholder()
2. Текстовые представления с поддержкой заполнителей прозрачно становятся UITextViewDelegate
для отслеживания количества символов, чтобы контролировать заполнитель видимость. Если делегат передается в addPlaceholder()
, этот последовательный код (то есть переадресация) делегирует обратные вызовы этому делегату.
3. Автор исследует способы проверки протокола UITextViewDelegate
и прокси его автоматически без необходимости жестко кодировать каждый метод. Это приведет к изменению кода от изменений сигнатур методов и новых методов, добавляемых в протокол.