@FocusState — е property wrapper в SwiftUI, представен в iOS 15, който позволява програмно да управлявате фокуса на въвеждане върху текстови полета и други елементи. Преди появата му, разработчиците трябваше да използват UIViewRepresentable за достъп до UIKit методите becomeFirstResponder и resignFirstResponder. @FocusState решава този проблем по нативен начин: свързвате свойство с полето чрез модификатора .focused(), след което задаването или нулирането на фокуса става чрез просто присвояване на стойност. Според Apple Developer Documentation — FocusState (2025), @FocusState поддържа два режима: Bool за просто управление (фокус има или няма) и enum за много полета, където всеки case съответства на конкретно поле за въвеждане.
Основни
@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 свойство, което позволява да реагирате на промените на фокуса в кода.
Най-простият начин за използване на @FocusState — тип Bool. Когато полето е на фокус, свойството е true. Когато фокусът изчезне — false. Можете принудително да зададете фокуса чрез присвояване на true или да го нулирате чрез присвояване на false.
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, когато клавиатурата е скрита. Бутоните позволяват програмно управление на фокуса — полезно за персонализирани клавиатури, бутони „Напред” и ситуации, когато трябва да принудително скриете клавиатурата след изпращане на формуляра.
За формуляри с много полета @FocusState поддържа enum, съответстващ на протокол FocusStateValue (или Hashable). Всеки case на enum-а съответства на конкретно поле. Това позволява да превключвате фокуса между полета — например, при натискане на „Напред” на клавиатурата да преминете към следващото поле.
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). Понякога обаче трябва да скриете клавиатурата без връзка с конкретно поле — например, при докосване на празно място. В този случай има няколко подхода.
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 се съчетава отлично с валидацията на формуляра. Типичен модел: след натискане на бутона „Изпрати” проверете всички полета и настройте фокуса на първото поле с грешка. Това подобрява потребителското изживяване — потребителят веднага вижда кое поле трябва да поправи.
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 с тип, който не е Hashable. @FocusState изисква типът на свойството да бъде Hashable (Bool и опционалните enum вече отговарят). Ако опитате да използвате персонализирана структура, убедете се, че тя имплементира Hashable.
// ❌ Грешка: два @FocusState Bool вместо enum
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool
// ✅ Правилно: един enum @FocusState
enum Field: Hashable { case name; case email }
@FocusState var focusedField: Field?
Често задавани въпроси
@FocusState е достъпен от iOS 15, iPadOS 15, macOS 12, tvOS 15 и watchOS 8. За проекти, поддържащи iOS 14 и по-долу, използвайте UIViewRepresentable с UITextField и becomeFirstResponder, или библиотеки на трети страни с персонализирано имплементиране на управлението на фокуса.
Да, за това трябва да имплементирате поддържка на FocusState в персонализирания UIViewRepresentable чрез протокол UIViewRepresentable. Персонализираният view трябва да има becomeFirstResponder и resignFirstResponder. SwiftUI автоматично ще свърже @FocusState с тези методи, ако посочите модификатора .focused().
В List или Form клетките могат да се преизползват, което скусява връзката на @FocusState с полето. Решение: добавете модификатора .id() с уникален идентификатор за всеки TextField. Например: .id(fieldName). Това кара SwiftUI да създава отделен екземпляр View за всеко поле.
Добавете .onTapGesture върху основния контейнер (VStack, ZStack) и нулирайте фокуса: focusedField = nil. Обаче .onTapGesture може да блокира докосванията върху бутоните вътре — използвайте контейнер с .contentShape(Rectangle()) и .onTapGesture върху него, или персонализиран UIKitBackgroundView.
@FocusState не предоставя пряк API за анимиране на клавиатурата — това е системно поведение на iOS. Можете обаче да реагирате на промените на фокуса с .onChange(of: focusedField) или .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) за персонализирана анимация на съдържанието.
Резюме
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също