LAST CHANCE: Save 50% on all my Swift books and bundles! >>

How to use @EnvironmentObject to share data between views

Paul Hudson    @twostraws   

Updated for Xcode 13.0

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 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 view.
struct ContentView: View {
    @StateObject var settings = GameSettings()

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

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

Download this as an Xcode project

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 view, which means all views inside the navigation view can read that object if they want it, as well as any views that get shown by the navigation view.
  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 view, 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.

Click here to save 50% on all my books and bundles!

Similar solutions…

BUY OUR BOOKS
Buy Pro Swift Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift (Vapor Edition) Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Server-Side Swift (Kitura Edition) Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.4/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.