Search code examples
swiftswiftui

Swift UI @Bindable


I'm kind of confused about the usage of @Bindable. I know this property wrapper is used when we have @State class var at the parent view and Use @Bindable at the child view so we can pass the class variable to child view and update the value, but I notice even without the @Bindable at the child view, the class variable would still reflect change made in the child view at parent view page.

My Parent View



import SwiftUI

struct parentView: View {
    @State var model = testModel(test: "string", num: 3, arr: ["a","b"])
    @State var isPresented = false
    var body: some View {
        NavigationStack{
            Text(model.test)
            NavigationLink("Pass") {
                childView(model: model)
            }
        }
        
    }
}

#Preview {
    parentView()
}




My child view

import SwiftUI

struct childView: View {
    var model: testModel
    @Environment(\.dismiss) var dismiss
    var body: some View {
        VStack{
            Text("Hello, World!")
            Button("Change") {
                changeModel()
                dismiss()
            }
        }
    }
    
    func changeModel(){
        model.test = "Undefined"
    }
}

#Preview {
    childView(model:testModel(test: "string", num: 3, arr: ["a","b"]))
}

My class model

import Foundation

@Observable
class  testModel{
    var test:  String
    var num: Int
    var arr:[String]
    
    init(test: String, num: Int, arr: [String]) {
        self.test = test
        self.num = num
        self.arr = arr
    }
}

I think my parent view can reflect the changes may because the class are reference type. if that is the case, when should we use @Bindable at child view to update the class var


Solution

  • You are correct that you can just pass the @Observable object across views and everything will be updated correctly.

    Compare this to the ObservableObject API, where you would need a @ObservedObject property to in the child view for it to update correctly. With @Observable, you don't need such a property wrapper.

    However, the @ObservedObject property wrapper also allows you to get a Binding of the object's properties using the $ prefix.

    @ObservedObject var foo: SomeObservableObject
    
    var body: some View {
        // You get a Binding<String> by writing '$foo.someProperty'!
        TextField("Some Text", text: $foo.someProperty)
    }
    

    but you cannot do this with an @Observable object if you don't use a property wrapper:

    // no need for @ObservedObject here!
    let foo: SomeObservableMacroObject
    
    var body: some View {
        // but '$foo' doesn't exist because 'foo' is not wrapped by a property wrapper
        TextField("Some Text", text: $foo.someProperty)
    }
    

    This is where @Bindable is needed. Its purpose is to allow you to get a Binding from an @Observable object.

    @Bindable var foo: SomeObservableMacroObject
    
    var body: some View {
        // Just like the first code snippet, you get a Binding<String> by writing '$foo.someProperty'!
        TextField("Some Text", text: $foo.someProperty)
    }
    

    See also Migrating from the Observable Object protocol to the Observable macro