NEW: Join my free 100 Days of SwiftUI challenge today! >>

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

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 previously 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 object.

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

In practice things are a little more complex, because we can actually place multiple values into the dictionary, and SwiftUI will connect them to individual properties in our structs. It can do this because the environment uses a special type called KeyValuePairs, which is a cross between a dictionary and an array: like a dictionary it has keys and values, but like an array you can have duplicate values and it retains the order of them.

LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!

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