I understand how a swift ObservableObject isn't seen as changed/updated when an array element (class) is changed. I have added a listener to the array items which works, however I can't fire off an objectWillChange for the array because an array doesn't have the objectWillChange function.
I have a global singleton like this:
final class GlobalData: ObservableObject {
static let shared = GlobalData()
var cancellables = [AnyCancellable]()
@Published private(set) var lists: [List] = [] {
didSet {
lists.forEach({ [unowned self] list in
let c = list.objectWillChange.sink { _ in
self.objectWillChange.send()
}
self.cancellables.append(c)
})
}
}
}
I want to monitor changes in SwiftUI of the array like this:
.onChange(of: globalData.lists) { _ in
print("✅", "lists changed")
}
The problem is, as it's a singleton I don't want to trigger the objectWillChange on self as it always remains the same, I need to trigger it on the published array.
Although the Array is @Published I cannot fire off lists.objectWillChange.send() as I get the error:
Value of type '[WishList]' has no member 'objectWillChange'
So I guess my question is, how can an Array be happy with @Published if I cannot trigger it?
Can I add this function to the Array, or is there another way to trigger the publisher?
There is a slight workaround, but it's not great...
I can add a new published property:
@Published private(set) var listsChanged = UUID()
Then in my array listener I change the UUID. Now in SwiftUI if I watch listsChanged for changes, I get the callbacks I need. But there must be a way to get them from the array?
EDIT -------
Here's an example of my view:
struct MainMenuView: View {
@EnvironmentObject private var globalData: GlobalData
var body: some View {
VStack {}
.onChange(of: globalData.lists) { _ in
print("✅", "lists changed")
}
}
}
I get the lists changed call when I initially set the array, but not if I fire off self. objectWillChange.send() on the GlobalData object.
I really think you are trying to abuse the
@PublishedandObservableObjectmechanism.That scheme is for an object to report its own changes to another object, and it's specifically targeted at making it easy for Swift UI views observing their own state. It's not really meant as a general implementation of the Observer pattern.
From a logical standpoint, using
@PublishedandObservableObjectforGlobalDatais incorrect because it's not theGlobalDatathat's changing. It's trying to report that something it owns is changing, and that's a logically distinct (albeit related) message.Another use of the Observable pattern may be more appropriate to report that event.
For example, a delegate pattern:
Or if want to use Combine:
In either of these two examples the logical event that is happening is clearly defined, more precisely than simply "object changed". In the observer case what's being reported is clear from the context of which delegate method was called. In the Combine case, the event has a distinct enum case that makes the notification explicit. Either should be more readable and understandable in your code.
In the delegate case your view will have to set itself as the delegate and implement the delegate protocol. In the Combine case your view can use
onReceiveview modifier to watch thelistsChangedpublisher.(You could also use
NSNotificationCenterwith a distinct notification name to get the same effect).However you solve your problem, I believe
ObservableObjectand@Publishedare the wrong implementation of the Observer pattern to use here.