I'm trying to understand how @EnvironmentObject
affects redrawing when a property in ObservableObject
changes.
As per Apple's doc on EnvironmentObject,
An environment object invalidates the current view whenever the observable object changes.
If the above statement is true, wouldn't the below code invalidate and recreate ChildView
when you press the button in ContentView
. And that should print the following output.
initializing parent
initializing child
// after pressing the button
initializing child
Contrary to my above assumption, it actually prints
initializing parent
initializing child
// after pressing the button
initializing parent
Can anyone explain why this is the case? Why is the ParentView
being recreated even though ParentView
is not depending on Library
?
class Library: ObservableObject {
@Published var item: Int = 0
}
struct ContentView: View {
@StateObject var library: Library = Library()
var body: some View {
VStack {
ParentView()
.environmentObject(library)
Button {
library.item += 1
} label: {
Text("increment")
}
}
}
}
struct ParentView: View {
init() {
print("initializing parent")
}
var body: some View {
VStack {
Text("Parent view")
ChildView()
}
}
}
struct ChildView: View {
@EnvironmentObject var library: Library
init() {
print("initializing child")
}
var body: some View {
Text("Child view")
}
}
SwiftUI View´s can be a little tricky.
An environment object invalidates the current view whenever the observable object changes.
does not mean the object itself is recreated. It just means the body of the view gets called and the view rebuilds itself.
Remember the struct is not the View
itself, it´s just a "description".
I´ve added some print statements to make this more clear:
struct ContentView: View {
@StateObject var library: Library = Library()
init(){
print("initializing content")
}
var body: some View {
VStack {
let _ = print("content body")
ParentView()
.environmentObject(library)
Button {
library.item += 1
} label: {
Text("increment")
}
}
}
}
struct ParentView: View {
init() {
print("initializing parent")
}
var body: some View {
VStack {
let _ = print("parent body")
Text("Parent view")
ChildView()
}
}
}
struct ChildView: View {
@EnvironmentObject var library: Library
init() {
print("initializing child")
}
var body: some View {
let _ = print("child body")
Text("child")
}
}
this initially prints:
initializing content
content body
initializing parent
parent body
initializing child
child body
and after pressing the button:
content body
initializing parent
child body
As you see the body of those View
´s depending on Library
get their respective body reevaluated.
The ParentView
initializer runs because in your ContentView
you call ParentView()
in the body so a new struct "describing" your View is created. The ParentView
´s view itself stays the same so its body
var is not called.
This WWDC 2021 video about SwiftUI Views will help you better understand this.