How to use @EnvironmentObject to share data between views
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:
- A
GameSettings
class that contains a single published property calledscore
. - A
ScoreView
view that expects to receive aGameSettings
object in the environment, and displays itsscore
property. - A
ContentView
view that creates aGameSettings
object, has a button to add 1 to itsscore
property, and aNavigationLink
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)
}
}
There are several important parts I want to pick out in that code:
- Just like
@StateObject
and@ObservedObject
, all classes used with@EnvironmentObject
must conform to theObservableObject
protocol. - 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. - 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. - 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. - We didn't need to explicitly associate the
GameSettings
instance in the environment with thesettings
property inScoreView
– SwiftUI automatically figured out that it has aGameSettings
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.