How to Use @EnvironmentObject in SwiftUI
What you’ll build or solve
Use @EnvironmentObject to read shared app state from the SwiftUI environment without passing it through initializers. You’ll declare an environment object in a view and use its values and bindings.
When this approach works best
@EnvironmentObject works best when:
Learn Swift on Mimo
- Many views need the same shared model, like auth state, settings, or an active account.
- Passing a model through several view layers would clutter initializers.
- You want one shared source of truth across a section of your app.
This is a bad idea for small, local state that only one view needs. In those cases, use @State, @Binding, or @ObservedObject.
Prerequisites
- Xcode with a SwiftUI project
- A shared model that conforms to
ObservableObjectand uses@Publishedfor changing properties - The model is injected into the view hierarchy at or above the view that reads it
Example model used in this guide:
importSwiftUI
classSettingsModel:ObservableObject {
@PublishedvarisDarkMode=false
}
Example injection setup (for reference):
importSwiftUI
@main
structMyApp:App {
@StateObjectprivatevarsettings=SettingsModel()
varbody:someScene {
WindowGroup {
ContentView()
.environmentObject(settings)
}
}
}
Step-by-step instructions
Step 1: Access the environment object with @EnvironmentObject
Declare the shared model using @EnvironmentObject. SwiftUI supplies the instance from the environment.
importSwiftUI
structContentView:View {
@EnvironmentObjectvarsettings:SettingsModel
varbody:someView {
VStack(spacing:12) {
Toggle("Dark Mode",isOn: $settings.isDarkMode)
Text(settings.isDarkMode?"Dark Mode On" :"Light Mode Off")
}
.padding()
}
}
What to look for
- The declared type must match the injected object type exactly.
- Use
$settings.propertyto bind controls to published properties. - Updates happen when
@Publishedproperties change.
Examples you can copy
Example 1: Shared auth state
CSS
importSwiftUI
classAuthModel:ObservableObject {
@PublishedvarisLoggedIn=false
}
structAccountStatusView:View {
@EnvironmentObjectvarauth:AuthModel
varbody:someView {
VStack(spacing:12) {
Text(auth.isLoggedIn?"Logged in" :"Logged out")
Button(auth.isLoggedIn?"Log Out" :"Log In") {
auth.isLoggedIn.toggle()
}
}
.padding()
}
}
Example 2: Global settings toggle
importSwiftUI
classAppSettings:ObservableObject {
@PublishedvarnotificationsOn=true
}
structNotificationsView:View {
@EnvironmentObjectvarsettings:AppSettings
varbody:someView {
Toggle("Notifications",isOn: $settings.notificationsOn)
.padding()
}
}
Example 3: Shared cart model
importSwiftUI
structCartItem:Identifiable {
letid=UUID()
letname:String
letprice:Double
}
classCartModel:ObservableObject {
@Publishedvaritems: [CartItem]= []
vartotal:Double {
items.reduce(0) {$0+$1.price }
}
funcadd(_item:CartItem) {
items.append(item)
}
}
structCartSummaryView:View {
@EnvironmentObjectvarcart:CartModel
varbody:someView {
VStack(spacing:8) {
Text("Items: \(cart.items.count)")
Text("Total: \(cart.total)")
}
.padding()
}
}
Common mistakes and how to fix them
Mistake 1: Forgetting to inject the environment object
What you might do:
CSS
structContentView:View {
@EnvironmentObjectvarsettings:SettingsModel
}
Why it breaks: SwiftUI crashes at runtime because it cannot find a SettingsModel in the environment.
Correct approach: Inject the object above this view:
ContentView()
.environmentObject(SettingsModel())
Mistake 2: Declaring the wrong type
What you might do:
@EnvironmentObjectvarsettings:SettingsModel
But you injected an AppSettings.
Why it breaks: SwiftUI cannot match the requested type, so the environment lookup fails.
Correct approach: Make the injected type and the declared type identical.
Troubleshooting
- If you see a crash about a missing environment object, inject it with
.environmentObject(...)above the view that reads it. - If the UI does not update, confirm the model’s changing properties are marked
@Published. - If a control needs a binding and you get a type error, use
$model.propertyinstead ofmodel.property. - If previews crash, inject the object in the preview:
#Preview {
ContentView()
.environmentObject(SettingsModel())
}
Quick recap
- Declare shared state in a view with
@EnvironmentObject var model: ModelType. - The model must be injected above the view in the hierarchy.
- The declared type must match the injected type.
- Use
$model.propertyto bind controls to published values. - Mark changing model properties with
@Publishedso views refresh.
Join 35M+ people learning for free on Mimo
4.8 out of 5 across 1M+ reviews
Check us out on Apple AppStore, Google Play Store, and Trustpilot