SwiftUI’s @Environment
is a powerful tool that allows you to share data (with an observable object) between multiple views.
It is particularly useful when you have a complex view hierarchy and need to share the same observable object between views that are not directly connected.
You could pass your observable object through view initializers, from parent to child to child to child, etc. This can be a lot of work.
Using environment objects allows you attach an object (instance of an observable object) to a parent view.
Any descendants (child views, sub views) of that parent view now have access to that object.
(This is a page from the book “SwiftUI Essentials: Architecting Scalable & Maintainable Apps”)
To use Environment, you first need to create an Observable class that has the @Observable
attribute.
This class will hold the data that you want to share between your views.
@Observable
class DeveloperOO {
var name: String = "Awesome developer"
}
Next, you need to create an instance of this class and pass it to your top-level view using the .environment(_:)
modifier.
var body: some View {
ContentView()
.environment(DeveloperOO())
}
In this example, all subviews within ContentView
can access the DeveloperOO
observable object.
Another common example is to add an observable object to a container view, such as a NavigationStack
or TabView
so any descendant views can access it.
(This is a page from the book “SwiftUI Essentials: Architecting Scalable & Maintainable Apps”)
Now, any view in your hierarchy can access this object by declaring an @Environment
property.
struct DetailView: View {
@Environment(DeveloperOO.self) var devInfo
var body: some View {
Text("Hello, \(nameInfo.name)!")
}
}
That’s it!
Note: The type (
DeveloperOO.self
) is what makes this all work.As long as the type is the same, your view will find the matching object in the environment.
In a previous example, an observable object was added to the TabView’s environment.
This is how one of the tab views can access that observable object:
(This is a page from the book “SwiftUI Essentials: Architecting Scalable & Maintainable Apps”)
You can now access and modify the shared data from any view in your hierarchy.
Imagine this view hierarchy:
View 1 has a NavigationStack with a NavigationLink to View 2
View 2 has a NavigationLink to View 3
View 3
View 1 and View 3 both use the same observable object.
Here is View 1:
struct View1: View {
@State var oo = DeveloperOO()
var body: some View {
NavigationStack {
NavigationLink("Navigate to View 2") {
View2()
}
.navigationTitle("Parent View")
}
}
}
You might think you add it to View2 so it can be passed on to View3:
NavigationLink("Navigate to View 2") {
View2()
.environment(oo)
}
❌ This is incorrect!
The observable object would just be in View2's environment which does not include View3. So View3 will not be able to access it.
✅ The correct solution would be to put the environment object on the navigation stack.
struct View1: View {
@State var oo = DeveloperOO()
var body: some View {
NavigationStack {
NavigationLink("Navigate to View 2") {
View2()
.environment(oo) // ❌
}
.navigationTitle("Parent View")
}
.environment(oo) // ✅
.font(.largeTitle)
}
}
All views you navigate to within a navigation stack are considered within the navigation stack's view hierarchy.
Now, every view within the navigation stack can potentially access the observable object through the environment if needed.
To preview a view that uses the @Environment
, you will have to add the .environment(_:)
modifier to the previewed view like this:
#Preview("Child View") {
TabViewOne()
.environment(DeveloperInfo())
}
Use the environment
modifier to add the observable to the view or else you will get a preview error.
The parent view doesn't need this because it is the view that sets the environment object.
The @Environment
property wrapper is a powerful tool for sharing data between different views in your SwiftUI application.
By passing an object down the view hierarchy using the environment(_:)
modifier, you can access it from any view without having to pass it explicitly as a parameter.
You use @Environment when:
You want to create a global place for views to access data
You want to create two-way bindings between the data and the UI
You want multiple views to simultaneously get updated when one observable object is updated
You can also learn more from my book SwiftUI Essentials: Architecting Scalable & Maintainable Apps:
Learn architecture and data in SwiftUI with "SwiftUI Essentials”.
Create UI with SwiftUI Views Mastery (beginner)
Architect your app using SwiftUI Essentials (beginner)
Improve your app's UX with SwiftUI Animations Mastery (junior)
Save & sync your app's data with Core Data Mastery -or- SwiftData Mastery (junior)
React to and manipulate data using Combine Mastery in SwiftUI (advanced)
Is there any way to get EnvironmentObject to work with subclasses? Wouldn't that be useful?
class NameInfo: ObservableObject {
@Published var name = "Mark Moeykens"
func doSomething() { print(String(describing: self), #function) }
}
class NameInfoSubclass: NameInfo {
override func doSomething() { print(String(describing: self), #function) }
}
struct DetailView: View {
@EnvironmentObject var nameInfo: NameInfo
var body: some View {
// Fatal error: No ObservableObject of type NameInfo found
Text("Hello, \(nameInfo.name)!")
.onAppear(perform: doSomething)
}
private func doSomething() { nameInfo.doSomething() }
}
struct MainView: View {
var body: some View {
DetailView().environmentObject(NameInfoSubclass())
}
}
Fundamentally the EnviromentObject is a "property wrapper type for an observable object supplied by a parent or ancestor view". So it is specifically designed to work with View(s) to make the Published property(s) of the Observable Object available to that View or to child Views.
Hi! I am working through this book. I am currently on 'Fetch - Observable Object'. I am getting an error when I try to compile. Can anyone offer assistance?
Error:
Variable 'self.oo' used before being initialized
I even copied and pasted what was offered in the Project -> OO_Fetch
Hi Mark,
I am working with the latest 'SwiftData Mastery in SwiftUI' book. Page 356 and Page 357.
@State private var oo: OO_FetchOO init(modelContext: ModelContext) { self.oo = OO_FetchOO(modelContext: modelContext)”
Excerpt From
SwiftData Mastery in SwiftUI
Mark Moeykens
This material may be protected by copyright.