So that chaining won't be needed first off; the indirect value when composed with other values will be tracked. So the code is considerably more simple than you might have expected:
// View
struct SomeView: View {
@State private var model = InternalObservable()
var body: some View {
VStack {
Text("\(model.external.directlyObservedProperty)")
Text("\(model.dependentProperty)")
}
}
}
// Internal Observable
@Observable
final class InternalObservable {
var dependentProperty: String {
"\(external.indirectlyObservedProperty). Woohoo!"
}
let external = ExternalObservable()
init() { }
}
// Some other module
@Observable
public final class ExternalObservable {
public var directlyObservedProperty = "Some Value"
public var indirectlyObservedProperty = "Some Value"
// Calling this method affects both text fields in the View.
// The changes _are_ guaranteed to be coalesced into
// a single view update!
public func updateBothProps() {
self.directlyObservedProperty = "I'm changing"
self.indirectlyObservedProperty = "And so am I!"
}
}
However if you do have a need to (in non SwiftUI scenarios) to do something similar then the use for the withObservationTracking
(note; it got changed to a free floating function).
func resetObservationOnExternalObservable() {
ObservationTracking.withTracking {
self.dependentProperty = "\(external.indirectlyObservedProperty). Woohoo!"
} onChange: { [weak self] in
// The onChange is fired on the willSet side of things - so the reset needs to be called asynchronously to prevent a recursion and also update after the property changes.
Task { @MainActor in
self?.resetObservationOnExternalObservable()
}
}
}