swiftuidata-bindingbindingobservablebindable

Can I use `@Binding` for `Observable` class instance rather than `@Bindable`?


In SwiftUI, @Bindable is used to create binding to mutable properties of observable class instances, ensuring synchronization between ParentView and ChildView. If I replace @Bindable with @Binding, it simply requires an additional $ sign. Why we need @Bindable if @Binding can achieve the same result that is synchronization of all related views when person.name is modified. What is the key difference in how they work?

▪︎@Bindable , ChildView(person: person)

@Observable
class People {
    var name = "No name"
}

struct ChildView: View {
    @Bindable var person: People

    var body: some View {
        VStack {
            TextField("...", text: $person.name)
        }
    }
}

struct ParentView: View {
    @State private var person = People()

    var body: some View {
        VStack {
            TextField("...", text: $person.name)
            // no `$` sign
            ChildView(person: person)
        }
    }
}

▪︎@Binding , ChildView(person: $person)


@Observable
class People {
    var name = "No name"
}

struct ChildView: View {
    @Binding var person: People

    var body: some View {
        VStack {
            TextField("...", text: $person.name)
        }
    }
}

struct ParentView: View {
    @State private var person = People()

    var body: some View {
        VStack {
            TextField("...", text: $person.name)
            // with `$` sign
            ChildView(person: $person)
        }
    }
}

If Apple have to create a separate property wrapper like @Bindable to achieve two-way binding with @Observable class, I think @Binding should not be fully compatible with @Observable class.

I am expecting the key reason for needing @Bindable rather than using @Binding and really appreciate any answers to figure out the key.


Solution

  • @Binding is compatible with @Observable.

    The main difference is that @Binding allows the child view to change entirely which instance of People it is, in addition to getting Bindings of the properties of People.

    For example:

    struct ChildView: View {
        @Binding var person: People
    
        var body: some View {
            VStack {
                TextField("...", text: $person.name)
                Button("Change Person") {
                    //  I cannot do this if 'person' is '@Bindable'
                    person = People()
                    person.name = "New Person!"
                }
            }
        }
    }
    

    See also the documentation for @State, which also mentions this.

    State properties provide bindings to their value. When storing an object, you can get a Binding to that object, specifically the reference to the object. This is useful when you need to change the reference stored in state in some other subview, such as setting the reference to nil [...]

    It also says:

    However, passing a Binding to an object stored in State isn’t necessary when you need to change properties of that object.

    If you need a binding to a specific property of the object, pass either the binding to the object and extract bindings to specific properties where needed, or pass the object reference and use the Bindable property wrapper to create bindings to specific properties.

    So use @Binding if the child view needs to change the whole reference to something else (e.g. a totally different instance of People). If you only want Bindings to the object's properties, use @Bindable.