Migrate watch navigation to a value-based NavigationStack - #4804
Conversation
Replaces the deprecated NavigationLink(destination:tag:selection:) links with NavigationLink(value:) and a single navigationDestination(for:) registered on the root stack. NavigationManager now owns the stack as [WatchRoute] instead of tracking a selected interface index, so taps and programmatic navigation share one source of truth. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0124af39BcDTwppPNMJJfSqn
|
Claude finished @kean's task in 3m 43s —— View job Code ReviewNice refactor — moving off A few observations, all minor: 1. Dead re-entrancy guard + unused parameter in private var navigatingToNowPlaying = false
func navigateToNowPlaying(source: Source, fromLaunchEvent: Bool) {
if navigatingToNowPlaying { return }
navigatingToNowPlaying = true
...
path = [.source(source), .interface(.nowPlaying)]
navigatingToNowPlaying = false
}Now that the body is fully synchronous, the flag is set and cleared within the same call, so it can never actually block re-entry — it's effectively dead. 2. 3. 4. Tests: None of these are blockers. Restoration mapping (including |
Generated by 🚫 Danger |
The watch app still navigated through
NavigationLink(destination:tag:selection:), deprecated since watchOS 9. Every link was driven by a selection binding, andNavigationManagertracked the pushed screen as anIntindex left over from the old WatchKit page-based API.This moves the whole watch app to a value-based stack:
NavigationManagerowns the stack as@Published var path: [WatchRoute].navigateToreplaces the stack (matching the old tag/selection behavior, where only one link could be active at a time); the newpushappends, for screens opened from within Now Playing.WatchRouteenum plusWatchRouteView, which maps a route to its screen and is registered once via.navigationDestination(for:)on the root stack, so any screen can push any route.NavigationLink(value:).SourceInterfaceNavigationViewusesNavigationStack(path:)instead of the deprecatedNavigationView.NowPlayingContainerViewloses theZStackof hidden zero-sizeNavigationLinks that existed only so buttons inside the pagedTabViewcould push. Those buttons now callnavigationModel.push(_:), which also removes thepresentViewbinding threaded throughNowPlayingOptionsandNowPlayingControls.NowPlayingRowcontained its ownNavigationLink, so inInterfaceViewit was nested inside another link. The link moved out to the two call sites (InterfaceView,UpNextView).WatchInterfaceType.interfacePosition/.indexPositionand the now-unusedNavigationManager.navigateToMainMenu()/topMostController(), which drops the lastWKInterfaceControllerusage from the navigation layer..unknown,.interface, and.filternever had a destination, soWatchRoute.init?returnsnilfor them and they stay no-ops rather than pushing a blank screen.No intended behavior change. The app still launches into the current source's interface list, which the old code achieved by pre-seeding the selection binding and this preserves by seeding
path.To test
Requires a paired Apple Watch or the watch simulator.
Checklist
CHANGELOG.mdif necessary.