I'm seeking clarity on the reasonings - if any known - why the order of initialisation and view modifiers occurs.
I couldn't find any documentation on this but hoping someone might know why.
I have the following example code:
@main
struct TestApp: App {
init() { print("@main: init" }
var body: some Scene {
WindowGroup {
ContentView()
.onAppear { let _ = print("@main: onAppear" }
}
}
}
struct ContentView: View {
var body: some View {
VStack {}
.onAppear { let _ = print("ContentView: onAppear" }
}
}
When this runs, the console outputs the following:
@main - init
ContentView - onAppear
@main - onAppear
Does anyone know why or any reasoning why the order would be init
, child view .onAppear
, then main .onAppear
?
I guess in my mind the order would be initialising all of the @main
including the modifiers before any of the child views - but maybe there is a proper reason for the current order.
The documentation for onAppear
says:
The exact moment that SwiftUI calls this method depends on the specific view type that you apply it to, but the action closure completes before the first rendered frame appears.
So depending on the type of view, onAppear
could totally be called in any order, as long as it is "before the first rendered frame appears".
Here is a possible explanation for your particular example.
If you just "flatten" ContentView
, you end up with a VStack
with 2 onAppear
s:
VStack {}
// 1
.onAppear { let _ = print("ContentView: onAppear" }
// 2
.onAppear { let _ = print("@main: onAppear" }
Each onAppear
is modifying everything "above" it. "@main: onAppear" is printed when everything above // 2
has "appeared". That means that the VStack
must have already appeared. Therefore, "@main: onAppear" is printed after "ContentView: onAppear".
And of course, "@main: init" is the first thing being printed, because without creating an instance of TestApp
, SwiftUI cannot call the getter of body
, and hence cannot know anything about what views you have.
From my experiments, most SwiftUI "containers" have their onAppear
called first, before the onAppear
of the views in them.
VStack {
Text("Foo")
.id("Foo")
.onAppear {
print("1")
}
Text("Foo")
.onAppear {
print("2")
}
}
.onAppear {
print("3")
}
// prints in the order of 3 2 1
This aligns with your expectation, if I understand correctly. The VStack
can appear first, without any subviews, then the subviews appear. Your ContentView
cannot do something similar - it is literally just a wrapper around a single VStack
.