Reputation: 93
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.
Upvotes: 2
Views: 2178
Reputation: 257493
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
Upvotes: 1
Reputation: 49580
This happens because anytime ChildView
gets init
-ialized - which happens when ContentView
's body is recomputed - it create a new instance of ViewModel
with the value 0
.
Determine first who "owns" the data. If it's some external object, like ViewModel
, then it should get instantiated somewhere where an instance could be longer-lived, for example in ContentView
(but it would depend on your real use case):
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")
}
// ...
}
Upvotes: 2