I am struggling to figure out how to make the NavigationStack work correctly with a completely custom navigation system.
I have a stack that is driven by a path. This path is updated by several different UI elements including a custom toolbar at the top of the screen with a back button.
The issue itself is quite simple, but I have not found a great solution. If you navigate forward and press the back button very shortly after, the path will show an array that is empty, but the screen will not be on the root view.
This particular problem has caused inconsistency and crashing in the app in the form of this exception:
NSInvalidArgumentException - <SwiftUI.UIKitNavigationController: <address>> is pushing the same view controller instance (<_TtGC7SwiftUI32NavigationStackHostingControllerVS_7AnyView_: <address>>) more than once which is not supported and is most likely an error in the application
This only happens when the navigation is animated. I can avoid the problem altogether by using:
var transaction = Transaction()
transaction.disablesAnimations = true
withTransaction(transaction) {
// navigate
}
However, this is not ideal because I want to use the animations when moving between views.
I have observed the system behavior and it appears that the back button is simply disabled while the view is transitioning. My fix so far is to add a mandatory delay between edits of the path that should pause navigation long enough for the animations to complete, but this just feels wrong and brittle.
I have put together a sample SwiftUI view that shows the issue I am having. As of now, this doesn't seem to crash in the example, but the views do get out of sync if you "Go to next" and hit "Back" in rapid succession.
struct ContentView: View {
@State private(set) var path = [String]()
var body: some View {
VStack {
NavigationStack(path: $path) {
Spacer()
VStack {
Spacer()
Button("Go to next") {
path.append(UUID().uuidString)
print(path)
}
.padding()
Spacer()
}
.navigationDestination(for: String.self) { textValue in
VStack {
Text(textValue)
Button("Go to next") {
path.append(UUID().uuidString)
}
}
}
}
Button("Back") {
if path.count > 0 {
path.removeLast()
}
print(path)
}
.padding()
}
}
}
I appreciate any help I can get on this! I would love to keep using SwiftUI for navigation. Apart from this issue, it has worked really well.