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

SwiftUI中App被杀死后,点击推送通知跳转指定页面的问题

SwiftUI冷启动时点击推送跳转指定页面解决方案

你的问题核心是APP被杀死(冷启动)时,推送通知的跳转逻辑无法触发,原因是冷启动阶段ContentView的通知观察者还未注册,而AppDelegate的通知已经发出,同时你的代码还存在通知名称不匹配的低级错误,先逐一解决:

1. 修复通知名称不匹配问题

你的AppDelegate中发送的通知名称是request_notification,但ContentView监听的是message_notification,这会导致所有场景下都可能收不到通知,先统一为全局常量避免拼写错误:

extension Notification.Name {
    static let notificationTapped = Notification.Name("notificationTapped")
}

2. 处理冷启动的参数传递逻辑

冷启动时,APP会先执行AppDelegate的didFinishLaunchingWithOptions方法,此时ContentView还未初始化,无法通过NotificationCenter接收参数,需要先将推送参数暂存,等视图加载后再读取跳转。

修改AppDelegate代码

提取通用的推送处理逻辑,同时在冷启动时捕获推送参数:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // 初始化推送设置(保留你的原有代码)
    UNUserNotificationCenter.current().delegate = self
    
    // 处理冷启动时的推送参数
    if let remoteNotification = launchOptions?[.remoteNotification] as? [String: Any] {
        handleNotificationUserInfo(remoteNotification)
    }
    
    return true
}

// 通用处理推送参数的方法
private func handleNotificationUserInfo(_ userInfo: [String: Any]) {
    if let messageID = userInfo[gcmMessageIDKey] {
        print("Message ID: \(messageID)")
    }
    
    // 取出跳转所需的message_id,暂存到UserDefaults
    if let msgId = userInfo["message_id"] as? String {
        UserDefaults.standard.set(msgId, forKey: "PendingNotificationMsgId")
        // 发送通知给已初始化的视图(前后台场景)
        NotificationCenter.default.post(name: .notificationTapped, object: nil, userInfo: userInfo)
    }
}

// 原有推送响应方法,调用通用处理逻辑
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    let userInfo = response.notification.request.content.userInfo
    handleNotificationUserInfo(userInfo)
    completionHandler()
}

修改ContentView代码

使用ObservableObject管理导航状态,同时在视图加载时检查暂存的推送参数:

// 导航状态管理器,统一控制页面跳转
class NavigationManager: ObservableObject {
    @Published var shouldNavigate = false
    @Published var targetMsgId = ""
}

struct ContentView: View {
    @StateObject private var navManager = NavigationManager()
    @State private var checkedPendingNotification = false
    
    var body: some View {
        NavigationStack { // iOS16+用NavigationStack,iOS15及以下用NavigationView
            VStack {
                Text("Content View")
            }
            // 隐藏的导航链接,绑定导航状态
            .background(
                NavigationLink(
                    destination: NotificationsView(msgId: navManager.targetMsgId),
                    isActive: $navManager.shouldNavigate,
                    label: { EmptyView() }
                )
            )
            .onAppear {
                guard !checkedPendingNotification else { return }
                checkedPendingNotification = true
                
                // 1. 检查冷启动时暂存的推送参数
                if let msgId = UserDefaults.standard.string(forKey: "PendingNotificationMsgId") {
                    navManager.targetMsgId = msgId
                    navManager.shouldNavigate = true
                    // 清空参数,避免下次启动重复跳转
                    UserDefaults.standard.removeObject(forKey: "PendingNotificationMsgId")
                }
                
                // 2. 注册通知观察者,处理前后台场景的推送跳转
                NotificationCenter.default.addObserver(forName: .notificationTapped, object: nil, queue: .main) { notification in
                    if let userInfo = notification.userInfo, let msgId = userInfo["message_id"] as? String {
                        navManager.targetMsgId = msgId
                        navManager.shouldNavigate = true
                    }
                }
            }
        }
        .environmentObject(navManager)
    }
}

3. 进阶优化(iOS16+)

如果你的APP只支持iOS16及以上,推荐使用NavigationStack的path导航方式,更稳定且支持多级跳转:

// 修改导航管理器
class NavigationManager: ObservableObject {
    @Published var navigationPath = NavigationPath()
}

// 定义路由类型,必须遵循Hashable
enum AppRoute: Hashable {
    case notification(String)
}

// 修改ContentView
struct ContentView: View {
    @StateObject private var navManager = NavigationManager()
    @State private var checkedPendingNotification = false
    
    var body: some View {
        NavigationStack(path: $navManager.navigationPath) {
            VStack {
                Text("Content View")
            }
            .navigationDestination(for: AppRoute.self) { route in
                switch route {
                case .notification(let msgId):
                    NotificationsView(msgId: msgId)
                }
            }
            .onAppear {
                guard !checkedPendingNotification else { return }
                checkedPendingNotification = true
                
                if let msgId = UserDefaults.standard.string(forKey: "PendingNotificationMsgId") {
                    navManager.navigationPath.append(AppRoute.notification(msgId))
                    UserDefaults.standard.removeObject(forKey: "PendingNotificationMsgId")
                }
                
                NotificationCenter.default.addObserver(forName: .notificationTapped, object: nil, queue: .main) { notification in
                    if let userInfo = notification.userInfo, let msgId = userInfo["message_id"] as? String {
                        navManager.navigationPath.append(AppRoute.notification(msgId))
                    }
                }
            }
        }
        .environmentObject(navManager)
    }
}

关键原理说明

  • 冷启动时,推送参数会通过launchOptions传递到didFinishLaunchingWithOptions,此时无法直接通知未初始化的视图,因此用UserDefaults暂存参数。
  • 视图加载后,先检查UserDefaults中的暂存参数,触发跳转并清空参数,避免重复跳转。
  • 使用ObservableObject管理导航状态,确保状态变化能被SwiftUI视图正确捕获,触发导航链接的激活。

内容的提问来源于stack exchange,提问作者Taimoor Arif

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 01:15:31