Binding with @Observable Macro

abdul ahad
2 min readOct 2, 2024

--

Photo by Gabriel Crismariu on Unsplash

Binding with @Observable

In SwiftUI, when using an @Observable macro, the standard $ syntax (used to create bindings) doesn't work directly because we don’t use property wrappers like @ObservedObject anymore with @Observable.

Example: Using @ObservedObject Traditionally, when using @ObservedObject, we create a binding like this:

struct Counter: View {
@ObservedObject var model: Model
var body: some View {
Stepper("\(model.value)", value: $model.value)
}
}

Here, @ObservedObject provides a projected value ($model) that allows us to bind to model.value.

However, if we switch to using the @Observable macro, the property wrapper (@ObservedObject) is gone, and we can’t create bindings the same way.

Solution: @Bindable

To solve this, SwiftUI introduces @Bindable, a property wrapper specifically for creating bindings with @Observable objects.

Example: Using @Bindable

struct Counter: View {
@Bindable var model: Model

var body: some View {
Stepper("\(model.value)", value: $model.value)
}
}
  • @Bindable wraps the model, allowing us to access its properties and create bindings using the $ syntax.
  • Here, @Bindable automatically handles binding model.value to the Stepper.

Simplifying with Inline Bindable

Instead of declaring @Bindable on the property, we can also create a Bindable object inline.

Example: Inline Bindable

struct ContentView: View {

var model = Model.shared
var body: some View {
Stepper("\(model.value)", value: Bindable(model).value)
}
}

In this example, we use Bindable(model) inline, which effectively does the same thing as the @Bindable wrapper but without the need to declare it as a property wrapper. It allows us to bind model.value directly.

Dynamic Member Lookup

Dynamic member lookup, which simplifies accessing properties of the bindable object.

When SwiftUI uses @Bindable, it leverages the _model.projectedValue[dynamicMember: \.value] syntax behind the scenes to access properties dynamically.

Here’s a version of the Counter view without the syntactic sugar, showing how the bindings actually work internally:

Example: Rewriting @Bindable with Full Syntax

struct Counter: View {
var model: Model { _model.wrappedValue } // Access the original model
var _model: Bindable<Model> // Declare a Bindable wrapper around the model

init(model: Model) {
_model = Bindable(wrappedValue: model) // Initialize the Bindable
}
var body: some View {
Stepper("\(model.value)", value: _model.projectedValue[dynamicMember: \.value])
}
}

Here, model.value is essentially a shortcut for _model.projectedValue[dynamicMember: \.value]. This dynamic member lookup allows SwiftUI to access properties on model in a clean way.

Key Takeaways:

  1. @Observable objects don’t support traditional bindings because they don’t use property wrappers like @ObservedObject.
  2. @Bindable is introduced to wrap Observable objects and allow bindings.
  3. You can use @Bindable as a property wrapper or inline when needed.
  4. SwiftUI uses dynamic member lookup to simplify property access through bindable objects.

This new approach streamlines working with Observable objects while still providing powerful binding capabilities in SwiftUI.

--

--

abdul ahad
abdul ahad

Written by abdul ahad

A software developer dreaming to reach the top and also passionate about sports and language learning

No responses yet