SwiftUI @Binding 值无法更改并调用了 init

SwiftUI @Binding value can not change and called init

我想做一个选择器使用SwiftUI,当我改变ChildView中的值时,它不会改变并调用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 = [=10=] }
    }
}

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

但是当我在ContentView里去掉Text("(parent) \(self.value)")的时候,好像就正常了

发生这种情况是因为任何时候 ChildView 都被 init 初始化了 - 这发生在 ContentView 的 body 被重新计算时 - 它创建了 [=15 的新实例=] 的值为 0.

首先确定谁“拥有”数据。如果它是一些外部 object,比如 ViewModel,那么它应该在实例可能是 longer-lived 的地方实例化,例如在 ContentView 中(但这取决于你的真实用例):

struct ContentView: View {
    @State var value = 0
    var childVm = ViewModel(v: 0)
    
    var body: some View {
        VStack {
            Text("(parent) \(self.value)")
            ChildView(vm: childVm, value: $value)
        }
    }
}
struct ChildView: View {
    @Binding var value: Int
    @ObservedObject var vm: ViewModel
    
    init(vm: ViewModel, value: Binding<Int>) {
        self._value = value
        self.vm = vm
        print("ChildView init")
    }
    // ...
}

通常,所描述的行为是预期的,因为 value 的真实来源在父级中,通过绑定更新它会更新所有使用它的地方。这导致重建父主体,因此重新创建子视图。

SwiftUI 2.0

解决方法很简单——使用状态对象

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

   // ... other code

SwiftUI 1.0+

使用更新的绑定值初始化视图模型

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