SwiftUI
EnvironmentObject
ViewModel
Swift
iOS Development

How to pass EnvironmentObject into View Model in SwiftUI?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

@EnvironmentObject is designed for SwiftUI views, not for plain view models. If a view model needs data that comes from the environment, the usual solution is to read the environment object in the view layer and inject the dependency into the view model explicitly.

Why direct access in the view model does not work

SwiftUI resolves @EnvironmentObject while it builds a view hierarchy. A view model is just a normal Swift type, so there is no automatic environment lookup happening inside it.

That is why this idea is the wrong mental model:

  • the view declares @EnvironmentObject
  • the view model somehow reads the same property wrapper on its own

Instead, treat the shared object like any other dependency and pass it in through an initializer or method.

Define the shared object normally

Suppose the app stores session state in a shared observable object:

swift
1import SwiftUI
2
3final class SessionStore: ObservableObject {
4    @Published var username: String = "mark"
5    @Published var isLoggedIn: Bool = true
6}

SwiftUI views can read this through the environment. The view model should not know where it came from; it should only receive a SessionStore dependency.

Inject the dependency into the view model

Create the view model so it accepts the shared object explicitly.

swift
1import Combine
2import Foundation
3
4final class ProfileViewModel: ObservableObject {
5    @Published var greeting: String = ""
6    private var cancellables = Set<AnyCancellable>()
7
8    init(session: SessionStore) {
9        session.$username
10            .map { "Hello, \($0)" }
11            .assign(to: &$greeting)
12    }
13}

This approach is testable and keeps the view model independent from SwiftUI environment mechanics.

Bridge environment to the view model with a wrapper view

The view reads the environment object, then passes it to a child view that owns the StateObject.

swift
1import SwiftUI
2
3struct ProfileScreen: View {
4    @EnvironmentObject private var session: SessionStore
5
6    var body: some View {
7        ProfileContent(session: session)
8    }
9}
10
11struct ProfileContent: View {
12    @StateObject private var viewModel: ProfileViewModel
13
14    init(session: SessionStore) {
15        _viewModel = StateObject(wrappedValue: ProfileViewModel(session: session))
16    }
17
18    var body: some View {
19        Text(viewModel.greeting)
20    }
21}

This wrapper pattern is common because the environment value is available in body, while StateObject construction needs explicit input during initialization.

Use narrower dependencies when possible

Often the view model does not need the full environment object. Passing a protocol or smaller service keeps coupling lower.

swift
1protocol SessionProviding {
2    var username: String { get }
3}
4
5extension SessionStore: SessionProviding {}
6
7final class GreetingViewModel: ObservableObject {
8    @Published var greeting: String
9
10    init(session: SessionProviding) {
11        greeting = "Hello, \(session.username)"
12    }
13}

This becomes valuable when the environment object grows over time and only part of it belongs in the feature you are building.

Inject the environment at the app boundary

The environment object still has to be supplied by a parent view or the application root.

swift
1@main
2struct DemoApp: App {
3    @StateObject private var session = SessionStore()
4
5    var body: some Scene {
6        WindowGroup {
7            ProfileScreen()
8                .environmentObject(session)
9        }
10    }
11}

If this injection is missing, SwiftUI will crash when the view attempts to read the environment object. The view model is not the source of that failure; the missing environment configuration is.

Common Pitfalls

The most common mistake is declaring @EnvironmentObject directly inside the view model and expecting SwiftUI to fill it in. SwiftUI only manages that property wrapper for views in the hierarchy.

Another issue is trying to initialize a @StateObject from an environment object in the same view’s init. Environment values are not resolved there, so the code becomes awkward or fails outright. Use a wrapper view instead.

Developers also over-inject. If the view model only needs one property or service, do not pass a huge shared state object by default. A smaller dependency makes the model easier to test and reason about.

Finally, remember that architectural boundaries matter. The view layer should own SwiftUI concerns such as environment lookup, while the view model should own presentation logic built from explicit dependencies.

Summary

  • '@EnvironmentObject belongs in SwiftUI views, not plain view models.'
  • Read the environment object in a view and inject it into the view model explicitly.
  • A wrapper view is the standard pattern when StateObject needs environment-backed input.
  • Prefer narrow protocols or services when the view model does not need the full shared object.
  • Supplying the environment at the app boundary is still required for the pattern to work.

Course illustration
Course illustration

All Rights Reserved.