Reputation: 631
When using different property wrappers associated with view updates, changes in one place affect rendering of views that do not use that property.
struct ContentView: View {
@StateObject var viewModel = MyViewModel()
@State var thirdTitle = "thirdTitle"
var body: some View {
VStack {
Text(viewModel.firstTitle)
.background(.random)
Text(viewModel.secondTitle)
.background(.random)
Text(thirdTitle)
.background(.random)
Button("Change First Title") {
viewModel.chageFirstTitle()
}
}
}
}
class MyViewModel: ObservableObject {
@Published var firstTitle = "firstTitle"
@Published var secondTitle = "secondTitle"
func chageFirstTitle() {
firstTitle = "hello world"
}
}
I understand that the reason why the Text
exposing the viewModel.secondTitle
is re-rendered is because the @StateObject varviewModel = MyViewModel()
dependency changed when the `viewModel.firstTitle changed.
However, I don't know why Text
using @State var thirdTitle = "thirdTitle"
is re-rendered too. In WWDC21 session Demystify SwiftUI, I saw that the view is re-rendered only when the related dependency is updated according to the dependency graph. But, even though the thirdTitle
is irrelevant to the change of the viewModel
, third Text
using that dependency is re-rendered and the background color is changed.
What's even more confusing is that if I seperate the third Text
into a separate view ( ThirdView
) and receive the thirdTitle
using @Binding
, the background color does not change because it is not re-rendering at that time.
struct ContentView: View {
@StateObject var viewModel = MyViewModel()
@State var thirdTitle = "thirdTitle"
var body: some View {
VStack {
Text(viewModel.firstTitle)
.background(.random)
Text(viewModel.secondTitle)
.background(.random)
ThirdView(text: $thirdTitle)
.background(.random)
Button("Change First Title") {
viewModel.chageFirstTitle()
}
}
}
}
struct ThirdView: View {
@Binding var text: String
var body: some View {
Text(text)
.background(.random)
}
}
Regarding the situation I explained, could you help me to understand the rendering conditions of the view?
Upvotes: 2
Views: 680
Reputation: 30719
A few things wrong here. We don't use view model objects in SwiftUI for view data, it's quite inefficient/buggy to do so. Instead, use a struct with mutating funcs with an @State
. Pass in params to sub-Views as lets for read access, @Binding
is only when you need write access. In terms of rendering, first of all body
is only called if the let property is different from the last time the sub-View is init, then it diffs the body from the last time it was called, if there are any differences then SwiftUI adds/removes/updates actual UIKit UIViews on your behalf, then actual rendering of those UIViews, e.g. drawRect
, is done by CoreGraphics.
struct ContentViewConfig {
var firstTitle = "firstTitle"
var secondTitle = "secondTitle"
mutating func changeFirstTitle() {
firstTitle = "hello world"
}
}
struct ContentView: View {
@State var config = Config()
...
struct ThirdView: View {
let text: String
...
Combine's ObservableObject
is usually only used when needing to use Combine, e.g. using combineLatest
with multiple publishers or for a Store
object to hold the model struct arrays in @Published
properties that are not tied to a View's lifetime like @State
. Your use case doesn't look like a valid use of ObservableObject
.
Upvotes: 1
Reputation: 258177
To render SwiftUI calls body
property of a view (it is computable, i.e. executes completely on call). This call is performed whenever any view dependency, i.e. dynamic property, is changed.
So, viewModel.chageFirstTitle()
changes dependency for ContentView
and ContentView.body
is called and every primitive in it is rendered. ThirdView
also created but as far as its dependency is not changed, its body
is not called, so content is not re-rendered.
Upvotes: 4