I've got an @EnvironmentObject that's updated in a worker thread, and several SwiftUI views subscribe to changes to the published values.
This all works quite nicely.
But I'm struggling to get a UIView to subscribe to changes in the @EnvironmentObject.
Given
@EnvironmentObject var settings: Settings
where Settings is:
final class Settings {
@Published var bar: Int = 0
@Published var beat: Int = 1
etc.
}
SwiftUI views update based on published value changes rather nicely.
But now, I want to declare a sink that receives the published values inside a UIView that conforms to UIViewRepresentable.
I've been working through the Combine book, and thought that I could declare a .sink closure with something like:
func subscribeToBeatChanges() {
settings.$bar
.sink(receiveValue: {
bar in
self.bar = bar
print("Bar = \(bar)")
} )
settings.$beat
.sink(receiveValue: {
beat in
self.beat = beat
print("Beat = \(beat)")
self.setNeedsDisplay()
} )
}
Unfortunately, the closure is only called once, when subscribeToBeatChanges() is called. What I want is for the closure to be called every time a @Published property in the @EnvironmentObject value changes.
I've also tried to subscribe inside the UIViewRepresentable wrapper, with something inside the makeUIView method, but was unsuccessful.
I'm obviously doing some rather simple and fundamental wrong, and would sure appreciate a push in the right direction, because I'm getting cross-eyed trying to puzzle this out!
Thanks!
You have to store a reference to the AnyCancellable
that you receive back from .sink
:
private var cancellables = Set<AnyCancellable>()
func subscribeToBeatChanges() {
settings.$bar
.sink(receiveValue: {
bar in
self.bar = bar
print("Bar = \(bar)")
} )
.store(in: &cancellables) // <-- store the instance
settings.$beat
.sink(receiveValue: {
beat in
self.beat = beat
print("Beat = \(beat)")
self.setNeedsDisplay()
} )
.store(in: &cancellables) // <-- store the instance
}
If you don't store it, the AnyCancellable
instance will automatically cancel a subscription on deinit.