Search code examples
swiftswiftuicombinepublisher

SwiftUI @Binding value can not change and called init


I want to make a picker use SwiftUI, when I change the value in ChildView, it will not change and called ChildView init.

class ViewModel: ObservableObject {
    @Published var value: Int
    
    init(v: Int) {
        self.value = v
    }
}

struct ChildView: View {
    @Binding var value: Int
    @ObservedObject var vm = ViewModel(v: 0)
    
    init(value: Binding<Int>) {
        self._value = value
        print("ChildView init")
    }
    
    var body: some View {
        VStack {
            Text("param value: \(value)")
            Text("@ObservedObject bar: \(vm.value)")
            Button("(child) bar.value++") {
                self.vm.value += 1
            }
        }
        .onReceive(vm.$value) { value = $0 }
    }
}

struct ContentView: View {
    @State var value = 0
    
    var body: some View {
        VStack {
            Text("(parent) \(self.value)")
            ChildView(value: $value)
        }
    }
}

But when I remove Text("(parent) \(self.value)") in ContentView, it seems to be normal.


Solution

  • In general, the described behavior is expected, because source of truth for value is in parent, and updating it via binding you update all places where it is used. That result in rebuild parent body, so recreate child view.

    SwiftUI 2.0

    Solution is simple - use state object

    struct ChildView: View {
        @Binding var value: Int
        @StateObject var vm = ViewModel(v: 0)   // << here !!
    
       // ... other code
    

    SwiftUI 1.0+

    Initialize view model with updated bound value

    struct ChildView: View {
        @Binding var value: Int
        @ObservedObject var vm: ViewModel    // << declare !!
    
        init(value: Binding<Int>) {
            self._value = value
            self.vm = ViewModel(v: value.wrappedValue) // << initialize !!
    
        // .. other code