Suppose I have a data model in my SwiftUI app that looks like the following:
class Tallies: Identifiable, ObservableObject {
let id = UUID()
@Published va
There are multiple issues here to address.
First, it's important to understand that SwiftUI updates the view's body when it detects a change, either in a @State
property, or from an ObservableObject
(via @ObservedObject
and @EnvironmentObject
property wrappers).
In the latter case, this is done either via a @Published
property, or manually with objectWillChange.send()
. objectWillChange
is an ObservableObjectPublisher
publisher available on any ObservableObject
.
This is a long way of saying that IF the change in a computed property is caused together with a change of any @Published
property - for example, when another element is added from somewhere:
elements.append(Talies())
then there's no need to do anything else - SwiftUI will recompute the view that observes it, and will read the new value of the computed property cumulativeCount
.
Of course, if the .count
property of one of the Tallies
objects changes, this would NOT cause a change in elements
, because Tallies
is a reference-type.
The best approach given your simplified example is actually to make it a value-type - a struct
:
struct Tallies: Identifiable {
let id = UUID()
var count = 0
}
Now, a change in any of the Tallies
objects would cause a change in elements
, which will cause the view that "observes" it to get the now-new value of the computed property. Again, no extra work needed.
If you insist, however, that Tallies
cannot be a value-type for whatever reason, then you'd need to listen to any changes in Tallies
by subscribing to their .objectWillChange
publishers:
class GroupOfTallies: Identifiable, ObservableObject {
let id = UUID()
@Published var elements: [Tallies] = [] {
didSet {
cancellables = [] // cancel the previous subscription
elements.publisher
.flatMap { $0.objectWillChange }
.sink(receiveValue: self.objectWillChange.send)
.store(in: &cancellables)
}
}
private var cancellables = Set
var cumulativeCount: Int {
return elements.reduce(0) { $0 + $1.count } // no changes here
}
}
The above will subscribe a change in the elements
array (to account for additions and removals) by:
Sequence
publisher of each array elementTallies
object, into its objectWillChange
publisherobjectWillChange.send()
, to notify of the view that observes it of its own changes.