Environment is a very powerful way to pass data from parent down to children.
It is like a global singleton.
Don’t cringe, because that’s the direction SwiftUI is taking. So embrace it.
1. Custom Environment
The steps involve creating a key, injecting into EnvironmentValues
, and then passing it (from parent to children).
/// 1. Declare a key with the type, and a default
struct MyEnvironmentKey: EnvironmentKey {
static var defaultValue: CGFloat { 0 }
}
/// 2. Provide extension to EnvironmentValues
extension EnvironmentValues {
var foo: CGFloat {
get { self[MyEnvironmentKey.self] }
set { self[MyEnvironmentKey.self] = newValue }
}
}
/// 3. Pass to children using `environment`
ParentView()
.environment(\.foo, 1.23)
/// 4. Children declare and use
@Environment(\.foo) var foo
NOTE: If no one sets the environment, the defaultValue
will be used. Therefore, some value will always be present. This is different from EnvironmentObject
.
2. Custom EnvironmentObject
The difference is that EnvironmentObject
does NOT require a key.
Because it simply uses the type as the “key”.
You start by creating your custom type, which has to implement ObservableObject
. Yup, that’s the same view model you’ll have.
/// 1. Declare the custom type
class MyModel: ObservableObject {
@Published var foo: String = "a"
}
struct ChildView: View {
/// 2. Use `@EnvironmentObject` with the custom type
@EnvironmentObject var model: MyModel
var body: some View {
Text(model.foo)
}
}
/// 3. Parent to pass down the model
struct ParentView: View {
@ObservedObject var model = MyModel()
var body: some View {
model.foo = "b" // eg. Parent could update it
return VStack {
ChildView()
ChildView()
}
.environmentObject(model)
}
}
NOTE: You must create your EnvironmentObject
and set it from somewhere. If it is never set and the child view uses it, the app will crash.
PITFALL: Using in modal sheet
Environment is passed down from a parent to all it’s descendant.
Typically you pass on the root view.
However, if you use a sheet, it will be a sibling to the root (NOT a descendant). So you have to set the environment in the sheet.
.sheet(isPresented: $presentDetailsView) {
DetailsView()
.environmentObject(self.state)
}
PITFALL: Xcode preview crashed
Cannot preview in this file - YourApp.app may have crashed
This is because you didn’t pass an environment for the preview. Simply pass it in your PreviewProvider
.