@FocusState — что это, управление фокусом и клавиатурой в SwiftUI

Автор: IT Sectr Опубликовано: 2026-06-26 Время чтения: 9 мин

@FocusState — это property wrapper в SwiftUI, представленный в iOS 15, который позволяет программно управлять фокусом ввода на текстовых полях и других элементах. До его появления разработчикам приходилось использовать UIViewRepresentable для доступа к UIKit-методам becomeFirstResponder и resignFirstResponder. @FocusState решает эту проблему нативным способом: вы связываете свойство с полем через модификатор .focused(), после чего установка или сброс фокуса происходит простым присваиванием значения. По данным Apple Developer Documentation — FocusState (2025), @FocusState поддерживает два режима: Bool для простого управления (фокус есть или нет) и enum для нескольких полей, где каждый case соответствует конкретному полю ввода.

Главное

  • @FocusState — property wrapper для программного управления фокусом в SwiftUI, доступен с iOS 15.
  • Режим Bool — для одного поля используй @FocusState var isFocused: Bool с .focused($isFocused).
  • Режим Enum — для нескольких полей используй enum с протоколом FocusStateValue и .focused($field, equals: .fieldName).
  • Скрытие клавиатуры — установи фокус в nil или false, чтобы скрыть клавиатуру.
  • Автоматический фокус — установи начальное значение в .onAppear, чтобы показать клавиатуру при открытии экрана.

Что такое @FocusState в SwiftUI

@FocusState — это property wrapper, который связывает состояние фокуса с конкретным полем ввода или другим focusable-элементом в SwiftUI. В отличие от UIKit, где управление фокусом происходит через методы becomeFirstResponder и resignFirstResponder, SwiftUI использует декларативный подход: вы объявляете состояние (@FocusState) и связываете его с элементом через модификатор .focused(). Изменение состояния автоматически меняет фокус.

До появления @FocusState в iOS 15 разработчикам приходилось создавать UIViewRepresentable обёртки вокруг UITextField или использовать сторонние библиотеки. @FocusState интегрирован непосредственно в SwiftUI и работает с TextField, TextEditor, SecureField и SearchField. Это делает код чище, уменьшает количество UIKit-мостиков и улучшает тестируемость.

По данным WWDC Session 10136 — What's new in SwiftUI (2024), @FocusState использует систему preference-ключей SwiftUI для передачи информации о фокусе между элементами. Когда поле получает фокус, SwiftUI автоматически обновляет связанное @FocusState свойство, что позволяет реагировать на изменения фокуса в коде.

Управление фокусом с Bool

Простейший способ использования @FocusState — тип Bool. Когда поле в фокусе, свойство равно true. Когда фокус уходит — false. Вы можете принудительно установить фокус, присвоив true, или сбросить его, присвоив false.

swift
struct LoginForm: View {
    @State var email = ""
    @FocusState var isEmailFocused: Bool
    
    var body: some View {
        VStack {
            TextField("Email", text: $email)
                .focused($isEmailFocused)
            
            Button("Show Keyboard") {
                isEmailFocused = true
            }
            Button("Hide Keyboard") {
                isEmailFocused = false
            }
        }
    }
}

В этом примере isEmailFocused автоматически становится true, когда пользователь тапает на текстовое поле, и false, когда клавиатура скрывается. Кнопки позволяют управлять фокусом программно — это полезно для кастомных клавиатур, кнопок "Далее" и ситуаций, когда нужно принудительно скрыть клавиатуру после отправки формы.

Управление фокусом с Enum для нескольких полей

Для форм с несколькими полями @FocusState поддерживает enum, соответствующий протоколу FocusStateValue (или Hashable). Каждый case enum соответствует конкретному полю. Это позволяет переключать фокус между полями — например, при нажатии "Next" на клавиатуре перемещаться к следующему полю.

swift
struct RegistrationForm: View {
    enum Field: Hashable {
        case email
        case password
        case confirmPassword
    }
    
    @State var email = ""
    @State var password = ""
    @State var confirmPassword = ""
    @FocusState var focusedField: Field?
    
    var body: some View {
        Form {
            TextField("Email", text: $email)
                .focused($focusedField, equals: .email)
                .onSubmit { focusedField = .password }
            
            SecureField("Password", text: $password)
                .focused($focusedField, equals: .password)
                .onSubmit { focusedField = .confirmPassword }
            
            SecureField("Confirm", text: $confirmPassword)
                .focused($focusedField, equals: .confirmPassword)
                .onSubmit { submitForm() }
        }
    }
}

Обрати внимание на модификатор .onSubmit — он вызывается, когда пользователь нажимает "Return" на клавиатуре. Внутри .onSubmit мы переключаем focusedField на следующее поле, что автоматически перемещает фокус. Последнее поле вызывает submitForm() для отправки формы.

Скрытие и показ клавиатуры

@FocusState предоставляет простой способ скрыть клавиатуру — достаточно установить свойство в nil (для enum) или false (для Bool). Однако иногда нужно скрыть клавиатуру без привязки к конкретному полю — например, при тапе на пустое место. В этом случае есть несколько подходов.

swift
struct DismissKeyboardView: View {
    @State var text = ""
    @FocusState var isFocused: Bool
    
