I have following observable made to keep track of a custom modal state
import SwiftUI
@Observable class ModalObservable {
// State
var view = ModalViewType.Profile
var open = false
// Actions
func show(view: ModalViewType) {
self.open = true
self.view = view
}
func hide() {
self.open = false
}
}
Instead of having each state value as a separate variable, I want to make a var state = ModalState()
one, where ModalState
will be a struct containing each individual value.
I’m unable to find any docs on how this could affect ui re-rendering logic, mainly will my ui re-render when anything in this struct changes or only when nested value that my ui is accessing changes?
As example, if my ui only uses ModalObservable.state.open
will it re-render when view
changes?
2
Answers
First, let’s be clear about what "re-render" means. If you mean "creates an entirely new view", then this will only happen if the identity of the view changes. If your view’s identity does not depend on
ModalObservable
, then how you changeModelObservable
is not relevant. This can be experimentally detected byonAppear
.If you mean "updates an existing view", then this depends on the view’s dependencies. SwiftUI creates a dependency graph from your views and the states/bindings/environments etc that the views use, and updates the view if any of the dependencies change. This can be experimentally detected by having
body
produce a side effect (thoughbody
is also called when the view is first created).Here is some code that you can use to experiment:
Here SwiftUI has determined that
UpdateDetector
depends onobservable.state
, so it updates whenever it observes a change inobservable.state
, i.e. when either of the buttons is pressed. On the other hand,ContentView
does not update, because it does not depend on any observable property.Note that
observable.state.open
andobservable.state.view
does not count as two separate observable properties, because the@Observable
macro only adds@ObservationTracked
tostate
.Here’s another example, this time with
Binding
s.Here,
UpdateDetector
only depends on theopen
binding, so pressing "Change View" does not update it, but "Change Open" does. On the other hand,ContentView
now also depends on theopen
binding, since a binding is two-way. Pressing "Change Open" updatesContentView
too.If you do want
observable.state.open
andobservable.state.view
to be tracked separately, you can changeModelState
to also be an@Observable
class, though this also changes semantics.If you use
@Observable
modifier it’s almost the same as you inerhit fromObservableObject
Difference is:
Usage of
@Observable
modifier make code more "clean", but it is causes some extra view refreshes in some cases that you can skip in case of usage inerhitance fromObservableObject
. Ofc you can use in some cases@Observable
in some casesObservableObject
, but it will create different codestyle in single project.My opinion: SwiftUI is not lightweight UI system, so better do not use
@Observable
as it makes it even slower because of extra refreshes.My choice is to use ObservableObject