@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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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