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:

  • 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 ObservableObject and uses @Published for 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.property to bind controls to published properties.
  • Updates happen when @Published properties change.

Examples you can copy

Example 1: Shared auth state

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:

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.property instead of model.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.property to bind controls to published values.
  • Mark changing model properties with @Published so views refresh.