NEW: Got a question? Get help on our new forums! >>

Reading custom values from the environment with @EnvironmentObject

Paul Hudson    @twostraws   

SwiftUI’s environment lets us work with values from an external source, which is useful for things like reading a Core Data context or a view’s presentation mode. But we can also send custom objects into the environment and have them read back out later on, which allows us to share data more easily in complex applications.

You’ve seen how @State is used to work with state that is local to a single view, and how @ObservedObject lets us pass one object from view to view so we can share it. Well, @EnvironmentObject takes that one step further: we can place an object into the environment so that any child view can automatically have access to it.

Imagine we had multiple views in an app, all lined up in a chain: view A shows view B, view B shows view C, C shows D, and D shows E. View A and E both want to access the same object, but to get from A to E you need to go through B, C, and D, and they don’t care about that object. If we were using @ObservedObject we’d need to pass our object from each view to the next until it finally reached view E where it could be used, which is annoying because B, C, and D don’t care about it. With @EnvironmentObject view A can put the object into the environment, view E can read the object out from the environment, and views B, C, and D don’t have to know anything happened – it’s much nicer.

One complexity with environment objects is what constitutes a child, because what environment objects a view has access to depends on its parent views. For example, if view A has access to an environment object and view B is inside view A – i.e., view B is placed in the body property of A – then view B also has access to that environment object. This means if view A is a navigation view, then all views that are pushed onto the navigation stack have access to the same environment. However, if view A presents view B as a sheet then they don’t automatically share environment data, and we need to send it in by hand. Apple has described this sheet situation as a bug that they want to fix, so I’m hopeful this will change in a future update to SwiftUI.

There’s one last thing before I show you some code: environment objects use the same ObservableObject protocol you’ve already learned, and SwiftUI will automatically make sure all views that share the same environment object get updated when it changes.

OK, let’s look at some code that shows how to share data between two views using environment objects. First, here’s some basic data we can work with:

class User: ObservableObject {
    @Published var name = "Taylor Swift"
}

As you can see, that uses ObservableObject and @Published just like we’ve learned previously – all that knowledge you built up continues to pay off.

Next we can define two SwiftUI views to use our new class. These will use the @EnvironmentObject property wrapper to say that the value of this data comes from the environment rather than being created locally:

struct EditView: View {
    @EnvironmentObject var user: User

    var body: some View {
        TextField("Name", text: $user.name)
    }
}

struct DisplayView: View {
    @EnvironmentObject var user: User

    var body: some View {
        Text(user.name)
    }
}

That @EnvironmentObject property wrapper will automatically look for a User instance in the environment, and place whatever it finds into the user property. If it can’t find a User in the environment your code will just crash, so please be careful.

We can now bring those two views together in one place, and send in a User instance for them to work with:

struct ContentView: View {
    let user = User()

    var body: some View {
        VStack {
            EditView().environmentObject(user)
            DisplayView().environmentObject(user)
        }
    }
}

And that’s all it takes to get our code working – you can run the app now and change the textfield to see its value appear in the text view below. Of course, we could have represented this in a single view, but this way you can see exactly how seamless the communication is when using environment objects.

Now, here’s the clever part. Try rewriting the body property of ContentView to this”

VStack {
    EditView()
    DisplayView()
}
.environmentObject(user)

What you’ll find is that it works identically. We’re now placing user into the environment of ContentView, but because both EditView and DisplayView are children of ContentView they inherit its environment automatically.

Now, you might wonder how SwiftUI makes the connection between .environmentObject(user) and @EnvironmentObject var user: User – how does it know to place that object into the correct property?

Well, you’ve seen how dictionaries let us use one type for the key and another for the value. The environment effectively lets us use data types themselves for the key, and instances of the type as the value. This is a bit mind bending at first, but imagine it like this: the keys are things like Int, String, and Bool, with the values being things like 5, “Hello”, and true, which means we can say “give me the Int” and we’d get back 5.

Hacking with Swift is sponsored by Instabug

SPONSORED Catch bugs as soon as they happen and know exactly why a crash occurred by integrating Instabug's SDK in one minute. You will automatically receive device data, network logs, and reproduction steps with every bug and crash report.

Learn more and get started for free

Sponsor Hacking with Swift and reach the world's largest Swift community!

Snapthread is a casual video editor and slideshow maker that makes discovering, compiling and sharing your favorite memories effortless.

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: 3.7/5