SCours SwiftUI
Fiche 02.06

Fiche 02.06 — @EnvironmentObject en SwiftUI

Objectif

Comprendre à quoi sert @EnvironmentObject, comment il permet de partager un objet observable dans plusieurs vues, et quand l’utiliser à la place de @StateObject, @ObservedObject ou @Binding.


1. L’idée à comprendre

@EnvironmentObject permet de partager un objet observable dans une partie de l’application sans devoir le passer manuellement de vue en vue.

Sans @EnvironmentObject, tu pourrais devoir faire ça :

Swift
HomeView(session: session) ProfileView(session: session) SettingsView(session: session) AccountView(session: session)

Avec @EnvironmentObject, tu injectes l’objet une fois au-dessus dans la hiérarchie :

Swift
AppRootView() .environmentObject(session)

Puis les vues qui en ont besoin le récupèrent directement :

Swift
@EnvironmentObject var session: SessionManager

À retenir :

Texte
@EnvironmentObject = objet partagé accessible par plusieurs vues descendantes.

2. Code minimal

Swift
@MainActor final class SessionManager: ObservableObject { @Published var isLoggedIn = false // État partagé dans l’app func login() { isLoggedIn = true } func logout() { isLoggedIn = false } }

Injection dans la racine :

Swift
@main struct SwiftUICourseApp: App { @StateObject private var session = SessionManager() // Créé une fois au démarrage var body: some Scene { WindowGroup { AppRootView() .environmentObject(session) // Rend session disponible aux vues enfants } } }

Utilisation dans une vue :

Swift
struct ProfileView: View { @EnvironmentObject var session: SessionManager // Récupère l’objet injecté plus haut var body: some View { Button("Se déconnecter") { session.logout() } } }

3. Exemple avec une racine d’app

@EnvironmentObject est souvent utilisé pour une session utilisateur.

Swift
struct AppRootView: View { @EnvironmentObject var session: SessionManager var body: some View { if session.isLoggedIn { MainTabView() } else { LoginView() } } }

LoginView peut modifier la session :

Swift
struct LoginView: View { @EnvironmentObject var session: SessionManager var body: some View { VStack(spacing: 16) { Text("Connexion") .font(.title) Button("Se connecter") { session.login() } .buttonStyle(.borderedProminent) } .padding() } }

ProfileView peut aussi y accéder :

Swift
struct ProfileView: View { @EnvironmentObject var session: SessionManager var body: some View { VStack(spacing: 16) { Text("Profil") .font(.title) Button("Se déconnecter") { session.logout() } } .padding() } }

Ici, LoginView, ProfileView et AppRootView utilisent le même SessionManager.


4. Exemple avec une TabView

Swift
struct MainTabView: View { var body: some View { TabView { HomeView() .tabItem { Label("Accueil", systemImage: "house") } ProfileView() .tabItem { Label("Profil", systemImage: "person") } } } }

Même si MainTabView ne transmet pas directement session, ProfileView peut quand même y accéder avec :

Swift
@EnvironmentObject var session: SessionManager

Parce que session a été injecté plus haut dans :

Swift
AppRootView() .environmentObject(session)

5. Quand utiliser @EnvironmentObject

Utilise @EnvironmentObject pour des objets partagés par plusieurs vues éloignées.

Exemples fréquents :

Swift
SessionManager AuthManager UserManager ThemeManager CartManager AppRouter AppSettings

Cas typiques :

