Greetings, traveler!
Since Apple introduced the Observation framework in iOS 17, life has become easier, and code has become faster. With the new Observation framework, you can migrate from the ObservableObject
protocol to the Observable
macro and from the EnviromentObject
to the Enviroment
. With Observable
macro, all properties become observable by default. But what if we want to create bindings to the properties of enviroment objects? Let’s try this out.
Before we delve into the new Observation framework, let’s consider how this was done the ‘old way ‘.
final class Model: ObservableObject {
@Published var name: String = ""
}
struct SwiftUIView: View {
@EnvironmentObject var model: Model
var body: some View {
TextField("", text: $model.name)
}
}
Now, let’s rewrite the code with the observation framework. Spoiler: we will get an error.
@Observable
final class Model {
var name: String = ""
}
struct SwiftUIView: View {
@Environment(Model.self) var model
var body: some View {
TextField("", text: $model.name) // ❌ Cannot find '$model' in scope
}
}
To use @Environment
for binding, you can create a Bindable variable. Apple offers the @Bindable
property wrapper to bind with observable types.
struct SwiftUIView: View {
@Environment(Model.self) var model
var body: some View {
@Bindable var model = model
TextField("", text: $model.name)
}
}
You can also inject bindable objects like this into child views.
struct SwiftUIView: View {
@Environment(Model.self) var model
var body: some View {
SwiftUIView2(model: model)
}
}
struct SwiftUIView2: View {
@Bindable var model: Model
var body: some View {
TextField("", text: $model.name)
}
}
Or use it like this:
struct SwiftUIView: View {
@Environment(Model.self) var model
var body: some View {
TextField("", text: Bindable(model).name)
}
}
The question is, how much will it cost? If we are talking about performance. The second option looks a bit cheaper.
Custom Bindable Environment
In addition, Iet’s create a custom EnvironmentBindable
propertyWrapper to make our code a bit cleaner.
@propertyWrapper
struct EnvironmentBindable<T: Observable & AnyObject>: DynamicProperty {
@Environment(T.self) private var object
var wrappedValue: T {
_object.wrappedValue
}
var projectedValue: Bindable<T> {
@Bindable var wrappedValue = wrappedValue
return $wrappedValue
}
}
Conclusion
That looks a bit weird, isn’t it? We may get another solution from Apple shortly. But for now, this is the only approved way to use Bindings with Observable objects.
Note
By the way, there is another suspicious API from the Observation framework. Don’t hesitate to check it out here.
If you enjoyed this article, please feel free to follow me on my social media: