Avatar ·

Как отследить изменение текста в UITextField в реальном времени (Swift)

Всем привет! Я новичок в iOS разработке на Swift и столкнулся с проблемой, которую не могу решить уже несколько часов. У меня есть простой экран с UITextField для ввода email. Мне нужно, чтобы при каждом изменении текста (вводе или удалении символа) выполнялась валидация email и показывалась иконка (галочка или крестик) рядом с полем. Я хочу, чтобы это происходило в реальном времени, а не после того, как пользователь закончит ввод и нажмет "Done". Я пробовал использовать делегат UITextFieldDelegate и метод textFieldDidEndEditing, но он срабатывает только когда поле теряет фокус. Также пробовал textFieldShouldReturn — это вообще не то. Вот мой текущий код, который не работает так, как нужно: ```swift class ViewController: UIViewController, UITextFieldDelegate { @IBOutlet weak var emailTextField: UITextField! @IBOutlet weak var validationIcon: UIImageView! override func viewDidLoad() { super.viewDidLoad() emailTextField.delegate = self } func textFieldDidEndEditing(_ textField: UITextField) { validateEmail() } func validateEmail() { // Простейшая проверка if let email = emailTextField.text, email.contains("@") { validationIcon.image = UIImage(systemName: "checkmark.circle.fill") validationIcon.tintColor = .green } else { validationIcon.image = UIImage(systemName: "xmark.circle.fill") validationIcon.tintColor = .red } } } ``` Как сделать, чтобы validateEmail() вызывалась при каждом изменении текста, а не только когда поле теряет фокус? Я слышал что-то про .editingChanged, но не понимаю, как это подключить. Подскажите правильный способ, пожалуйста! <<>>

Решение через событие UIControl.Event.editingChanged

Вы абсолютно правы — делегатные методы textFieldDidEndEditing и textFieldShouldReturn не подходят для отслеживания изменений в реальном времени. Правильный способ — использовать событие .editingChanged, которое генерируется при каждом изменении текста (вставка, удаление, замена).

Способ 1: Подключение через Interface Builder (рекомендуется)

Самый простой и наглядный способ — подключить событие в Storyboard или XIB:

  1. Откройте ваш Storyboard
  2. Нажмите правой кнопкой мыши на UITextField
  3. В появившемся меню найдите событие Editing Changed
  4. Перетащите соединение к вашему ViewController и выберите метод, который будет вызываться

Способ 2: Программное подключение через target-action

Этот способ даёт больше гибкости и подходит, если вы создаёте UI программно. Вот полный рабочий пример:

class ViewController: UIViewController, UITextFieldDelegate {
    
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var validationIcon: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Настраиваем делегат (оставляем для других методов, если нужно)
        emailTextField.delegate = self
        
        // Главное: добавляем target для события editingChanged
        emailTextField.addTarget(self, 
                                 action: #selector(textFieldDidChange(_:)), 
                                 for: .editingChanged)
    }
    
    // Этот метод будет вызываться при каждом изменении текста
    @objc func textFieldDidChange(_ textField: UITextField) {
        validateEmail()
    }
    
    func validateEmail() {
        guard let email = emailTextField.text else { return }
        
        // Простая, но более надёжная валидация
        if !email.isEmpty && email.contains("@") && email.contains(".") {
            validationIcon.image = UIImage(systemName: "checkmark.circle.fill")
            validationIcon.tintColor = .green
        } else {
            validationIcon.image = UIImage(systemName: "xmark.circle.fill")
            validationIcon.tintColor = .red
        }
    }
    
    // Делегат можно использовать для других целей, например, скрыть клавиатуру
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true
    }
}

Способ 3: Использование уведомлений (для продвинутых случаев)

Если вам нужно отслеживать изменения из разных частей приложения, можно использовать NotificationCenter:

override func viewDidLoad() {
    super.viewDidLoad()
    
    NotificationCenter.default.addObserver(
        self,
        selector: #selector(textDidChangeNotification(_:)),
        name: UITextField.textDidChangeNotification,
        object: emailTextField
    )
}

@objc func textDidChangeNotification(_ notification: Notification) {
    validateEmail()
}

// Не забудьте отписаться в deinit
deinit {
    NotificationCenter.default.removeObserver(self)
}

Почему это работает?

  • .editingChanged — это событие UIControl, которое срабатывает при каждом изменении содержимого текстового поля
  • Оно вызывается как при вводе с клавиатуры, так и при программном изменении textField.text = "..."
  • Не требует, чтобы поле теряло фокус — реакция мгновенная

Используйте второй способ (target-action) как основной — это стандартный и самый читаемый подход в Swift. Удачи!

<<>>

Альтернатива: Combine и Publisher (iOS 13+)

Если вы используете современные версии iOS (13+), можно применить фреймворк Combine для реактивного отслеживания изменений. Это особенно удобно, если вы уже используете Combine в проекте.

import UIKit
import Combine

class ViewController: UIViewController {
    
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var validationIcon: UIImageView!
    
    private var cancellables = Set<AnyCancellable>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Создаём Publisher для изменений текста
        NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: emailTextField)
            .compactMap { $0.object as? UITextField }
            .map { $0.text ?? "" }
            .sink { [weak self] text in
                self?.validateEmail(text)
            }
            .store(in: &cancellables)
    }
    
    private func validateEmail(_ email: String) {
        if !email.isEmpty && email.contains("@") && email.contains(".") {
            validationIcon.image = UIImage(systemName: "checkmark.circle.fill")
            validationIcon.tintColor = .green
        } else {
            validationIcon.image = UIImage(systemName: "xmark.circle.fill")
            validationIcon.tintColor = .red
        }
    }
    
    deinit {
        cancellables.removeAll()
    }
}

Плюсы Combine подхода:

  • Декларативный стиль — легко читать и поддерживать
  • Автоматическое управление памятью через AnyCancellable
  • Можно легко комбинировать с другими Publisher (например, debounce для поиска)

Минусы:

  • Требуется iOS 13+
  • Дополнительная зависимость от Combine (хотя он встроенный)
  • Может быть избыточным для простых случаев

Выбирайте этот способ, если ваш проект уже использует Combine или вы хотите изучить реактивное программирование. Для простых задач лучше подойдёт target-action из первого ответа.