@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("Покажи клавиатурата") {
                isEmailFocused = true
            }
            Button("Скрий клавиатурата") {
                isEmailFocused = false
            }
        }
    }
}

В този пример isEmailFocused автоматично става true, когато потребителят докосне до текстовото поле, и false, когато клавиатурата е скрита. Бутоните позволяват програмно управление на фокуса — полезно за персонализирани клавиатури, бутони „Напред” и ситуации, когато трябва да принудително скриете клавиатурата след изпращане на формуляра.

Управление на фокуса с Enum за много полета

За формуляри с много полета @FocusState поддържа enum, съответстващ на протокол FocusStateValue (или Hashable). Всеки case на enum-а съответства на конкретно поле. Това позволява да превключвате фокуса между полета — например, при натискане на „Напред” на клавиатурата да преминете към следващото поле.

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("Готово") {
                        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("Изпрати") { validateAndSubmit() }
        }
    }
    
    func validateAndSubmit() {
        if name.isEmpty {
            focusedField = .name
            return
        }
        if phone.isEmpty {
            focusedField = .phone
            return
        }
        // изпрати формуляра
    }
}

В този пример, когато полето 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
// ❌ Грешка: два @FocusState Bool вместо enum
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool

// ✅ Правилно: един 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?

Да, за това трябва да имплементирате поддържка на FocusState в персонализирания UIViewRepresentable чрез протокол 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 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също