Assume, you have a view hierarchy of (minimal example below).
Outer (Navigation)View -> (Pushed) ContainerDetailView -> (Embedded) View
Clicking a NavigationLink in the outer view, pushes a detail view which includes an embedded view. If you put some print statement in the init function to see how often views are loaded, all is well and as expected (1 print for each view).
However -- here comes the question -- if you include
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
in the middle view, it will reload (init new objects) of the embedded view many times. In the minimal example it is 3 times. Why is that? I can only suspect that it has something to do with pushing something on the navigation stack internally, changes the environment variable which leads to an invalidation of the middle view's view body and therefore triggers a reload of the child view.
It however gets even better if the (Embedded) View has a .toolbar modifier. If that is the case, the view is ìnited 5 times in total for being pushed just once on the stack. Why is that and can it be avoided?
Here is a minimal example (I got this behavior on Simulator and Device in XCode 12 and iOS 14.5).
import SwiftUI
@main
struct SomeSwiftyTestApp: App {
let items = [1,2,3,4]
/* This is the outer view */
var body: some Scene {
WindowGroup {
NavigationView {
ScrollView {
LazyVStack {
ForEach(items, id:\.self) { item in
NavigationLink("Click Here", destination: ContentView())
}
}
}
}
}
}
}
/* This is is the middle view */
struct ContentView: View {
@Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
init() { print("ContentView loaded") }
var body: some View {
InnerView()
}
}
/* This is the inner view */
struct InnerView: View {
init() { print("InnerView loaded") }
var body: some View {
Text("Hello, World!")
/* Comment this toolbar modifier in to get 5x loading instead of 3x */
.toolbar(content: {
ToolbarItem(id: "id", placement: .principal, showsByDefault: true, content: {
Text("ToolbarItem")
})
})
}
}
If you run this and push a button, you get
ContentView loaded
ContentView loaded
ContentView loaded
ContentView loaded
InnerView loaded
InnerView loaded
InnerView loaded
InnerView loaded
InnerView loaded
Where ContentView loaded is expected due to eager NavigationLink loading. But I would have assumed to see InnerView loaded just once. Am I doing something wrong? Can this be avoided as my (actual) view hierarchy is fairly complex and starts background processing on init.