Как отловить изменение текста в UITextField: лучшие практики 2025

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение: Почему "поймать" изменение текста — это база?

Представьте: вы пишете приложение для поиска рецептов. Пользователь начинает вводить "паста", а ваш поиск должен срабатывать мгновенно, без нажатия кнопки. Или вы делаете форму регистрации, где нужно подсвечивать ошибку "пароль слишком короткий" сразу, как только пользователь дописал третью букву.

В обоих случаях вам нужно знать точный момент, когда изменился текст в UITextField. Этот вопрос на Stack Overflow собрал тысячи просмотров — и не зря. Каждый iOS-разработчик рано или поздно с ним сталкивается.

В 2025 году у нас есть как минимум 4 способа решить эту задачу. Одни — старожилы, прошедшие проверку временем. Другие — свежие подходы, которые Apple активно продвигает. В этой статье мы разберем все варианты, от классики до модерна, и я покажу, какой способ выбрать под конкретную задачу.

Поехали! 🚀

Способ 1. Target-Action: Дедовский, но рабочий

Самый простой и понятный метод, который работает с iOS 2.0. Да-да, ему больше 15 лет, но он до сих пор актуален.

Идея в том, что UITextField наследуется от UIControl, а значит, умеет генерировать события. Нас интересует событие .editingChanged — оно срабатывает при каждом изменении текста.

Пример кода (UIKit + Storyboard или кодом)

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var searchTextField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        // Добавляем цель для события изменения текста
        searchTextField.addTarget(self,
                                  action: #selector(textFieldDidChange(_:)),
                                  for: .editingChanged)
    }

    @objc func textFieldDidChange(_ textField: UITextField) {
        guard let text = textField.text else { return }
        print("Текст изменился: \(text)")
        // Здесь можно делать поиск, валидацию и т.д.
    }
}

Плюсы: минимум кода, не требует подписывания на протоколы, работает быстро.

Минусы: нужно использовать @objc — это legacy Swift. Нет тонкого контроля (например, нельзя отличить вставку текста от удаления).

Способ 2. Делегат UITextFieldDelegate: Контроль над каждым символом

Если вам нужно не просто узнать, что текст изменился, а повлиять на изменение (заблокировать ввод, отформатировать номер телефона), то делегат — ваш выбор.

Здесь ключевой метод — textField(_:shouldChangeCharactersIn:replacementString:). Он вызывается до того, как текст реально изменится.

Пример: форматирование номера телефона

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet weak var phoneTextField: UITextField!

    override func viewDidLoad() {
        super.viewDidLoad()
        phoneTextField.delegate = self
    }

    func textField(_ textField: UITextField,
                   shouldChangeCharactersIn range: NSRange,
                   replacementString string: String) -> Bool {

        // Разрешаем только цифры
        let allowedCharacters = CharacterSet.decimalDigits
        let characterSet = CharacterSet(charactersIn: string)
        guard allowedCharacters.isSuperset(of: characterSet) else {
            return false
        }

        // Ограничим длину 10 символами
        let currentText = textField.text ?? ""
        guard let stringRange = Range(range, in: currentText) else { return false }
        let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
        return updatedText.count <= 10
    }

    // Дополнительно: ловим момент, когда текст уже изменился
    func textFieldDidChangeSelection(_ textField: UITextField) {
        print("Текст обновлен: \(textField.text ?? "")")
    }
}

Важно! В iOS 13+ появился метод textFieldDidChangeSelection — он срабатывает после изменения текста и выделения. Это более "чистый" аналог .editingChanged, но уже в протоколе делегата.

Плюсы: максимальный контроль, можно валидировать в реальном времени.

Минусы: нужно не забыть установить delegate, код становится объемнее.

Способ 3. Combine: Реактивный подход для iOS 13+

Если вы пишете современное приложение и используете Combine, то забудьте про делегаты. Apple предоставляет publisher для NotificationCenter, который идеально подходит для этой задачи.

Пример: реактивный поиск с debounce

import UIKit
import Combine

class SearchViewController: UIViewController {

    @IBOutlet weak var searchField: UITextField!
    private var cancellables = Set<AnyCancellable>()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Создаем publisher на изменение текста
        NotificationCenter.default.publisher(for: UITextField.textDidChangeNotification, object: searchField)
            .compactMap { ($0.object as? UITextField)?.text }
            .debounce(for: .milliseconds(300), scheduler: RunLoop.main) // ждем 0.3 сек после последнего ввода
            .removeDuplicates() // не шлем запрос, если текст не изменился
            .sink { [weak self] searchText in
                print("Поиск: \(searchText)")
                // Отправляем запрос на сервер
                self?.performSearch(with: searchText)
            }
            .store(in: &cancellables)
    }

    private func performSearch(with query: String) {
        // Ваша логика поиска
    }
}

Обратите внимание на debounce — это "золотая пуля" для поисковых строк. Пока пользователь печатает, запросы не отправляются. Как только он делает паузу в 300 мс — запрос уходит. Это спасает и сервер, и батарею.

Плюсы: декларативный стиль, легко комбинировать с другими publisher, встроенная обработка задержек и фильтрация.

Минусы: требует iOS 13+, Combine — отдельная парадигма, не все разработчики с ней знакомы.

Способ 4. SwiftUI: onChange и @Binding (для справки)

Хотя статья про UIKit, нельзя игнорировать SwiftUI. Если вы используете SwiftUI, то все еще проще:

import SwiftUI

struct SearchView: View {
    @State private var searchText = ""

    var body: some View {
        TextField("Введите запрос...", text: $searchText)
            .onChange(of: searchText) { oldValue, newValue in
                print("Текст изменился с \(oldValue) на \(newValue)")
                // Ваша логика
            }
    }
}

В SwiftUI 5 (iOS 17+) у onChange появились два параметра — старое и новое значение. Это удобно для сравнения.

Плюсы: минималистично, SwiftUI-native.

Минусы: работает только в SwiftUI, не подходит для UIKit проектов.

Сравнительная таблица: Какой способ выбрать?

Блоги

Рекомендаций по книгам