Note : ces notions ne sont plus applicables si vous utiisez @Observable plutpôt que ObservableObject.
Tel que décrit dans la fiche « ObservableObject, @Published et @StateObject », lorsque vous déclarez un objet avec avec @StateObject, vous vous assurez que la vue sera rafraîchie à chaque fois qu'une propriété publiée de cet objet sera modifiée, que ce soit fait dans la classe de l'objet ou directement dans la vue qui utilise l'objet.
La classe de cet objet doit répondre au protocole ObservableObject et au moins une de ses propriétés doit être déclarée avec l'attribut @Published.
Le même comportement se produit si vous décorez l'objet avec l'attribut @ObservedObject.
Alors, quelle est la différence entre ces deux attributs et comment déterminer lequel utiliser?
Réponse rapide :
Il faut utiliser @StateObject lorsqu'on instancie l'objet à observer et réserver @ObservedObject pour les sous-vues qui reçoivent une référence à l'objet.
Voici maintenant les explications.
Si vous utilisez @ObservedObject au moment où l'objet est instancié, la variable retrouvera sa valeur initiale à chaque fois que la vue est recréée.
Ce ne sera pas le cas avec @StateObject : la variable conservera sa valeur même si la vue est recréée.
Prenons l'exemple d'une classe nommée MotHasard.
Dans cette classe, il y a différentes façons de modifier la propriété mot, notamment en appelant la méthode nouveauMot() ou en modifiant la valeur de la propriété majuscules.
class MotHasard: ObservableObject {
var mots = ["abeille", "tableau", "voiture", "chien", "téléphone"]
@Published var mot: String = ""
@Published var majuscules: Bool = false {
didSet {
if majuscules {
mot = mot.uppercased()
}
else {
mot = mot.lowercased()
}
}
}
func nouveauMot() {
mot = mots.randomElement()!
if majuscules {
mot = mot.uppercased()
}
}
func reinitialiser() {
mot = ""
majuscules = false
}
}
La vue qui se charge d'instancier l'objet à observer doit le déclarer avec @StateObject.
Comme pour les variables d'état (@State), il faut soit donner une valeur initiale dès la déclaration, soit donner la valeur initiale dans le constructeur init().
De plus, cette propriété devrait être déclarée privée.
struct ContentView: View {
@StateObject private var motHasard = MotHasard()
var body: some View {
VStack {
Text("Majuscules")
Toggle(isOn: $motHasard.majuscules) {}
.labelsHidden()
.tint(.accentColor)
Button(action: {
motHasard.nouveauMot()
}) {
Text("Générer un mot")
}
.buttonStyle(.bordered)
.padding()
Text("Le mot est : \(motHasard.mot)") // sera automatiquement rafraîchi dès que la valeur change
}
}
}

Si une sous-vue a besoin elle aussi de travailler avec cette variable, elle pourra la recevoir en la déclarant avec l'attribut @ObservedObject.
La sous-vue n'instanciera pas l'objet ni lors de sa déclaration, ni dans le constructeur puisque l'objet lui aura été passé en paramètre par la vue principale.
Pour démontrer ceci, j'ai déplacé dans une sous-vue le bouton bascule qui permet de passer en lettres majuscules.
J'ai ajouté cette sous-vue à la vue principale en lui founissant une référence à l'objet observé.
Afin de démontrer que la sous-vue peut elle aussi être informée des modifications, j'ai ajouté à la vue principale un bouton pour réinitialiser l'objet.
struct ContentView: View {
@StateObject private var motHasard = MotHasard() // l'objet est instancié ici
var body: some View {
VStack {
MajusculesView(motHasard: motHasard)
Button(action: {
motHasard.nouveauMot()
}) {
Text("Générer un mot")
}
.buttonStyle(.bordered)
.padding()
Text("Le mot est : \(motHasard.mot)") // sera automatiquement rafraîchi dès que la valeur change
Button(action: {
motHasard.reinitialiser()
}) {
Text("Réinitialiser")
}
.buttonStyle(.bordered)
.padding()
}
}
}
struct MajusculesView: View {
@ObservedObject var mot: MotHasard // reçoit une référence à l'objet
var body: some View {
VStack {
Text("Majuscules")
Toggle(isOn: $motHasard.majuscules) {}
.labelsHidden()
.tint(.accentColor)
}
}
}

Dans les exemples précédents, vous auriez pu utiliser indifféremment @StateObject ou @ObservedObject. L'application fonctionnera correctement dans tous les cas.
Cependant, si la vue qui déclare l'objet était elle-même contenue dans une autre vue, l'utlisation de @ObservedObject poserait problème.
Pour démontrer cela, j'ai déplacé tout le code du ContentView dans une sous-vue que j'ai appelée AccueilView.
Dans le ContentView, je fais appel à AccueilView.
Afin de voir ce qui se passe quand la vue est rafraîchie, j'ai ajouté une variable bidon dont la valeur peut être modifiée à l'aide d'un bouton.
struct ContentView: View {
@State private var variablePourRafraichirEcran: Int = 0
var body: some View {
VStack {
AccueilView()
Button(action: {
variablePourRafraichirEcran += 1
}) {
Text("Incrémenter pour rafraîchir la vue")
}
.buttonStyle(.bordered)
.padding()
Text("\(variablePourRafraichirEcran)")
}
}
}
Dans ce code, un clic sur le bouton d'incrémentation forcera le rafraîchissement de la vue puisque le nombre à afficher sera différent.
Si, dans AccueilView, l'objet est déclaré avec @StateObject, tout fonctionne bien.
Dans le cas où l'objet est déclaré avec @ObservedObject, le rafraîchissement de la vue fera en sorte que l'objet observé sera recréé et donc qu'il ne retiendra plus le mot généré ni l'état des majuscules.
Voilà pourquoi il faut utiliser @StateObject lorsqu'on instancie l'objet à observer et réserver @ObservedObject pour les sous-vues qui reçoivent une référence à l'objet.
« @StateObject vs. @ObservedObject: The differences explained ». Swift Lee. https://www.avanderlee.com/swiftui/stateobject-observedobject-differences/
« How to use @ObservedObject to manage state from external objects ». Hacking with Swift. https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-observedobject-to-manage-state-from-external-objects
« SwiftUI: @State vs @StateObject vs @ObservedObject vs @EnvironmentObject ». Telstra Purple. https://purple.telstra.com/blog/swiftui---state-vs--stateobject-vs--observedobject-vs--environme
▼Publicité