    var body: some View {
        TextField("Enter text", text: $text)
            .focused($isFocused)
            .toolbar {
                ToolbarItemGroup(placement: .keyboard) {
                    Spacer()
                    Button("Done") {
                        isFocused = false
                    }
                }
            }
    }
}

Модификатор .toolbar с placement .keyboard добавляет кнопку над клавиатурой. Это стандартный UX-паттерн в iOS для скрытия клавиатуры. Альтернативный подход — использовать .onTapGesture на корневом VStack для сброса фокуса при тапе на фон.

Фокус и валидация формы

@FocusState отлично комбинируется с валидацией формы. Типичный паттерн: после нажатия кнопки "Отправить" проверять все поля и устанавливать фокус на первое поле с ошибкой. Это улучшает пользовательский опыт — пользователь сразу видит, какое поле нужно исправить.

swift
struct ValidatedForm: View {
    enum Field: Hashable { case name; case phone }
    
    @State var name = ""
    @State var phone = ""
    @FocusState var focusedField: Field?
    @State var errors: [String] = []
    
    var body: some View {
        Form {
            TextField("Name", text: $name)
                .focused($focusedField, equals: .name)
            TextField("Phone", text: $phone)
                .focused($focusedField, equals: .phone)
            
            Button("Submit") { validateAndSubmit() }
        }
    }
    
    func validateAndSubmit() {
        if name.isEmpty {
            focusedField = .name
            return
        }
        if phone.isEmpty {
            focusedField = .phone
            return
        }
        // submit form
    }
}

В этом примере при пустом поле name фокус перемещается на него, и пользователь сразу видит, где ошибка. Если name заполнено, проверяется phone. Это естественное поведение для форм — пользователь заполняет поля сверху вниз, и валидация следует тому же порядку.

Типичные ошибки с @FocusState

Наиболее частая ошибка — попытка использовать @FocusState с типом, не соответствующим Hashable. @FocusState требует, чтобы тип свойства был Hashable (Bool и опциональные enum уже соответствуют). Если вы пытаетесь использовать кастомную структуру, убедитесь, что она реализует Hashable.

  • Забыл .focused() модификатор — @FocusState сам по себе не управляет фокусом. Нужно обязательно связать его с полем через .focused($property) или .focused($property, equals: .case).
  • Несколько @FocusState в одном View — для нескольких полей используй один @FocusState с enum, а не несколько @FocusState свойств. Несколько Bool-свойств не будут синхронизированы между собой.
  • Изменение @FocusState вне main thread — @FocusState нужно изменять только на главном потоке, как и все UI-свойства в SwiftUI. Асинхронные операции должны переключаться на MainActor перед изменением.
  • Сброс фокуса при перестроении — если View перестраивается, @FocusState может сброситься. Используй .id() модификатор для стабильной идентификации View.
swift
// ❌ Wrong: two @FocusState Bool instead of enum
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool

// ✅ Correct: single enum @FocusState
enum Field: Hashable { case name; case email }
@FocusState var focusedField: Field?

Часто задаваемые вопросы

С каких версий iOS доступен @FocusState?

@FocusState доступен с iOS 15, iPadOS 15, macOS 12, tvOS 15 и watchOS 8. Для проектов, поддерживающих iOS 14 и ниже, используй UIViewRepresentable с UITextField и becomeFirstResponder, либо сторонние библиотеки с кастомной реализацией управления фокусом.

Можно ли использовать @FocusState с кастомными UIViewRepresentable?

Да, для этого нужно в кастомном UIViewRepresentable реализовать поддержку FocusState через протокол UIViewRepresentable. Кастомный view должен иметь becomeFirstResponder и resignFirstResponder. SwiftUI автоматически свяжет @FocusState с этими методами, если указать .focused() модификатор.

Почему @FocusState не работает с TextField в List?

В List или Form ячейки могут переиспользоваться, что ломает связь @FocusState с полем. Решение: добавь .id() модификатор с уникальным идентификатором для каждого TextField. Например: .id(fieldName). Это заставляет SwiftUI создавать отдельный экземпляр View для каждого поля.

Как скрыть клавиатуру при тапе на пустое место?

Добавь .onTapGesture на корневой контейнер (VStack, ZStack) и сбрось фокус: focusedField = nil. Однако .onTapGesture может блокировать тапы на кнопки внутри — используй контейнер с .contentShape(Rectangle()) и .onTapGesture на нём, либо кастомный UIKitBackgroundView.

Как анимировать появление клавиатуры с @FocusState?

@FocusState не предоставляет прямого API для анимации клавиатуры — это системное поведение iOS. Однако ты можешь реагировать на изменения фокуса с помощью .onChange(of: focusedField) или .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) для кастомной анимации контента.

Итоги

  • @FocusState — нативный SwiftUI property wrapper для управления фокусом ввода, доступен с iOS 15.
  • Два режима — Bool для одного поля, Hashable enum для нескольких полей формы.
  • Модификатор .focused() — обязателен для связи @FocusState с конкретным полем ввода.
  • Программное управление — установка значения nil или false скрывает клавиатуру.
  • Валидация форм — @FocusState позволяет устанавливать фокус на первое поле с ошибкой после проверки.
  • Enum для нескольких полей — один @FocusState с enum предпочтительнее нескольких Bool-свойств.
  • iOS 15+ — для более старых версий используй UIViewRepresentable с becomeFirstResponder.

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также