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:
Swift
import SwiftUI
class SettingsModel: ObservableObject {
@Published var isDarkMode = false
}
Example injection setup (for reference):
Swift
import SwiftUI
@main
struct MyApp: App {
@StateObject private var settings = SettingsModel()
var body: some Scene {
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.
Swift
import SwiftUI
struct ContentView: View {
@EnvironmentObject var settings: SettingsModel
var body: some View {
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
Swift
import SwiftUI
class AuthModel: ObservableObject {
@Published var isLoggedIn = false
}
struct AccountStatusView: View {
@EnvironmentObject var auth: AuthModel
var body: some View {
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
Swift
import SwiftUI
class AppSettings: ObservableObject {
@Published var notificationsOn = true
}
struct NotificationsView: View {
@EnvironmentObject var settings: AppSettings
var body: some View {
Toggle("Notifications", isOn: $settings.notificationsOn)
.padding()
}
}
Example 3: Shared cart model
Swift
import SwiftUI
struct CartItem: Identifiable {
let id = UUID()
let name: String
let price: Double
}
class CartModel: ObservableObject {
@Published var items: [CartItem] = []
var total: Double {
items.reduce(0) { $0 + $1.price }
}
func add(_ item: CartItem) {
items.append(item)
}
}
struct CartSummaryView: View {
@EnvironmentObject var cart: CartModel
var body: some View {
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:
Swift
struct ContentView: View {
@EnvironmentObject var settings: 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:
Swift
ContentView()
.environmentObject(SettingsModel())
Mistake 2: Declaring the wrong type
What you might do:
Swift
@EnvironmentObject var settings: 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:
Swift
#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