@FocusState — qu'est-ce que c'est, gestion du focus et du clavier dans SwiftUI

Auteur : IT Sectr Publié le : 2026-06-26 Temps de lecture : 9 min

@FocusState est un property wrapper dans SwiftUI, introduit dans iOS 15, qui permet de contrôler programmatiquement le focus de saisie sur les champs de texte et autres éléments. Avant son apparition, les développeurs devaient utiliser UIViewRepresentable pour accéder aux méthodes UIKit becomeFirstResponder et resignFirstResponder. @FocusState résout ce problème de manière native : vous liez une propriété à un champ via le modificateur .focused(), après quoi la définition ou la suppression du focus se fait par une simple assignation de valeur. Selon Apple Developer Documentation — FocusState (2025), @FocusState prend en charge deux modes : Bool pour une gestion simple (focus activé ou désactivé) et enum pour plusieurs champs, où chaque cas correspond à un champ de saisie spécifique.

Points clés

  • @FocusState — un property wrapper pour la gestion programmatique du focus dans SwiftUI, disponible depuis iOS 15.
  • Mode Bool — pour un seul champ, utilisez @FocusState var isFocused: Bool avec .focused($isFocused).
  • Mode Enum — pour plusieurs champs, utilisez un enum conforme au protocole FocusStateValue et .focused($field, equals: .fieldName).
  • Masquer le clavier — définissez le focus sur nil ou false pour masquer le clavier.
  • Focus automatique — définissez la valeur initiale dans .onAppear pour afficher le clavier à l'ouverture de l'écran.

Qu'est-ce que @FocusState dans SwiftUI

@FocusState est un property wrapper qui lie l'état du focus à un champ de saisie spécifique ou à un autre élément focusable dans SwiftUI. Contrairement à UIKit, où la gestion du focus se fait via les méthodes becomeFirstResponder et resignFirstResponder, SwiftUI utilise une approche déclarative : vous déclarez un état (@FocusState) et le liez à un élément via le modificateur .focused(). Changer l'état change automatiquement le focus.

Avant l'introduction de @FocusState dans iOS 15, les développeurs devaient créer des wrappers UIViewRepresentable autour de UITextField ou utiliser des bibliothèques tierces. @FocusState est intégré directement dans SwiftUI et fonctionne avec TextField, TextEditor, SecureField et SearchField. Cela rend le code plus propre, réduit le nombre de ponts UIKit et améliore la testabilité.

Selon WWDC Session 10136 — What's new in SwiftUI (2024), @FocusState utilise le système de preference keys de SwiftUI pour transmettre les informations de focus entre les éléments. Lorsqu'un champ reçoit le focus, SwiftUI met automatiquement à jour la propriété @FocusState associée, ce qui permet de réagir aux changements de focus dans le code.

Gestion du focus avec Bool

La façon la plus simple d'utiliser @FocusState est avec le type Bool. Lorsqu'un champ est focus, la propriété est true. Lorsque le focus est perdu — false. Vous pouvez forcer le focus en le définissant sur true, ou le supprimer en le définissant sur false.

swift
struct LoginForm: View {
    @State var email = ""
    @FocusState var isEmailFocused: Bool
    
    var body: some View {
        VStack {
            TextField("Email", text: $email)
                .focused($isEmailFocused)
            
            Button("Afficher le clavier") {
                isEmailFocused = true
            }
            Button("Masquer le clavier") {
                isEmailFocused = false
            }
        }
    }
}

Dans cet exemple, isEmailFocused devient automatiquement true lorsque l'utilisateur tape sur le champ de texte, et false lorsque le clavier est masqué. Les boutons permettent de gérer le focus programmatiquement — utile pour les claviers personnalisés, les boutons "Suivant" et les situations où vous devez masquer de force le clavier après la soumission du formulaire.

Gestion du focus avec Enum pour plusieurs champs

Pour les formulaires avec plusieurs champs, @FocusState prend en charge un enum conforme au protocole FocusStateValue (ou Hashable). Chaque cas de l'enum correspond à un champ spécifique. Cela permet de basculer le focus entre les champs — par exemple, lorsque l'utilisateur appuie sur "Suivant" au clavier pour passer au champ suivant.

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() }
        }
    }
}

Notez le modificateur .onSubmit — il est appelé lorsque l'utilisateur appuie sur "Return" au clavier. Dans .onSubmit, nous basculons focusedField vers le champ suivant, ce qui déplace automatiquement le focus. Le dernier champ appelle submitForm() pour soumettre le formulaire.

Masquer et afficher le clavier

@FocusState fournit un moyen simple de masquer le clavier — il suffit de définir la propriété sur nil (pour enum) ou false (pour Bool). Cependant, vous avez parfois besoin de masquer le clavier sans le lier à un champ spécifique — par exemple, en tapant sur un espace vide. Dans ce cas, il existe plusieurs approches.

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("Terminé") {
                        isFocused = false
                    }
                }
            }
    }
}

Le modificateur .toolbar avec placement .keyboard ajoute un bouton au-dessus du clavier. C'est un modèle UX standard dans iOS pour masquer le clavier. Une approche alternative consiste à utiliser .onTapGesture sur le VStack racine pour réinitialiser le focus lors d'un tap sur le fond.

Focus et validation de formulaire