  • session utilisateur ;
  • état de connexion ;
  • profil utilisateur courant ;
  • panier dans une app e-commerce ;
  • thème global ;
  • navigation globale ;
  • réglages globaux de l’app.

6. Quand ne pas utiliser @EnvironmentObject

N’utilise pas @EnvironmentObject pour tout.

Si la donnée appartient à une seule vue :

Swift
@State private var isShowingSheet = false

Si la donnée est seulement transmise à une sous-vue proche :

Swift
@Binding var searchText: String

Si une sous-vue reçoit un ViewModel précis :

Swift
@ObservedObject var viewModel: ProfileViewModel

@EnvironmentObject est surtout utile quand passer l’objet manuellement deviendrait lourd ou répétitif.


7. @EnvironmentObject vs @StateObject

@StateObject crée et possède l’objet.

Swift
@StateObject private var session = SessionManager()

@EnvironmentObject récupère un objet injecté plus haut.

Swift
@EnvironmentObject var session: SessionManager

Résumé :

Texte
Créer et garder l’objet → @StateObject Lire un objet partagé injecté → @EnvironmentObject

Dans une app, tu peux utiliser les deux ensemble :

Swift
@StateObject private var session = SessionManager() AppRootView() .environmentObject(session)

Puis dans les vues :

Swift
@EnvironmentObject var session: SessionManager

8. @EnvironmentObject vs @ObservedObject

@ObservedObject est transmis explicitement :

Swift
ProfileView(viewModel: profileViewModel)

@EnvironmentObject est récupéré automatiquement depuis l’environnement :

Swift
@EnvironmentObject var session: SessionManager

Résumé :

Texte
Objet passé directement à une vue → @ObservedObject Objet global partagé dans une hiérarchie → @EnvironmentObject

9. Preview avec @EnvironmentObject

Si une vue utilise @EnvironmentObject, la preview doit fournir l’objet.

Swift
#Preview { ProfileView() .environmentObject(SessionManager()) }

Si tu oublies .environmentObject(...), la preview peut planter au lancement.

Exemple complet :

Swift
#Preview("Connecté") { let session = SessionManager() session.isLoggedIn = true return AppRootView() .environmentObject(session) }

Si cette syntaxe pose problème dans Xcode, crée une vue wrapper :

Swift
#Preview { AppRootPreviewWrapper() } struct AppRootPreviewWrapper: View { @StateObject private var session = SessionManager() var body: some View { AppRootView() .environmentObject(session) .onAppear { session.isLoggedIn = true } } }

10. Exemple avec un thème global

@EnvironmentObject peut aussi servir pour un thème d’app.

Swift
@MainActor final class ThemeManager: ObservableObject { @Published var isDarkModeEnabled = false }

Injection :

Swift
@main struct SwiftUICourseApp: App { @StateObject private var theme = ThemeManager() var body: some Scene { WindowGroup { AppRootView() .environmentObject(theme) } } }

Utilisation :

Swift
struct ThemeSettingsView: View { @EnvironmentObject var theme: ThemeManager var body: some View { Toggle("Dark mode", isOn: $theme.isDarkModeEnabled) .padding() } }

11. Exemple avec un panier

Swift
@MainActor final class CartManager: ObservableObject { @Published var items: [String] = [] var count: Int { items.count } func add(_ item: String) { items.append(item) } }

Vue produit :

Swift
struct ProductView: View { @EnvironmentObject var cart: CartManager var body: some View { Button("Ajouter au panier") { cart.add("Produit") } } }

Vue tabbar :

Swift
struct MainTabView: View { @EnvironmentObject var cart: CartManager var body: some View { TabView { ProductView() .tabItem { Label("Produits", systemImage: "bag") } Text("Articles : \(cart.count)") .tabItem { Label("Panier", systemImage: "cart") } .badge(cart.count) } } }

Ici, plusieurs vues utilisent le même CartManager.


12. Points à connaître

L’objet doit être injecté plus haut

Si une vue déclare :

Swift
@EnvironmentObject var session: SessionManager

Alors une vue au-dessus doit faire :

Swift
.environmentObject(session)

Sinon l’app peut planter au runtime.


@EnvironmentObject crée une dépendance implicite

Avec @ObservedObject, tu vois la dépendance dans l’init :

Swift
ProfileView(viewModel: viewModel)

Avec @EnvironmentObject, la dépendance est moins visible :

Swift
ProfileView()

mais la vue a quand même besoin de :

Swift
@EnvironmentObject var session: SessionManager

Donc utilise @EnvironmentObject pour les vrais objets globaux, pas pour tout.


Évite les EnvironmentObjects trop énormes

Un seul objet global qui contient tout peut devenir difficile à maintenir.

Pas idéal :

Swift
AppManager auth profile settings notifications cart theme API navigation

Préférable : plusieurs objets spécialisés si nécessaire.

Exemple :

Swift
SessionManager ThemeManager CartManager AppRouter

Résumé

À retenir :

  • @EnvironmentObject permet de récupérer un objet partagé injecté plus haut ;
  • il évite de passer le même objet manuellement à beaucoup de vues ;
  • l’objet doit respecter ObservableObject ;
  • ses propriétés importantes sont souvent en @Published ;
  • on crée souvent l’objet avec @StateObject dans la racine ;
  • on l’injecte avec .environmentObject(...) ;
  • on le lit avec @EnvironmentObject dans les vues enfants ;
  • si tu oublies l’injection, l’app ou la preview peut planter ;
  • utilise-le pour des objets réellement partagés comme session, thème, panier ou router.