You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI多根导航视图下推送跳转后保留导航栈问题

解决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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 07:17:34