-
-
Save chriseidhof/f7a19206d07ab04335f4156d9e99f40e to your computer and use it in GitHub Desktop.
Thank you for taking the time to write such a nice comment, but I don't think it's correct. Without the overlay the problem persists.
With regard to the id
it's actually the other way around: by setting a new ID the rendering engine transitions to a new view hierarchy and shows the correct view without animating.
Tried this myself, even with separate properties for different levels.
Could it be related to double navigation animations happening simultaneously? I mean, UIKit typically throws in warnings when you try to do multiple navigations until animation is complete. However, no warnings in console this time.
A couple of notes from testing:
While this causes navigation to pop:
DetailView(showDetail: $levels[1])
These options do not:
MyListDetailView(showDetail: .constant(true))
MyListDetailView(showDetail: .constant(levels[1]))
I also tried:
- Creating and passing an
ObservableObject
- Using
@EnvironmentObject
to share levels data
Neither worked. 🙁
I submitted a Feedback for this (FB9795803).
Thanks. Yes, I have tried those things as well. It's easy to "fix" this by disabling various parts, but in the end, I "only" want navigation that's driven by a single @State property.
Hello @chriseidhof , I think there's related post on Apple Forum. When parent view state changes, Navigation automatically pops out child views (on iOS14.0 it propagated changes to update child views instead AFAIK). Unfortunately I don't have 14.0SDK to verify for this use-case
I don't think SwiftUI is ready for this kind of navigation yet. Navigation is controlled by stated, but it has only 2 states... may be we need more? Somehow there is a missing state for performing the action, etc. I would like some interface like withAnimation...
performNavigation(withAnimaion: .animateLastOnly) {
levels = [true, true]
}
Also it would be awesome if it would work for closing too.
I think for now, you can use workaround:
Button("Navigate To Detail") {
levels = [true, false]
// slow down due to the opening of the first screen
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
levels = [true, true]
}
}
Code with changes:
import SwiftUI
struct DetailView: View {
@Binding var showDetail: Bool
var body: some View {
List {
NavigationLink("detail", isActive: $showDetail) {
Text("Detail")
}
}
}
}
struct MyList: View {
@Binding var levels: [Bool]
var body: some View {
List {
NavigationLink("Detail", isActive: $levels[0]) {
DetailView(showDetail: $levels[1])
}
}
.overlay(
Button("Navigate To Detail") {
levels = [true, false]
// slow down due to the opening of the first screen
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
levels = [true, true]
}
}
)
}
}
struct ContentView: View {
@State var levels = [false, false]
var body: some View {
NavigationView {
MyList(levels: $levels)
}
.navigationViewStyle(.stack)
.overlay(Text(levels.map { $0 ? "t" : "f"}.joined()), alignment: .bottom)
}
}
In regards to this question
I've experienced this on parent views re-rendering due to some change either in their own models or in their parent view (the grand-parent view?).
It could be that your
ContentView
is rendered again due to some change and then the SwiftUI rendering engine recreates the whole hierarchy.Without knowing how you bootstrapped your app, this modifier is a probably suspect
.overlay(Text(levels.map { $0 ? "t" : "f"}.joined()), alignment: .bottom)
for triggering that change that pops your view out.This tells me that the rendering engine is able to tell that there's no change with thanks to this ID and that's why the workaround works.
This sucks and it's cumbersome and does not look like what Apple always showcases at their conferences and sample code. But I got rid of most navigation bugs by detaching navigation state from view states and maintaining that on another layer of the app that is not bound to SwiftUI's View struct rendering lifecycle.