Skip to main content

How to use @EnvironmentObject to share data between views

About 4 minSwiftSwiftUIArticle(s)bloghackingwithswift.comcrashcourseswiftswiftuixcodeappstore

How to use @EnvironmentObject to share data between views 관련

SwiftUI by Example

Back to Home

How to use @EnvironmentObject to share data between views | SwiftUI by Example

How to use @EnvironmentObject to share data between views

Updated for Xcode 15

For data that should be shared with many views in your app, SwiftUI gives us the @EnvironmentObject property wrapper. This lets us share model data anywhere it's needed, while also ensuring that our views automatically stay updated when that data changes.

Think of @EnvironmentObject as a smarter, simpler way of using @ObservedObject on lots of views. Rather than creating some data in view A, then passing it to view B, then view C, then view D before finally using it, you can create it in view A and put it into the environment so that views B, C, and D will automatically have access to it.

Just like @ObservedObject, you never assign a value to an @EnvironmentObject property. Instead, it should be passed in from elsewhere, and ultimately you're probably going to want to use @StateObject to create it somewhere.

However, unlike @ObservedObject we don't pass our objects into other views by hand. Instead, we use send the data into a modifier called environmentObject(), which makes the object available in SwiftUI's environment for that view plus any others inside it.

Note

Environment objects must be supplied by an ancestor view – if SwiftUI can't find an environment object of the correct type you'll get a crash. This applies for previews too, so be careful.

To demonstrate environment objects, we're going to define three things:

  1. A GameSettings class that contains a single published property called score.
  2. A ScoreView view that expects to receive a GameSettings object in the environment, and displays its score property.
  3. A ContentView view that creates a GameSettings object, has a button to add 1 to its score property, and a NavigationLink to show the detail view.

Here's the code:

// Our observable object class
class GameSettings: ObservableObject {
    @Published var score = 0
}

// A view that expects to find a GameSettings object
// in the environment, and shows its score.
struct ScoreView: View {
    @EnvironmentObject var settings: GameSettings

    var body: some View {
        Text("Score: \(settings.score)")
    }
}

// A view that creates the GameSettings object,
// and places it into the environment for the
// navigation stack.
struct ContentView: View {
    @StateObject var settings = GameSettings()

    var body: some View {
        NavigationStack {
            VStack {
                // A button that writes to the environment settings
                Button("Increase Score") {
                    settings.score += 1
                }

                NavigationLink {
                    ScoreView()
                } label: {
                    Text("Show Detail View")
                }
            }
            .frame(height: 200)
        }
        .environmentObject(settings)
    }
}

Download this as an Xcode projectopen in new window

There are several important parts I want to pick out in that code:

  1. Just like @StateObject and @ObservedObject, all classes used with @EnvironmentObject must conform to the ObservableObject protocol.
  2. We put GameSettings into the environment for the navigation stack, which means all views inside the navigation stack can read that object if they want it, as well as any views that get shown by the navigation stack.
  3. When you use the @EnvironmentObject property wrapper, you declare the type of thing you expect to receive but you do not create it – you're expecting to receive it from the environment, after all.
  4. Because our detail view is shown inside the navigation stack, it will get access to the same environment, which in turn means it can read the GameSettings object we created.
  5. We didn't need to explicitly associate the GameSettings instance in the environment with the settings property in ScoreView – SwiftUI automatically figured out that it has a GameSettings instance in the environment, so that's the one it uses.

Warning

Now that our views rely on an environment object being present, it's important that you also update your preview code to provide some example settings to use. For example, using something like ScoreView().environmentObject(GameSettings()) for your preview ought to do it.

If you need to add multiple objects to the environment, you should add multiple environmentObject() modifiers – just call them one after the other.

Similar solutions…
How to let users share content using the system share sheet | SwiftUI by Example

How to let users share content using the system share sheet
What's the difference between @ObservedObject, @State, and @EnvironmentObject? | SwiftUI by Example

What's the difference between @ObservedObject, @State, and @EnvironmentObject?
What is the @EnvironmentObject property wrapper? | SwiftUI by Example

What is the @EnvironmentObject property wrapper?
Adding items to an order with @EnvironmentObject | SwiftUI by Example

Adding items to an order with @EnvironmentObject
How to make a ScrollView snap with paging or between child views | SwiftUI by Example

How to make a ScrollView snap with paging or between child views

이찬희 (MarkiiimarK)
Never Stop Learning.