@FocusState — este un property wrapper în SwiftUI, introdus în iOS 15, care permite gestionarea programatică a focalizării intrării pe câmpuri text și alte elemente. Înainte de apariția sa, dezvoltatorii trebuiau să folosească UIViewRepresentable pentru a accesa metodele UIKit becomeFirstResponder și resignFirstResponder. @FocusState rezolvă această problemă în mod nativ: legați o proprietate de câmp prin modificatorul .focused(), după care setarea sau resetarea focalizării are loc prin simpla atribuire a valorii. Conform Apple Developer Documentation — FocusState (2025), @FocusState suportă două moduri: Bool pentru gestionare simplă (focalizare există sau nu) și enum pentru mai multe câmpuri, unde fiecare case corespunde unui anumit câmp de introducere.
Principalele
@FocusState — este un property wrapper care leagă starea de focalizare de un anumit câmp de introducere sau alt element focusabil în SwiftUI. Spre deosebire de UIKit, unde gestionarea focalizării se face prin metodele becomeFirstResponder și resignFirstResponder, SwiftUI folosește o abordare declarativă: declarați starea (@FocusState) și o legați de element prin modificatorul .focused(). Schimbarea stării modifică automat focalizarea.
Înainte de apariția @FocusState în iOS 15, dezvoltatorii trebuiau să creeze îmbrăcăminți UIViewRepresentable în jurul UITextField sau să folosească biblioteci terțe. @FocusState este integrat direct în SwiftUI și funcționează cu TextField, TextEditor, SecureField și SearchField. Acest lucru face codul mai curat, reduce numărul de punți UIKit și îmbunătățește testabilitatea.
Conform WWDC Session 10136 — What's new in SwiftUI (2024), @FocusState folosește sistemul de chei de preferință SwiftUI pentru a transmite informații despre focalizare între elemente. Când un câmp primește focalizarea, SwiftUI actualizează automat proprietatea @FocusState asociată, permițând reacția la schimbările de focalizare în cod.
Cel mai simplu mod de a folosi @FocusState — tipul Bool. Când câmpul este focalizat, proprietatea este true. Când focalizarea pleacă — false. Puteți forța setarea focalizării atribuind true sau o puteți reseta atribuind false.
struct LoginForm: View {
@State var email = ""
@FocusState var isEmailFocused: Bool
var body: some View {
VStack {
TextField("Email", text: $email)
.focused($isEmailFocused)
Button("Arată tastatura") {
isEmailFocused = true
}
Button("Ascunde tastatura") {
isEmailFocused = false
}
}
}
}
În acest exemplu, isEmailFocused devine automat true când utilizatorul atinge câmpul text și false când tastatura este ascunsă. Butoanele permit gestionarea programatică a focalizării — util pentru tastaturi personalizate, butoane „Următorul” și situații când trebuie forțată ascunderea tastaturii după trimiterea formularului.
Pentru formulare cu mai multe câmpuri, @FocusState suportă un enum conform protocolului FocusStateValue (sau Hashable). Fiecare case a enum-ului corespunde unui anumit câmp. Aceasta permite comutarea focalizării între câmpuri — de exemplu, la apăsarea „Următorul” pe tastatură pentru a trece la următorul câmp.
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() }
}
}
}
Observați modificatorul .onSubmit — este apelat când utilizatorul apasă „Return” pe tastatură. În interiorul .onSubmit comutăm focusedField pe următorul câmp, ceea ce mută automat focalizarea. Ultimul câmp apelează submitForm() pentru trimiterea formularului.
@FocusState oferă o modalitate simplă de a ascunde tastatura — este suficient să setați proprietatea la nil (pentru enum) sau false (pentru Bool). Uneori însă trebuie să ascundeți tastatura fără a o lega de un anumit câmp — de exemplu, la atingerea unui loc gol. În acest caz, există mai multe abordări.
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("Gata") {
isFocused = false
}
}
}
}
}
Modificatorul .toolbar cu placement .keyboard adaugă un buton deasupra tastaturii. Acesta este un model standard UX în iOS pentru ascunderea tastaturii. O abordare alternativă — folosirea .onTapGesture pe VStack-ul principal pentru a reseta focalizarea la atingerea fundalului.
@FocusState se combină excelent cu validarea formularului. Modelul tipic: după apăsarea butonului „Trimite”, verificați toate câmpurile și setați focalizarea pe primul câmp cu eroare. Acest lucru îmbunătățește experiența utilizatorului — utilizatorul vede imediat care câmp trebuie corectat.
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("Trimite") { validateAndSubmit() }
}
}
func validateAndSubmit() {
if name.isEmpty {
focusedField = .name
return
}
if phone.isEmpty {
focusedField = .phone
return
}
// trimite formularul
}
}
În acest exemplu, când câmpul name este gol, focalizarea se mută pe el și utilizatorul vede imediat unde este eroarea. Dacă name este completat, se verifică phone. Acesta este un comportament natural pentru formulare — utilizatorul completează câmpurile de sus în jos, iar validarea urmează aceeași ordine.
Cea mai frecventă eroare — încercarea de a folosi @FocusState cu un tip care nu respectă Hashable. @FocusState necesită ca tipul proprietății să fie Hashable (Bool și enum-urile opționale sunt deja conforme). Dacă încercați să folosiți o structură personalizată, asigurați-vă că implementează Hashable.
// ❌ Eroare: două @FocusState Bool în loc de enum
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool
// ✅ Corect: un singur enum @FocusState
enum Field: Hashable { case name; case email }
@FocusState var focusedField: Field?
Întrebări frecvente
@FocusState este disponibil de la iOS 15, iPadOS 15, macOS 12, tvOS 15 și watchOS 8. Pentru proiecte care suportă iOS 14 și mai jos, folosiți UIViewRepresentable cu UITextField și becomeFirstResponder, sau biblioteci terțe cu implementare personalizată a gestionării focalizării.
Da, pentru aceasta trebuie să implementați suportul pentru FocusState în UIViewRepresentable personalizat prin protocolul UIViewRepresentable. View-ul personalizat trebuie să aibă becomeFirstResponder și resignFirstResponder. SwiftUI va lega automat @FocusState de aceste metode dacă specificați modificatorul .focused().
În List sau Form, celulele pot fi reutilizate, ceea ce strică legătura @FocusState cu câmpul. Soluție: adăugați modificatorul .id() cu un identificator unic pentru fiecare TextField. De exemplu: .id(fieldName). Acest lucru forțează SwiftUI să creeze o instanță View separată pentru fiecare câmp.
Adăugați .onTapGesture pe containerul principal (VStack, ZStack) și resetați focalizarea: focusedField = nil. Totuși, .onTapGesture poate bloca atingerile pe butoanele din interior — folosiți un container cu .contentShape(Rectangle()) și .onTapGesture pe el, sau un UIViewBackgroundView personalizat.
@FocusState nu oferă o API directă pentru animarea tastaturii — acesta este un comportament de sistem iOS. Puteți totuși reacționa la schimbările de focalizare cu .onChange(of: focusedField) sau .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) pentru animație personalizată a conținutului.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și