SwiftUI Environment: How to share data between views in 2024

Mark Moeykens
May 29, 2023

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.

Concepts

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.

SwiftUI @Environment in one central place with 3 views being able to access it from within your app. This is a page from Big Mountain Studio's book "SwiftUI Essentials".

(This is a page from the book “SwiftUI Essentials: Architecting Scalable & Maintainable Apps”)

1. Create Your Observable Object

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"
}

2. Add Object to the Environment

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.

Container View Example

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.

SwiftUI environment object modifier to attach an observable object to a tab view. This is a page from Big Mountain Studio's book "Working with Data in SwiftUI".

(This is a page from the book “SwiftUI Essentials: Architecting Scalable & Maintainable Apps”)

3. Access the Object in the Environment

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.

Child View Example

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:

SwiftUI environment object property wrapper used to access an observable object. This is a page from Big Mountain Studio's book "SwiftUI Essentials".

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

Warning: EnvironmentObject and NavigationStack

Imagine this view hierarchy:

  1. View 1 has a NavigationStack with a NavigationLink to View 2

  2. View 2 has a NavigationLink to View 3

  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")
        }
    }
}

Question: Where do you set the .environment modifier?

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.

Put Environment Object on the NavigationStack

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

Previews

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.

Conclusion

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

Learn More

You can also learn more from my book SwiftUI Essentials: Architecting Scalable & Maintainable Apps:

Cover of the book “SwiftUI Essentials” available at https://www.bigmountainstudio.com/essentials

Learn architecture and data in SwiftUI with "SwiftUI Essentials”.

Your Learning Path Quick Links

  1. Create UI with SwiftUI Views Mastery (beginner)

  2. Architect your app using SwiftUI Essentials (beginner)

  3. Improve your app's UX with SwiftUI Animations Mastery (junior)

  4. Save & sync your app's data with Core Data Mastery -or- SwiftData Mastery (junior) 

  5. React to and manipulate data using Combine Mastery in SwiftUI (advanced)

5 comments

Steven O'Toole
May 29, 2023

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())
    }
}
Chris Parker
May 31, 2023

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.

Melissa Bain
Jan 29

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

Melissa Bain
Jan 30

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.