Как отслеживать изменения текста в UITextField в Swift?
Решение через target-action (рекомендуемый способ)
Самый надёжный и простой способ отслеживать изменения текста в UITextField — использовать событие .editingChanged. Этот метод срабатывает каждый раз, когда пользователь вводит или удаляет символ, и не требует реализации делегата. Вот как это сделать:
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var submitButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Добавляем target-action для события editingChanged
textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
// Начальное состояние кнопки
updateSubmitButtonState()
}
@objc func textFieldDidChange(_ textField: UITextField) {
updateSubmitButtonState()
}
private func updateSubmitButtonState() {
if let text = textField.text, !text.isEmpty {
submitButton.isEnabled = true
} else {
submitButton.isEnabled = false
}
}
}
Почему это работает лучше, чем делегат?
- Мгновенная реакция: Событие
.editingChangedсрабатывает сразу при каждом изменении текста, включая вставку, удаление и замену. - Простота: Не нужно подписываться на делегат или реализовывать его методы.
- Гибкость: Вы можете добавить несколько target-action для одного поля (например, для разных действий).
Альтернатива: Использование Combine (iOS 13+)
Если ваш проект поддерживает iOS 13 и выше, можно использовать фреймворк Combine для реактивного отслеживания:
import Combine
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var submitButton: UIButton!
private var cancellables = Set<AnyCancellable>()
override func viewDidLoad() {
super.viewDidLoad()
// Подписываемся на изменения текста через NotificationCenter
NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: textField)
.map { ($0.object as? UITextField)?.text ?? "" }
.sink { [weak self] text in
self?.submitButton.isEnabled = !text.isEmpty
}
.store(in: &cancellables)
}
}
Почему ваш код с делегатом не работал?
Метод textFieldDidChangeSelection действительно вызывается при изменении выделения, а не при изменении текста. Для отслеживания изменений текста через делегат нужно использовать textField(_:shouldChangeCharactersIn:replacementString:):
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Вызываем обновление после изменения текста
DispatchQueue.main.async {
self.updateSubmitButtonState()
}
return true // Разрешаем изменение
}
Но этот метод менее надёжен, так как он вызывается до фактического изменения текста, и вам приходится предсказывать результат. Поэтому рекомендую использовать target-action с событием .editingChanged — это самый простой и эффективный подход.
Дополнительные советы
- Проверяйте не только пустоту, но и пробелы:
text.trimmingCharacters(in: .whitespaces).isEmpty - Для сложной валидации (например, email) используйте регулярные выражения внутри обработчика.
- Не забывайте про слабые ссылки (
[weak self]) в замыканиях, чтобы избежать утечек памяти.
Решение через делегат с правильным методом
Если вы предпочитаете использовать делегат (например, в проекте, где уже есть другие методы делегата), то правильным методом будет textField(_:shouldChangeCharactersIn:replacementString:). Вот как это реализовать:
class ViewController: UIViewController, UITextFieldDelegate {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var submitButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
updateSubmitButtonState() // Начальное состояние
}
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)
// Обновляем кнопку
submitButton.isEnabled = !updatedText.isEmpty
return true // Разрешаем изменение
}
private func updateSubmitButtonState() {
submitButton.isEnabled = !(textField.text?.isEmpty ?? true)
}
}
Когда это может быть полезно?
- Когда вам нужно отменить ввод определённых символов (например, запретить цифры).
- Когда вы уже используете делегат для других целей (например, обработка нажатия Return).
Обратите внимание: этот метод вызывается до фактического изменения текста, поэтому мы сами вычисляем новый текст. Это даёт больше контроля, но требует аккуратности. Для простого отслеживания изменений лучше использовать target-action, как описано в первом ответе.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги