@EnvironmentObject & the Environment
Passing a shared model through every view gets tedious fast. SwiftUI's environment lets you inject an object once near the top and read it anywhere below with @EnvironmentObject . You'll also meet @Environment for built-in values like dark mode and the dismiss action.
Learn @EnvironmentObject & the Environment in our free Swift course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…
Part of the free Swift course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
Building iOS Apps with SwiftUI • Intermediate
What You'll Learn in This Lesson
1️⃣ Injecting with .environmentObject(_)
Create your shared model (an ObservableObject ) and own it with @StateObject . Then attach .environmentObject(_) to an ancestor view. Everything below it can now reach that instance.
2️⃣ Reading with @EnvironmentObject
A descendant declares @EnvironmentObject var session: Session and SwiftUI supplies the injected instance — even if many views sit between them. Intermediate views never have to forward it.
3️⃣ @Environment — Built-In Values
@Environment(\\.key) reads built-in keyed values rather than your own objects: the current colorScheme (light/dark), the locale , the dismiss action, and more.
Your turn. Fill in the blanks to inject an object and read it in a descendant.
Create a Theme: ObservableObject with a @Published var isDark = false . Inject it at the root, then build two unrelated child views several levels deep: one with a toggle button that flips isDark , and another that displays the current theme. Confirm both react without passing the object through any intermediate view.
Practice quiz
What problem does @EnvironmentObject solve?
- Storing data on disk
- Passing a shared object deep down without threading it through every view
- Drawing animations
- Networking
Answer: Passing a shared object deep down without threading it through every view. @EnvironmentObject injects a shared model into the environment so deep descendants read it directly.
How do you inject an object into the environment?
- With the .environmentObject(_) modifier on a parent view
- By writing @State
- With a global variable
- It is automatic for all objects
Answer: With the .environmentObject(_) modifier on a parent view. Call .environmentObject(myObject) on an ancestor view to place it in the environment for descendants.
How does a descendant view read an injected object?
- @State var model
- @Binding var model
- @EnvironmentObject var model: MyModel
- import model
Answer: @EnvironmentObject var model: MyModel. Declare @EnvironmentObject var model: MyModel and SwiftUI supplies the injected instance.
What must the injected type conform to (pre-iOS 17)?
- View
- ObservableObject
- Codable
- Hashable
Answer: ObservableObject. An @EnvironmentObject is an ObservableObject, so its @Published changes refresh observing views.
What happens if you read an @EnvironmentObject that was never injected?
- It returns nil
- The app crashes at runtime
- It returns a default
- It compiles to an empty view
Answer: The app crashes at runtime. Forgetting to inject the object crashes at runtime because there is nothing to supply.
What does @Environment(\.colorScheme) read?
- A custom model
- A built-in environment value (light or dark mode)
- The view's frame
- User defaults
Answer: A built-in environment value (light or dark mode). @Environment reads built-in environment values like colorScheme, locale, or dismiss.
What is the difference between @EnvironmentObject and @Environment?
- They are identical
- @EnvironmentObject reads an injected ObservableObject; @Environment reads keyed environment values
- @Environment is for networking
- @EnvironmentObject is deprecated
Answer: @EnvironmentObject reads an injected ObservableObject; @Environment reads keyed environment values. @EnvironmentObject pulls a shared object; @Environment(\.key) reads a specific keyed value.
Which value would you read with @Environment(\.dismiss)?
- A color
- An action to close the current screen
- The device name
- A binding to text
Answer: An action to close the current screen. @Environment(\.dismiss) gives a dismiss action you call to close a presented view.
Why is @EnvironmentObject convenient for app-wide state?
- It is faster than a database
- Any descendant can read it without manual passing through intermediaries
- It encrypts data
- It avoids using classes
Answer: Any descendant can read it without manual passing through intermediaries. It removes the need to pass the object through every intermediate view's initializer.
Where do you typically inject app-wide objects?
- In a leaf view
- Inside a Button
- In a Text view
- Near the root, e.g. on the top-level view in the App
Answer: Near the root, e.g. on the top-level view in the App. Inject near the root so the whole view tree below it can access the shared object.