In SwiftUI, why should I use @Bindable if @State works just fine in its place?

99 Views Asked by At

Please help me understand the difference between @Bindable and @State.

If I take the following example from the Xcode Documentation and change @Bindable with @State like this: @State var book: Book, it would work just fine.

@Observable
class Book: Identifiable {
    var title = "Sample Book Title"
    var isAvailable = true
}


struct BookEditView: View {
    @Bindable var book: Book
    @Environment(\.dismiss) private var dismiss


    var body: some View {
        Form {
            TextField("Title", text: $book.title)


            Toggle("Book is available", isOn: $book.isAvailable)


            Button("Close") {
                dismiss()
            }
        }
    }
}

The same would apply to more complex examples as well.

So why should I ever consider using @Bindable if @State works the same way?

1

There are 1 best solutions below

0
Andrés Pizá Bückmann On

Before iOS 17, you would use @State for value types (String, Int, Bool, struct, enum). If you needed to modify that state from a child view, you would use @Binding and pass it as a parameter. If, instead of a value type you had a reference type (class conforming ObservableObject), then you would use @StateObject in the view that owned that model. In the child view that needed to modify that model, you would use @ObservedObject.

With iOS 17 and the Observable protocol, there is some simplification to this. @State is now used for both value types and reference types. Therefore, you use @State in the view that you want to own the model. For child views, if you want to modify the state of an @Observable object, you use @Bindable. For value types, you still use @Binding.

If the child view does not need to modify the models, then you don't need to add any property wrapper.

// Before

class ViewModel: ObservableObject {
    @Published var text = ""
}

struct ParentView: View {
    @State var showTextField = false
    @StateObject var viewModel = ViewModel()

    var body: some View {
        VStack {
            Toggle(isOn: $showTextField) {
                Text("Show text field")
            }

            ChildView(isVisible: $showTextField, viewModel: viewModel)

            Text("Input: \(viewModel.text)")
        }
    }
}

struct ChildView: View {
    @Binding var isVisible: Bool
    @ObservedObject var viewModel: ViewModel

    var body: some View {
        if isVisible {
            TextField("Name", text: $viewModel.text)
        } else {
            Button {
                isVisible.toggle()
            } label: {
                Text("Show text field")
            }
        }
    }
}
// After

@Observable class ViewModel {
    var text = ""
}

struct ParentView: View {
    @State var showTextField = false
    @State var viewModel = ViewModel()

    var body: some View {
        VStack {
            Toggle(isOn: $showTextField) {
                Text("Show text field")
            }

            ChildView(isVisible: $showTextField, viewModel: viewModel)

            Text("Input: \(viewModel.text)")
        }
    }
}

struct ChildView: View {
    @Binding var isVisible: Bool
    @Bindable var viewModel: ViewModel

    var body: some View {
        if isVisible {
            TextField("Name", text: $viewModel.text)
        } else {
            Button {
                isVisible.toggle()
            } label: {
                Text("Show text field")
            }
        }
    }
}