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:

import SwiftUI

class SettingsModel: ObservableObject {
    @Published var isDarkMode = false
}

Example injection setup (for reference):

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.

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

Examples you can copy

Example 1: Shared auth state

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

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

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:

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:

ContentView()
    .environmentObject(SettingsModel())

Mistake 2: Declaring the wrong type

What you might do:

@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.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.