< How to use @ObservedObject to manage state from external objects | How to send state updates manually using objectWillChange > |
Updated for Xcode 14.2
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:
GameSettings
class that contains a single published property called score
.ScoreView
view that expects to receive a GameSettings
object in the environment, and displays its score
property.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 project
There are several important parts I want to pick out in that code:
@StateObject
and @ObservedObject
, all classes used with @EnvironmentObject
must conform to the ObservableObject
protocol.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.@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.GameSettings
object we created.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.
SPONSORED From March 20th to 26th, you can join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.