Avatar ·

Как отследить изменение текста в UITextField в Swift? Событие text change

Привет! Я новичок в iOS разработке на Swift и столкнулся с проблемой. Мне нужно в реальном времени отслеживать, когда пользователь вводит или удаляет текст в UITextField, и выполнять какое-то действие (например, проверять длину введённого текста или обновлять интерфейс). Я пробовал использовать делегат UITextFieldDelegate и метод textFieldDidChangeSelection, но он не вызывается, когда я просто печатаю. Вот мой код: ```swift class ViewController: UIViewController, UITextFieldDelegate { @IBOutlet weak var myTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() myTextField.delegate = self } func textFieldDidChangeSelection(_ textField: UITextField) { print("Текст изменился: \(textField.text ?? "")") } } ``` Этот метод вызывается только когда я выделяю текст или перемещаю курсор, но не при обычном вводе символов. Я также пробовал метод textField(_:shouldChangeCharactersIn:replacementString:), но он вызывается до того, как текст реально изменится, и в textField.text ещё старые данные. Мне нужно именно после изменения. Подскажите, как правильно подписаться на событие изменения текста, чтобы вызывать обработчик при каждом вводе или удалении символа? Желательно на Swift, без Objective-C. Заранее спасибо! <<>>

Решение: Используйте событие UIControl.Event.editingChanged

Проблема в том, что вы полагаетесь на делегат, но для отслеживания изменений текста в реальном времени правильный способ — подписаться на событие .editingChanged от UIControl. UITextField наследуется от UIControl, поэтому вы можете использовать Target-Action механизм.

Вот как это делается:

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var myTextField: UITextField!
    @IBOutlet weak var characterCountLabel: UILabel!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Подписываемся на событие изменения текста
        myTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
    }
    
    @objc func textFieldDidChange(_ textField: UITextField) {
        // Здесь textField.text уже содержит актуальное значение
        guard let text = textField.text else { return }
        
        print("Текст изменился: \(text)")
        
        // Пример: обновляем счётчик символов
        characterCountLabel.text = "\(text.count) символов"
        
        // Пример: проверка минимальной длины
        if text.count >= 3 {
            print("Достаточно символов")
        }
    }
}

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

  • .editingChanged срабатывает каждый раз, когда содержимое текстового поля изменяется: при вводе, удалении, вставке из буфера обмена или при использовании автозамены.
  • В момент вызова метода текст уже обновлён, поэтому textField.text содержит актуальное значение.
  • Не нужно устанавливать делегат — это отдельный механизм, не конфликтующий с делегатом.

Если вам нужен и делегат, и событие

Вы можете использовать оба подхода одновременно. Например, делегат для валидации ввода (запрет определённых символов), а событие — для реакции на изменения:

class ViewController: UIViewController, UITextFieldDelegate {
    @IBOutlet weak var myTextField: UITextField!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        myTextField.delegate = self
        myTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
    }
    
    // Делегат: запрещаем ввод букв
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let allowedCharacters = CharacterSet.decimalDigits
        let characterSet = CharacterSet(charactersIn: string)
        return allowedCharacters.isSuperset(of: characterSet)
    }
    
    // Событие: реагируем на изменения
    @objc func textFieldDidChange(_ textField: UITextField) {
        print("Текущий текст: \(textField.text ?? "")")
    }
}

Дополнительные советы

  • Не забудьте добавить @objc перед методом, если используете Swift 4+ (для совместимости с Objective-C runtime).
  • Если вы создаёте текстовое поле программно, подключите событие после добавления на экран.
  • Для отписки от события используйте removeTarget, но обычно это не требуется, если контроллер живёт вместе с текстовым полем.

Это стандартный и рекомендуемый Apple подход для отслеживания изменений текста в UITextField. Используйте его в своих проектах!

<<>>

Альтернативный подход: Использование Combine (iOS 13+)

Если ваш проект поддерживает iOS 13 или новее, можно использовать реактивный подход с фреймворком Combine. Это особенно удобно, если вы уже используете Combine в проекте или хотите избежать селекторов и @objc:

import UIKit
import Combine

class ViewController: UIViewController {
    @IBOutlet weak var myTextField: UITextField!
    private var cancellables = Set<AnyCancellable>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Подписываемся на Notification от UITextField
        NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: myTextField)
            .compactMap { ($0.object as? UITextField)?.text }
            .sink { text in
                print("Текст изменился: \(text)")
                // Ваша логика здесь
            }
            .store(in: &cancellables)
    }
}

Этот метод использует системное уведомление UITextField.textDidChangeNotification, которое отправляется каждый раз после изменения текста. Преимущество — чистый код без @objc и делегатов, а также возможность легко комбинировать с другими publisher'ами (например, фильтровать или задерживать обработку с помощью Debounce).

Недостаток: требует импорта Combine и поддержки iOS 13+. Для старых версий используйте первый способ с addTarget.