The SwiftUI Guide to Clean State Management and Dependency Injection

Why Use @StateObject in the App File?

In SwiftUI, you use @StateObject when you want to create and manage a view model that your whole app will use.

What is @StateObject?

@StateObject is how you create and keep a view model (or any observable class) in SwiftUI.

SwiftUI will watch it for changes, and automatically update the UI when something inside it changes.

It makes sure the view model is only created once and stays alive as long as the app runs.

Example

ViewModel as ObservableObject

SwiftUI watches ObservableObject for changes and updates the view.

import SwiftUI
import Combine

class MyViewModel: ObservableObject {
    @Published var counter: Int = 0

    func increment() {
        counter += 1
    }
}

with @StateObject SwiftUI will watch it for changes, and automatically update the UI

Don't pass state — inject it with .environmentObject()"

@main
struct MyApp: App {
    @StateObject private var viewModel = MyViewModel()

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(viewModel) // Inject into environment
        }
    }
}

All views using @EnvironmentObject automatically access the same shared data injected in the App file.

struct ContentView: View {
    @EnvironmentObject var viewModel: MyViewModel

    var body: some View {
        VStack {
            Text("Counter: \(viewModel.counter)")
            Button("Increment") {
                viewModel.increment()
            }
        }
    }
}

@ObservedObject Vs @StateObject

Passing State with @ObservedObject

You manually pass the view model to each view that needs it:

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            // Manually create and pass the view model
            ContentView(viewModel: MyViewModel())
        }
    }
}
struct ContentView: View {
    @ObservedObject var viewModel: MyViewModel

    var body: some View {
        Text("Count: \(viewModel.counter)")
    }
}

By @ObservedObject, the App file creates the view model and passes it directly to ContentView.
By @EnvironmentObject, the App file injects the view model into the environment, and ContentView accesses it automatically.

Don’t pass state – inject it with .environmentObject() — own it, share it, and keep your SwiftUI clean.

10
Subscribe to my newsletter

Read articles from Tabassum Akter Nusrat directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Tabassum Akter Nusrat
Tabassum Akter Nusrat

Former Android dev at Samsung R&D, now diving deep into iOS development. Writing daily about Swift Concurrency, SwiftUI, and the journey of building real-world apps. Passionate about clean architecture, mental health tech, and learning in public.