@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 користи SwiftUI систем preference кључева за преношење информација о фокусу између елемената. Када поље прими фокус, 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("Prikaži tastaturu") {
isEmailFocused = true
}
Button("Sakrij tastaturu") {
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("Gotovo") {
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("Pošalji") { validateAndSubmit() }
}
}
func validateAndSubmit() {
if name.isEmpty {
focusedField = .name
return
}
if phone.isEmpty {
focusedField = .phone
return
}
// pošalji formular
}
}
У овом примеру, када је поље name празно, фокус се помера на њега и корисник одмах види где је грешка. Ако је name попуњено, проверава се phone. Ово је природно понашање за формуларе — корисник пољунава поља одозго надоле, а валидација прати исти редослед.
Најчешћа грешка — покушај коришћења @FocusState са типом који није Hashable. @FocusState захтева да тип својства буде Hashable (Bool и опционални enum већ јесу усаглашени). Ако покушавате да користите прилагођену структуру, уверите се да имплементира Hashable.
// ❌ Greška: dva @FocusState Bool umesto enum
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool
// ✅ Ispravno: jedan 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. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође