What’s the difference between @Binding and @Bindable
Published on: June 10, 2023With iOS 17, macOS Sonoma and the other OSses from this year's generation, Apple has made a couple of changes to how we work with data in SwiftUI. Mainly, Apple has introduced a Combine-free version of @ObservableObject
and @StateObject
which takes the shape of the @Observable
macro which is part of a new package called Observation
.
One interesting addition is the @Bindable
property wrapper. This property wrapper co-exists with @Binding
in SwiftUI, and they cooperate to allow developers to create bindings to properties of observable classes. So what's the role of each of these property wrappers? What makes them different from each other?
If you prefer learning by video, the key lessons from this blog post are also covered in this video:
To start, let's look at the @Binding
property wrapper.
When we need a view to mutate data that is owned by another view, we create a binding. For example, our binding could look like this:
struct MyButton: View {
@Binding var count: Int
var body: some View {
Button(action: {
count += 1
}, label: {
Text("Increment")
})
}
}
The example isn' t particularly interesting or clever, but it illustrates how we can write a view that reads and mutates a counter that is owned external to this view.
Data ownership is a big topic in SwiftUI and its property wrappers can really help us understand who owns what. In the case of @Binding
all we know is that some other view will provide us with the ability to read a count
, and a means to mutate this counter.
Whenever a user taps on my MyButton
, the counter increments and the view updates. This includes the view that originally owned and used that counter.
Bindings are used in out of the box components in SwiftUI quite often. For example, TextField
takes a binding to a String
property that your view owns. This allows the text field to read a value that your view owns, and the text field can also update the text value in response to the user's input.
So how does @Bindable
fit in?
If you're famliilar with SwiftUI on iOS 16 and earlier you will know that you can create bindings to @State
, @StateObject
, @ObservedObject
, and a couple more, similar, objects. On iOS 17 we have access to the @Observable
macro which doesn't enable us to create bindings in the same way that the ObservableObject
does. Instead, if our @Observable
object is a class
, we can ask our views to make that object bindable.
This means that we can mark a property that holds an Observable
class instance with the @Bindable
property wrapper, allowing us to create bindings to properties of our class instance. Without @Bindable
, we can't do that:
@Observable
class MyCounter {
var count = 0
}
struct ContentView: View {
var counter: MyCounter = MyCounter()
init() {
print("initt")
}
var body: some View {
VStack {
Text("The counter is \(counter.count)")
// Cannot find '$counter' in scope
MyButton(count: $counter.count)
}
.padding()
}
}
When we make the var counter
property @Bindable
, we can create a binding to the counter's count
property:
@Observable
class MyCounter {
var count = 0
}
struct ContentView: View {
@Bindable var counter: MyCounter
init() {
print("initt")
}
var body: some View {
VStack {
Text("The counter is \(counter.count)")
// This now compiles
MyButton(count: $counter.count)
}
.padding()
}
}
Note that if your view owns the Observable
object, you will usually mark it with @State
and create the object instance in your view. When your Observable
object is marked as @State
you are able to create bindings to the object's properties. This is thanks to your @State
property wrapper annotation.
However, if your view does not own the Observable
object, it wouldn't be appropriate to use @State
. The @Bindable
property wrapper was created to solve this situation and allows you to create bindings to the object's properties.
Usage of Bindable
is limited to classes that conform to the Observable
protocol. The easiest way to create an Observable
conforming object is with the @Observable
macro.
Conclusion
In this post, you learned that the key difference between @Binding
and @Bindable
is in what they do. The @Binding
property wrapper indicates that some piece of state on your view is owned by another view and you have both read and write access to the underlying data.
The @Bindable
property wrapper allows you to create bindings for properties that are owned by Observable
classes. As mentioned earlier,@Bindable
is limted to classes that conform to Observable
and the easiest way to make Observable
objects is the @Observable
macro.
As you now know, these two property wrappers co-exist to enable powerful data sharing behaviors.
Cheers!