I have an ObservableObject
with @Published
properties:
class SettingsViewState: ObservableObject {
@Published var viewData: SettingsViewData = .init()
…
I would like to change viewData
to a computed var based on other sources of truth rather than allowing it to be directly modified. However, I still want views looking at viewData
to automatically update as it changes. They should update when properties it is computed from change.
I'm really not very certain about how @Published
actually works though. I think it has its willSet
perform objectWillChange.send()
on the enclosing ObservableObject
before a change occurs, but I'm not sure!
If my suspicion is correct, it seems like I could manually call objectWillChange.send()
on the enclosing object if anything viewData
depends on will change.
Alternatively, if properties viewData
is computed from are themself @Published
, when I change one, presumably an equivalent objectWillChange.send()
will occur automatically, and I won't need to do anything special? This should work even if these properties are private
and a watching view doesn't have access to them: it should still see the objectWillChange
being emitted?
However, It's entirely possible I've got this horribly garbled or mostly backwards! Eg, perhaps the @Published
properties have their own independent change publisher, rather than simply making use of the enclosing ObservableObject
's? Or both of them publish prior to a change?
Clarification will be gratefully received. Thank you!
I'm really not very certain about how @Published actually works though. I think it has its willSet perform objectWillChange.send() on the enclosing ObservableObject before a change occurs, but I'm not sure!
You are correct, that is how @Published
and ObservableObject
work together, however, these are 2 independent things.
@Published
is a property wrapper, which adds a Publisher
to the wrapped property, which emits the new value from the willSet
of the property. So the Published.Publisher
emits the value that is about to be set before it is actually set.
ObservableObject
is a protocol, which has an objectWillChange
publisher, whose value is autosynthesised by the compiler. The synthesised implementation emits a value when any of the @Published
property's publishers emits. So objectWillChange
emits a value whenever an @Published
property on the ObservableObject
conformant type is about to change.
If you store an ObservableObject
conformant type on a SwiftUI View
as @StateObject
, @ObservedObject
or @EnvironmentObject
, the view updates (and hence recalculates its body
) whenever the objectWillChange
of the ObservableObject
emits.
If you have a property, which needs to be recalculated whenever other properties are updated and you want to update your view with these changes, you have several options to achieve that.
@Published
properties and set up the dependant property to be updated whenever any of the properties it depends on are updated.This 100% guarantees that your view will always be updated with the correct values and you don't need to call objectWillChange.send()
manually.
This solution also works even if the properties that your "computed" property depends on are declared on other types, since your "computed" property is @Published
so whenever it is updated, it will trigger a view update.
However, you do need to set up the observation of your dependant properties to update the property that depends on them.
@Published var height: Int
@Published var width: Int
@Published private(set) var size: Int
init(height: Int, width: Int) {
self.height = height
self.width = width
self.size = height * width
$height.combineLatest($width).map { height, width in
height * width
}.assign(to: &$size)
}
@Published
and are declared on the same object as your computed property, simply declaring the property that depends on them as computed should work, since the properties that you depend on will trigger a view update themselves whenever they are updated.This doesn't work though if your @Published
properties are declared on another object, since that object won't trigger a view update.