I was experimenting with Combine and SwiftUI but stuck in updating state basically I want to update my state in the view every time ObservableObject changes, here's the example.
class XViewModel: ObservableObject {
@Published var tVal: Bool = false
private var cancellables = Set<AnyCancellable>()
func change() {
Just(true)
.delay(for: 3.0, scheduler: RunLoop.main)
.receive(on: RunLoop.main)
.assign(to: \.tVal, on: self)
.store(in: &self.cancellables)
}
}
I have viewModel and one publisher and delayed publisher which triggers after 3 seconds.
struct ContentView: View {
@ObservedObject var viewModel = XViewModel()
@State var toggleVal: Bool = false
private var cancellables = Set<AnyCancellable>()
init() {
self.viewModel.$tVal
.sink { newVal in
print(newVal)
}
.store(in: &cancellables)
self.viewModel
.$tVal.assign(to: \.toggleVal, on: self)
.store(in: &cancellables)
viewModel.change()
}
var body: some View {
VStack {
Toggle(isOn: self.$viewModel.tVal) {
Text("Toggle")
Toggle(isOn: self.$toggleVal) {
Text("Toggle from View")
}
}
}
What I expected is
viewModel.Just triggers
viewModel.tVal publisher triggers
view.toggleVal state triggers
updates UI
But it seems although everything is updated it doesn't update state. Is there any way to update State or it wasn't meant to be updated at all and I need to bind my views directly to the viewModel's tVal value which is publisher.
Thanks.
The @State
is not ready in init
to operate, use instead .onReceive
as below.
struct ContentView: View {
@ObservedObject var viewModel = XViewModel()
@State var toggleVal: Bool = false
private var cancellables = Set<AnyCancellable>()
init() {
self.viewModel.$tVal
.sink { newVal in
print(newVal)
}
.store(in: &cancellables)
viewModel.change()
}
var body: some View {
VStack {
Toggle(isOn: self.$viewModel.tVal) {
Text("Toggle")
Toggle(isOn: self.$toggleVal) {
Text("Toggle from View")
}
.onReceive(self.viewModel.$tVal) { newVal in // << here !!
self.toggleVal = newVal
}
}
}