I have 2 independent ObservableObject
s called ViewModel1
and ViewModel2
.
ViewModel2
has an array of strings:
@Published var strings: [String] = []
.
Whenever that array is modified i want ViewModel1
to be informed.
What's the recommended approach to achieve this?
Clearly, there are a number of potential solutions to this, like the aforementioned NotificationCenter
and singleton ideas.
To me, this seems like a scenario where Combine would be rather useful:
import SwiftUI
import Combine
class ViewModel1 : ObservableObject {
var cancellable : AnyCancellable?
func connect(_ publisher: AnyPublisher<[String],Never>) {
cancellable = publisher.sink(receiveValue: { (newStrings) in
print(newStrings)
})
}
}
class ViewModel2 : ObservableObject {
@Published var strings: [String] = []
}
struct ContentView : View {
@ObservedObject private var vm1 = ViewModel1()
@ObservedObject private var vm2 = ViewModel2()
var body: some View {
VStack {
Button("add item") {
vm2.strings.append("\(UUID().uuidString)")
}
ChildView(connect: vm1.connect)
}.onAppear {
vm1.connect(vm2.$strings.eraseToAnyPublisher())
}
}
}
struct ChildView : View {
var connect : (AnyPublisher<[String],Never>) -> Void
@ObservedObject private var vm2 = ViewModel2()
var body: some View {
Button("Connect child publisher") {
connect(vm2.$strings.eraseToAnyPublisher())
vm2.strings = ["Other strings","From child view"]
}
}
}
To test this, first try pressing the "add item" button -- you'll see in the console that ViewModel1
receives the new values.
Then, try the Connect child publisher
button -- now, the initial connection is cancelled and a new one is made to the child's iteration of ViewModel2
.
In order for this scenario to work, you always have to have a reference to ViewModel1
and ViewModel2
, or at the least, the connect
method, as I demonstrated in ChildView
. You could easily pass this via dependency injection or even through an EnvironmentObject
ViewModel1
could also be changed to instead of having 1 connection, having many by making cancellable
a Set<AnyCancellable>
and adding a connection each time if you needed a one->many scenario.
Using AnyPublisher
decouples the idea of having a specific types for either side of the equation, so it would be just as easy to connect ViewModel4
to ViewModel1
, etc.