SwiftUI多根导航视图下推送跳转后保留导航栈问题
问题根源
你当前的实现是在根视图ContentView层级下添加了一个隐藏的NavigationLink,这个链接的跳转基于根导航栈执行——无论当前导航栈已有Screen A→Screen B→Screen C,推送触发的Screen D都会直接从根栈push,因此返回时会回到根视图Screen A,而非之前的栈顶Screen C。
解决方案:用导航路径统一管理跳转
核心思路是通过NavigationStack(iOS16+)配合NavigationPath,让推送跳转追加到当前导航栈的末尾,而非从根栈启动。以下是iOS16+的最优实现:
1. 创建全局导航管理器
用ObservableObject统一管理导航路径和跳转逻辑:
import SwiftUI class NavigationManager: ObservableObject { // 用NavigationPath管理导航栈 @Published var path = NavigationPath() // 枚举所有页面,方便统一管理 enum Page: Hashable { case screenA, screenB, screenC, screenD } // 追加页面到栈尾 func push(_ page: Page) { path.append(page) } // 弹出当前页面 func pop() { path.removeLast() } // 回到根页面 func popToRoot() { path.removeLast(path.count) } }
2. 修改主App,注入导航管理器
将导航管理器作为全局环境对象注入,确保所有页面和AppDelegate能访问同一实例:
@main struct Push_Notification_POCApp: App { @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate @StateObject var notificationManager = NotificationManager() @StateObject var navigationManager = NavigationManager() // 初始化导航管理器 var body: some Scene { WindowGroup { ContentView() .environmentObject(navigationManager) // 注入环境对象 .onAppear{ appDelegate.app = self appDelegate.navigationManager = navigationManager // 传给AppDelegate Task{ await notificationManager.request() } } } } }
3. 修改AppDelegate,直接调用导航跳转
移除NotificationCenter通知,直接通过导航管理器触发跳转:
import Foundation import UIKit class AppDelegate: NSObject, UIApplicationDelegate, ObservableObject { var app: Push_Notification_POCApp? var navigationManager: NavigationManager? // 持有导航管理器引用 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { application.registerForRemoteNotifications() UNUserNotificationCenter.current().delegate = self return true } func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let stringifiedToken = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() print("stringifiedToken:", stringifiedToken) } } extension AppDelegate: UNUserNotificationCenterDelegate { func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { // 直接推送目标页面到当前栈尾 navigationManager?.push(.screenD) completionHandler() } func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions { return [.badge, .banner, .list, .sound] } }
4. 重构页面,使用导航管理器跳转
替换原有的NavigationLink为导航管理器的push方法,并用NavigationStack配合navigationDestination处理页面跳转:
struct ContentView: View { @EnvironmentObject var navigationManager: NavigationManager var body: some View { NavigationStack(path: $navigationManager.path) { // 根页面默认是Screen A ScreenA() .navigationDestination(for: NavigationManager.Page.self) { page in // 根据枚举匹配对应页面 switch page { case .screenA: ScreenA() case .screenB: ScreenB() case .screenC: ScreenC() case .screenD: ScreenFromPush() } } } } } struct ScreenA: View { @EnvironmentObject var navigationManager: NavigationManager var body: some View { Button("Open Screen B") { navigationManager.push(.screenB) } .navigationTitle("Screen A") } } struct ScreenB: View { @EnvironmentObject var navigationManager: NavigationManager var body: some View { Button("Open Screen C") { navigationManager.push(.screenC) } .navigationTitle("Screen B") } } struct ScreenC: View { var body: some View { Text("This is Screen C") .navigationTitle("Screen C") } } struct ScreenFromPush: View { var body: some View { Text("Opened From Push") .navigationTitle("Push Screen") } }
效果验证
当你从Screen A→Screen B→Screen C后,点击推送通知跳转Screen D,此时导航栈为A→B→C→D,点击返回按钮会依次回到C→B→A,完美保留原导航栈。
iOS15及以下兼容方案
如果需要兼容iOS15,无法使用NavigationStack,可以用NavigationView配合自定义数组栈(比如@Published var stack: [Page]),通过动态生成的NavigationLink或直接操作UIKit的UINavigationController实现相同逻辑,代码复杂度会略高。
内容的提问来源于stack exchange,提问作者Muhammad Noamany

