Rapidly calling removeLast on system/custom navigation path causes NavigationStack and navigation path states to become desynced.
Bug report filed on Sept. 19, 2023 (FB13189889)
This demo app shows how programmatically dismissing views on a NavigationStack
by manipuating its navigationPath
can cause the stack (UI state) and path (data state) to become "de-synced".
- When using the environment dismiss action, the navigationPath's last value is removed after the "pop" animation completes.
- When using programmatic dismissal (i.e. manually manipulating the navigationPath), the navigationPath's last value is removed before the "pop" animation begins.
The latter's behaviour causes the issues described below.
(You can switch between SystemNavigationPathView
and CustomNavigationPathView
in ContentView
)
- Add 10 or more detail views onto navigation stack using the on-screen button.
- Rapidly tap (more than 10 times) the "Navigate Back ((path.count))" overlay button so that
navigationPath.count == 0
. - NavigationPath now reports
count == 0
, but NavigationStack still shows one or more detail views. - At this point, performing any of the following steps results in unexpected behaviour
- Navigate back using a custom back button no longer works because navigationPath is already empty!
- Using the system back button causes stack to go back to an empty view with animation.
- Attempting to push a new detail view using on-screen button causes stack to perform a "pop" action instead (or some other unexpected action).
Either
- If user rapidly taps custom back button, say, 7 times, navigation stack should go back 7 pages.
- NavigationPath should ignore programmatic manipulation while animation is in-flight. There should be a way to coordinate programmatic manipulation of a navigation path with its associated animation. (Using withAnimation or withAnimation(…completion) on iOS 17 has no effect).
- The custom back button (e.g. a persistent overlay or tab button not associated with any detail view) would have to gain access to each view's environment dismiss action, and use that to navigate back.
- We'll likely use this workaround, but it's a lot more cumbersome than being able to just manipulate a navigationPath in a root view.
- Link to PR that uses this workaround
- This happens on iOS 16.5, 16.6 and iOS 17 (RC)
- This can be reproduced on device and in simulator.