@Binding est un Property Wrapper dans SwiftUI qui crée une référence à des données appartenant à un autre composant. Binding ne stocke pas la valeur lui-même — il fournit seulement un accès à la source de vérité existante via la projection $. Selon la Documentation Développeur Apple (2025), Binding assure une communication réactive bidirectionnelle entre la vue parent et la vue enfant sans possession directe des données. @Binding est le mécanisme clé pour transmettre un état mutable vers le bas dans la hiérarchie.
Points clés
@Binding est un Property Wrapper qui crée une connexion bidirectionnelle entre une propriété stockée dans la vue parent et un composant enfant. La principale différence entre Binding et @State : Binding ne possède pas les données. Il lit et écrit seulement les valeurs via la source véritable — @State, @StateObject ou un autre Binding dans le parent. Sans Binding, les vues enfants ne pourraient pas modifier l'état de l'ancêtre sans callbacks ou délégués.
Binding est implémenté comme une structure avec deux propriétés : wrappedValue (la valeur actuelle) et projectedValue (le Binding lui-même, accessible via $). Lorsqu'une vue enfant modifie wrappedValue via Binding, SwiftUI transmet le changement à la source de données et redessine toutes les vues dépendantes. Cela se produit de manière synchrone dans le cycle de mise à jour actuel.
Une caractéristique importante : @Binding ne se limite pas à la transmission à un seul niveau. Binding peut être transmis à travers plusieurs niveaux de hiérarchie — chaque composant enfant reçoit une référence à la même source de données. Un changement à n'importe quel niveau déclenche une mise à jour unique de toutes les vues liées.
Le mécanisme de liaison bidirectionnelle via @Binding est construit sur les projections de Property Wrappers. Lorsqu'un parent déclare @State var value: T, SwiftUI génère automatiquement la projection $value de type Binding<T>. En passant $value à un composant enfant avec @Binding var value: T, vous connectez les deux vues à la même cellule mémoire. Toute écriture via Binding dans la vue enfant déclenche un redessin des deux composants.
struct SliderContainer: View {
@State private var value: Double = 0.5
var body: some View {
VStack {
Text("Value: \(value)")
SliderView(value: $value)
}
}
}
struct SliderView: View {
@Binding var value: Double
var body: some View {
Slider(value: $value, in: 0...1)
}
}
Dans l'exemple, SliderContainer possède @State value, et SliderView reçoit le Binding via $value. Le Slider à l'intérieur de SliderView est lié à ce Binding. En faisant glisser le curseur, Slider modifie la valeur via Binding, ce qui met automatiquement à jour @State dans SliderContainer, et les deux vues affichent le nombre actuel. Toute la chaîne fonctionne sans un seul callback ou notification.
Pour créer un Binding à partir de @StateObject ou @ObservedObject, on utilise la même projection : $object.property donne Binding<PropertyType>. Cela permet de passer des propriétés individuelles d'ObservableObject aux vues enfants sans passer l'objet entier. Cette approche fournit un couplage plus étroit et évite les redessins inutiles.
Avant SwiftUI, la façon standard de transmettre les changements vers le haut dans la hiérarchie était via des callbacks et délégués : le parent passait une closure, et le composant enfant l'invoquait lors du changement. @Binding offre une alternative avec moins de code et une syntaxe plus déclarative. Au lieu de passer une closure d'achèvement, vous passez simplement $stateValue.
| Critère | @Binding | Callbacks |
|---|---|---|
| Code | Une annotation + $ | Closure + invocation |
| Multiniveau | Automatique | Chaîne de closures |
| Test | Binding(value:constant) | Closures mock |
| Lisibilité | Élevée | Moyenne |
| Flexibilité | Données seulement | Toute logique |
Utilisez @Binding lorsque la vue enfant a seulement besoin de lire et modifier une valeur. Si des effets secondaires sont nécessaires lors du changement (validation, journalisation, requête réseau), combinez Binding avec un callback : passez Binding pour les données et une closure pour les événements. Par exemple, un TextField peut se lier à un Binding, tandis que onChange déclenche la validation.
@Binding est utilisé dans plusieurs scénarios typiques. Le premier — les contrôles personnalisés : interrupteurs, curseurs, sélecteurs de couleur et autres éléments interactifs acceptent Binding pour la synchronisation bidirectionnelle. Le deuxième — les fenêtres modales : le drapeau d'affichage de la sheet est passé comme Binding, permettant à la vue enfant de se fermer via presentationMode ou un réglage direct.
Le troisième modèle — les formulaires avec séparation. Si un formulaire se compose de nombreux champs, chaque champ peut être extrait dans un composant séparé qui accepte un Binding pour sa valeur. Cela simplifie le test et la réutilisation des champs dans différents formulaires. Le composant parent reste le seul propriétaire de l'ensemble du modèle de formulaire.
struct FormField: View {
let title: String
@Binding var text: String
var body: some View {
VStack(alignment: .leading) {
Text(title).font(.caption)
TextField("Enter \(title.lowercased())", text: $text)
.textFieldStyle(.roundedBorder)
}
}
}
Le composant FormField accepte un titre et un Binding vers une chaîne. Il affiche un label et un TextField lié au Binding passé. Tout formulaire peut utiliser FormField plusieurs fois en passant $property pour chaque champ. Cela réduit la duplication de balisage et centralise le style des champs de texte.
SwiftUI permet de créer Binding manuellement via l'initialiseur Binding(get:set:). C'est utile lorsqu'il faut ajouter de la logique à la lecture ou à l'écriture d'une valeur. Par exemple, on peut créer un Binding qui formate un nombre avant de l'enregistrer, ou un Binding qui synchronise la valeur avec un serveur distant à chaque modification.
struct ValidatedField: View {
@State private var email: String = ""
var emailBinding: Binding<String> {
.init(
get: { email },
set: { email = $0.lowercased().trimmingCharacters(in: .whitespaces) }
)
}
var body: some View {
TextField("Email", text: emailBinding)
}
}
Dans le listing, le emailBinding personnalisé convertit automatiquement le texte en minuscules et supprime les espaces à chaque modification. Le TextField utilise ce Binding au lieu de se lier directement à $email. Cette approche centralise la validation et la transformation des données à l'intérieur du Binding sans encombrer le code avec des gestionnaires onChange.
La première erreur et la plus courante est de passer une valeur au lieu d'un Binding. Si un composant enfant déclare @Binding var text: String, et que le parent passe text (sans $), le compilateur donnera une erreur : Cannot convert value of type 'String' to expected argument type 'Binding<String>'. La solution — utilisez toujours le préfixe $ lors de la transmission : $text.
La deuxième erreur — Binding sur des données en lecture seule. Si la vue enfant a seulement besoin de lire une valeur, n'utilisez pas @Binding — un simple let ou @State du parent suffit. Binding implique la capacité d'écriture, et des permissions de modification excessives compliquent le débogage et violent le principe du moindre privilège.
Le troisième problème — Binding.constant en production. Binding.constant(value) crée une liaison factice sans retour — les modifications sont ignorées. Utilisez constant seulement pour le prototypage et les prévisualisations (Xcode Previews), mais jamais dans le code réel. Pour les tests, utilisez Binding(get:set:) avec un comportement contrôlé.
Foire aux questions
@State possède les données et gère leur stockage dans le tas. @Binding référence seulement l'état existant sans possession. @State est toujours privé, @Binding est un paramètre d'entrée de la vue enfant.
Oui, via l'initialiseur Binding(get:set:) ou Binding.constant(value). Binding peut également être obtenu depuis @StateObject via la projection $object.$property et depuis Publisher via Binding(get:set:) à l'intérieur de Subscribe.
@Binding est passé par chaîne : chaque composant intermédiaire déclare @Binding et le transmet via $. Tous les niveaux référencent la même source de données dans la vue racine.
Binding.constant crée une enveloppe muette — le setter ignore les nouvelles valeurs. Il est destiné uniquement au prototypage et aux Previews SwiftUI où le retour du composant enfant n'est pas nécessaire.
Oui, Binding<T?> est pris en charge. Si vous passez Binding<String?>, la vue enfant pourra définir nil. C'est pratique pour les champs de formulaire optionnels ou les états avec possibilité de réinitialisation.
Résumé
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.
Lisez aussi