@FocusState se combine parfaitement avec la validation de formulaire. Un modèle typique : après avoir appuyé sur le bouton "Envoyer", validez tous les champs et définissez le focus sur le premier champ avec une erreur. Cela améliore l'expérience utilisateur — l'utilisateur voit immédiatement quel champ doit être corrigé.

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("Soumettre") { validateAndSubmit() }
        }
    }
    
    func validateAndSubmit() {
        if name.isEmpty {
            focusedField = .name
            return
        }
        if phone.isEmpty {
            focusedField = .phone
            return
        }
        // soumettre le formulaire
    }
}

Dans cet exemple, si le champ name est vide, le focus se déplace vers lui, et l'utilisateur voit immédiatement où se trouve l'erreur. Si name est rempli, phone est vérifié. C'est un comportement naturel pour les formulaires — l'utilisateur remplit les champs de haut en bas, et la validation suit le même ordre.

Erreurs courantes avec @FocusState

L'erreur la plus courante est d'essayer d'utiliser @FocusState avec un type qui n'est pas conforme à Hashable. @FocusState exige que le type de la propriété soit Hashable (Bool et les enums optionnels sont déjà conformes). Si vous essayez d'utiliser une structure personnalisée, assurez-vous qu'elle implémente Hashable.

  • Modificateur .focused() oublié — @FocusState seul ne gère pas le focus. Vous devez le lier à un champ via .focused($property) ou .focused($property, equals: .case).
  • Plusieurs @FocusState dans une même View — pour plusieurs champs, utilisez un seul @FocusState avec un enum, pas plusieurs propriétés @FocusState. Plusieurs propriétés Bool ne seront pas synchronisées entre elles.
  • Modification de @FocusState en dehors du thread principal — @FocusState ne doit être modifié que sur le thread principal, comme toutes les propriétés UI dans SwiftUI. Les opérations asynchrones doivent basculer sur MainActor avant de le modifier.
  • Réinitialisation du focus lors de la reconstruction de la View — si la View est reconstruite, @FocusState peut se réinitialiser. Utilisez le modificateur .id() pour une identification stable de la View.
swift
// ❌ Erreur : deux @FocusState Bool au lieu d'enum
@FocusState var isNameFocused: Bool
@FocusState var isEmailFocused: Bool

// ✅ Correct : un seul enum @FocusState
enum Field: Hashable { case name; case email }
@FocusState var focusedField: Field?

Foire aux questions

À partir de quelles versions d'iOS @FocusState est-il disponible ?

@FocusState est disponible à partir d'iOS 15, iPadOS 15, macOS 12, tvOS 15 et watchOS 8. Pour les projets prenant en charge iOS 14 et inférieur, utilisez UIViewRepresentable avec UITextField et becomeFirstResponder, ou des bibliothèques tierces avec une implémentation personnalisée de la gestion du focus.

Peut-on utiliser @FocusState avec un UIViewRepresentable personnalisé ?

Oui, pour cela vous devez implémenter le support de FocusState dans le UIViewRepresentable personnalisé via le protocole UIViewRepresentable. La vue personnalisée doit avoir becomeFirstResponder et resignFirstResponder. SwiftUI liera automatiquement @FocusState à ces méthodes si vous spécifiez le modificateur .focused().

Pourquoi @FocusState ne fonctionne-t-il pas avec TextField dans List ?

Dans List ou Form, les cellules peuvent être réutilisées, ce qui casse la liaison @FocusState avec le champ. Solution : ajoutez le modificateur .id() avec un identifiant unique pour chaque TextField. Par exemple : .id(fieldName). Cela force SwiftUI à créer une instance de View séparée pour chaque champ.

Comment masquer le clavier en tapant sur un espace vide ?

Ajoutez .onTapGesture au conteneur racine (VStack, ZStack) et réinitialisez le focus : focusedField = nil. Cependant, .onTapGesture peut bloquer les taps sur les boutons internes — utilisez un conteneur avec .contentShape(Rectangle()) et .onTapGesture dessus, ou un UIKitBackgroundView personnalisé.

Comment animer l'apparition du clavier avec @FocusState ?

@FocusState ne fournit pas d'API directe pour l'animation du clavier — c'est un comportement système iOS. Cependant, vous pouvez réagir aux changements de focus avec .onChange(of: focusedField) ou .onReceive(NotificationCenter.default.publisher(for: UIResponder.keyboardWillShowNotification)) pour une animation personnalisée du contenu.

Résumé

  • @FocusState — un property wrapper natif SwiftUI pour la gestion du focus de saisie, disponible depuis iOS 15.
  • Deux modes — Bool pour un seul champ, enum Hashable pour plusieurs champs de formulaire.
  • Modificateur .focused() — obligatoire pour lier @FocusState à un champ de saisie spécifique.
  • Contrôle programmatique — définir la valeur sur nil ou false masque le clavier.
  • Validation de formulaire — @FocusState permet de définir le focus sur le premier champ avec une erreur après validation.
  • Enum pour plusieurs champs — un seul @FocusState avec enum est préférable à plusieurs propriétés Bool.
  • iOS 15+ — pour les versions plus anciennes, utilisez UIViewRepresentable avec becomeFirstResponder.

Nous développerons une application mobile clé en main

IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.

Discuter du projet

Lisez aussi