UITextField text change event: каждый символ на счету
Представьте: вы пишете приложение для заметок, поиск по каталогу или мессенджер. Пользователь только начинает печатать в UITextField, а вам уже нужно реагировать — подсвечивать подсказки, считать оставшиеся символы или отправлять запрос на сервер. Как поймать этот самый момент, когда текст изменился? Вопрос, который мучает новичков и даже опытных iOS-разработчиков. На Stack Overflow тема UITextField text change event собрала тысячи просмотров — и неспроста.
В iOS разработке есть как минимум три рабочих подхода. И выбор неправильного может стоить вам багов, утечки памяти или лишних 100 строк кода. Сегодня мы разложим по полочкам все способы отследить изменение текста в UITextField: от классического делегата до современного Combine. Поехали!
Почему недостаточно просто нажать кнопку?
Казалось бы, что сложного? Пользователь ввел текст — нажал «Готово» или кнопку «Поиск». Но современные интерфейсы требуют реакции в реальном времени. Вот типичные сценарии:
- Автодополнение (search as you type) — подсказки появляются при каждом нажатии клавиши.
- Валидация на лету — проверка email или номера телефона без отправки формы.
- Счетчик символов — например, в Twitter-клиенте нужно показывать, сколько букв осталось до лимита.
- Динамический поиск — фильтрация списка контактов или товаров.
Во всех этих случаях нам нужно событие UITextField text change event. И теперь вопрос: как его правильно реализовать в Swift?
Способ 1: Делегат UITextFieldDelegate — классика жанра
Самый очевидный и, пожалуй, самый популярный метод. Вы назначаете делегата своему текстовому полю и реализуете метод textField(_:shouldChangeCharactersIn:replacementString:). Но есть нюанс: этот метод вызывается до того, как текст реально изменится. Он спрашивает: «Можно ли вставить этот символ?»
Чтобы получить актуальный текст, нужно сконструировать его вручную:
class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentText = textField.text ?? ""
guard let stringRange = Range(range, in: currentText) else { return false }
let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
print("Новый текст: \(updatedText)")
// Здесь можно обновить UI или запустить поиск
return true
}
}
Плюсы:
- Полный контроль над вводом — можно запретить вставку определенных символов.
- Работает на всех версиях iOS.
Минусы:
- Код громоздкий для простого «поймать изменение».
- Нужно помнить про
NSRangeи конвертацию вRange<String.Index>. - Не срабатывает при программном изменении
textField.text = "новый текст".
Этот подход отлично подходит, если вам нужно валидировать ввод (например, запретить буквы в поле для номера телефона). Но для простого отслеживания изменений есть более элегантные варианты.
Способ 2: Target-Action (UIControl.Event.editingChanged) — простота и надежность
Второй способ — это добавление цели и действия для события .editingChanged. Он срабатывает после каждого изменения текста, и код выглядит лаконично:
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
}
@objc func textFieldDidChange(_ textField: UITextField) {
guard let text = textField.text else { return }
print("Текст изменился: \(text)")
// Обновляем поиск, счетчик и т.д.
}
}
Можно сделать даже проще — подключить @IBAction прямо из Storyboard:
- Откройте Assistant Editor.
- Зажмите Ctrl и перетащите из Value Changed события текстового поля в код.
- Выберите тип
UITextFieldи назовите метод.
И вуаля — код сгенерируется автоматически.
Плюсы:
- Минимум кода, легко читается.
- Не нужно возиться с делегатами и
NSRange. - Срабатывает при любом изменении, включая программное (если вызвать
sendActions(for: .editingChanged)).
Минусы:
- Не дает отменить ввод (не подходит для валидации).
- Требует
@objcаннотации.
Этот способ — золотая середина для 80% задач. Если вам нужно просто узнать, что UITextField text change event произошел — используйте .editingChanged.
Способ 3: Уведомления (NotificationCenter) — для сложной архитектуры
Иногда один контроллер должен знать об изменениях в другом. Или вам нужно реагировать на изменения текста в нескольких полях сразу. Тут на помощь приходит UITextField.textDidChangeNotification:
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(textDidChange(_:)),
name: UITextField.textDidChangeNotification,
object: textField
)
}
@objc func textDidChange(_ notification: Notification) {
guard let textField = notification.object as? UITextField,
let text = textField.text else { return }
print("Уведомление: текст = \(text)")
}
deinit {
NotificationCenter.default.removeObserver(self)
}
}
Плюсы:
- Развязывает код — можно подписаться из любого места.
- Удобно для глобальных обработчиков.
Минусы:
- Нужно не забыть отписаться (
deinit), иначе — утечка памяти. - Меньше контроля, чем у делегата.
Используйте уведомления, если вы строите архитектуру на MVVM или Coordinator, где ViewModel не должна иметь