@FocusState è un property wrapper in SwiftUI, introdotto in iOS 15, che consente di controllare programmaticamente il focus di input su campi di testo e altri elementi. Prima della sua comparsa, gli sviluppatori dovevano usare UIViewRepresentable per accedere ai metodi UIKit becomeFirstResponder e resignFirstResponder. @FocusState risolve questo problema in modo nativo: si lega una proprietà a un campo tramite il modificatore .focused(), dopodiché l'impostazione o la rimozione del focus avviene con una semplice assegnazione di valore. Secondo Apple Developer Documentation — FocusState (2025), @FocusState supporta due modalità: Bool per la gestione semplice (focus attivo o meno) ed enum per più campi, dove ogni case corrisponde a un campo di input specifico.
Punti chiave
@FocusState è un property wrapper che lega lo stato del focus a un campo di input specifico o a un altro elemento focusabile in SwiftUI. A differenza di UIKit, dove la gestione del focus avviene tramite i metodi becomeFirstResponder e resignFirstResponder, SwiftUI utilizza un approccio dichiarativo: si dichiara uno stato (@FocusState) e lo si lega a un elemento tramite il modificatore .focused(). Modificare lo stato cambia automaticamente il focus.
Prima dell'introduzione di @FocusState in iOS 15, gli sviluppatori dovevano creare wrapper UIViewRepresentable attorno a UITextField o usare librerie di terze parti. @FocusState è integrato direttamente in SwiftUI e funziona con TextField, TextEditor, SecureField e SearchField. Questo rende il codice più pulito, riduce il numero di ponti UIKit e migliora la testabilità.
Secondo WWDC Session 10136 — What's new in SwiftUI (2024), @FocusState utilizza il sistema di preference keys di SwiftUI per passare le informazioni di focus tra gli elementi. Quando un campo riceve il focus, SwiftUI aggiorna automaticamente la proprietà @FocusState associata, consentendo di reagire alle modifiche del focus nel codice.
Il modo più semplice per usare @FocusState è con il tipo Bool. Quando un campo è focalizzato, la proprietà è true. Quando il focus viene perso — false. È possibile forzare il focus impostandolo a true o rimuoverlo impostandolo a false.
struct LoginForm: View {
@State var email = ""
@FocusState var isEmailFocused: Bool
var body: some View {
VStack {
TextField("Email", text: $email)
.focused($isEmailFocused)
Button("Mostra tastiera") {
isEmailFocused = true
}
Button("Nascondi tastiera") {
isEmailFocused = false
}
}
}
}
In questo esempio, isEmailFocused diventa automaticamente true quando l'utente tocca il campo di testo e false quando la tastiera viene nascosta. I pulsanti consentono di gestire il focus in modo programmatico — utile per tastiere personalizzate, pulsanti "Avanti" e situazioni in cui è necessario nascondere forzatamente la tastiera dopo l'invio del modulo.
Per moduli con più campi, @FocusState supporta un enum conforme al protocollo FocusStateValue (o Hashable). Ogni case dell'enum corrisponde a un campo specifico. Ciò consente di spostare il focus tra i campi — ad esempio, quando l'utente preme "Avanti" sulla tastiera per passare al campo successivo.
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() }
}
}
}
Nota il modificatore .onSubmit — viene chiamato quando l'utente preme "Return" sulla tastiera. All'interno di .onSubmit spostiamo focusedField al campo successivo, che sposta automaticamente il focus. L'ultimo campo chiama submitForm() per inviare il modulo.
@FocusState fornisce un modo semplice per nascondere la tastiera — basta impostare la proprietà su nil (per enum) o false (per Bool). Tuttavia, a volte è necessario nascondere la tastiera senza legarla a un campo specifico — ad esempio, toccando uno spazio vuoto. In questo caso, esistono diversi approcci.
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("Fatto") {
isFocused = false
}
}
}
}
}
Il modificatore .toolbar con placement .keyboard aggiunge un pulsante sopra la tastiera. Questo è un pattern UX standard in iOS per nascondere la tastiera. Un approccio alternativo è usare .onTapGesture sul VStack radice per reimpostare il focus toccando lo sfondo.
@FocusState si combina perfettamente con la validazione dei moduli. Un pattern tipico: dopo aver premuto il pulsante "Invia", validare tutti i campi e impostare il focus sul primo campo con errore. Questo migliora l'esperienza utente — l'utente vede immediatamente quale campo deve essere corretto.
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("Invia") { validateAndSubmit() }
}
}
func validateAndSubmit() {
if name.isEmpty {
focusedField = .name
return
}
if phone.isEmpty {
focusedField = .phone
return
}
// invia modulo
}
}
In questo esempio, se il campo name è vuoto, il focus si sposta su di esso e l'utente vede immediatamente dov'è l'errore. Se name è compilato, viene controllato phone. Questo è un comportamento naturale per i moduli — l'utente compila i campi dall'alto verso il basso e la validazione segue lo stesso ordine.
L'errore più comune è cercare di usare @FocusState con un tipo che non è conforme a Hashable. @FocusState richiede che il tipo della proprietà sia Hashable (Bool ed enum opzionali sono già conformi). Se si sta cercando di usare una struttura personalizzata, assicurarsi che implementi Hashable.
// ❌ Errore: due @FocusState Bool invece di enum
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool
// ✅ Corretto: singolo enum @FocusState
enum Field: Hashable { case name; case email }
@FocusState var focusedField: Field?
Domande frequenti
@FocusState è disponibile da iOS 15, iPadOS 15, macOS 12, tvOS 15 e watchOS 8. Per progetti che supportano iOS 14 e versioni precedenti, usa UIViewRepresentable con UITextField e becomeFirstResponder, o librerie di terze parti con implementazione personalizzata della gestione del focus.
Sì, per farlo è necessario implementare il supporto per FocusState nel UIViewRepresentable personalizzato tramite il protocollo UIViewRepresentable. La vista personalizzata deve avere becomeFirstResponder e resignFirstResponder. SwiftUI collegherà automaticamente @FocusState a questi metodi se si specifica il modificatore .focused().
In List o Form, le celle possono essere riutilizzate, il che rompe il legame @FocusState con il campo. Soluzione: aggiungi il modificatore .id() con un identificatore univoco per ogni TextField. Ad esempio: .id(fieldName). Questo forza SwiftUI a creare un'istanza di View separata per ogni campo.
Aggiungi .onTapGesture al contenitore radice (VStack, ZStack) e reimposta il focus: focusedField = nil. Tuttavia, .onTapGesture potrebbe bloccare i tocchi sui pulsanti interni — usa un contenitore con .contentShape(Rectangle()) e .onTapGesture su di esso, o un UIKitBackgroundView personalizzato.
@FocusState non fornisce un'API diretta per l'animazione della tastiera — questo è un comportamento di sistema iOS. Tuttavia, puoi reagire alle modifiche del focus con .onChange(of: focusedField) o .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) per animazioni personalizzate del contenuto.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche