Reputation: 7687
Imagine I have a view with some mutable state, but that the state might need to be updated to reflect changes in another object (e.g. a ViewModel
).
How can I implement that in SwiftUI?
I've tried the following, but can't get the view to reflect updates coming from the ViewModel
:
class ViewModel: ObservableObject {
@Published var text: String = "loading"
private var task: AnyCancellable?
func fetch() {
task = Just("done")
.delay(for: 1, scheduler: RunLoop.main)
.assign(to: \.text, on: self)
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
@State var viewText = "idle"
private var bind: AnyCancellable?
init() {
viewText = viewModel.text
bind = viewModel
.$text
.print()
.assign(to: \.viewText, on: self)
}
var body: some View {
VStack {
TextField(titleKey: "editable text", text: $viewText)
Text(viewText)
Text(viewModel.text)
}
.onAppear {
self.viewModel.fetch()
}
}
}
The TextField
and the first Text
element get their content from ContentView.viewText
, the second Text
goes directly to the source: ViewModel.text
.
As expected, the second Text
shows "loading"
and then "done"
. The first Text
never changes from "idle"
.
Upvotes: 3
Views: 741
Reputation: 17534
If next screen recording looks like answering your question
it was recorded using next code
import SwiftUI
import Combine
class ViewModel: ObservableObject {
@Published var text: String = "loading"
private var task: AnyCancellable?
func fetch() {
task = Just("done")
.delay(for: 3, scheduler: RunLoop.main)
.assign(to: \.text, on: self)
}
}
struct ContentView: View {
@ObservedObject var viewModel = ViewModel()
@State var viewText = "idle"
var body: some View {
VStack {
Text(viewText)
Text(viewModel.text)
}.onReceive(viewModel.$text.filter({ (s) -> Bool in
s == "done"
})) { (txt) in
self.viewText = txt
}.onAppear {
self.viewModel.fetch()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Upvotes: 3
Reputation: 257493
Here is possible approach (tested & works with Xcode 11.2 / iOS 13.2) - modified only ContentView
:
struct ContentView: View {
@ObservedObject var viewModel = ViewModelX()
@State private var viewText = "idle"
init() {
_viewText = State<String>(initialValue: viewModel.text)
}
var body: some View {
VStack {
Text(viewText)
Text(viewModel.text)
}
.onReceive(viewModel.$text) { value in
self.viewText = value
}
.onAppear {
self.viewModel.fetch()
}
}
}
Upvotes: 2