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

UNUserNotificationCenterDelegate didReceive在应用未运行时未触发本地通知响应

我之前也踩过这个一模一样的坑!问题核心在于应用冷启动时,UNUserNotificationCenterDelegate还没来得及完成注册,导致系统无法触发didReceive回调。下面是分场景的完整解决方案,覆盖iOS12及更早、iOS13+的情况:

一、先把代理设置时机提前

不管是哪种iOS版本,代理设置一定要在应用启动的最早阶段完成,不能等到页面加载后再设置:

  • iOS12及更早(仅AppDelegate):在application:didFinishLaunchingWithOptions:里直接设置代理
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 优先设置通知中心代理
        UNUserNotificationCenter.current().delegate = self
        // 其他初始化代码...
        return true
    }
    
  • iOS13+(含SceneDelegate):除了AppDelegate,还要在Scene的初始化方法里再设置一次代理
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // 同样优先设置代理
        UNUserNotificationCenter.current().delegate = self
        // 其他Scene初始化代码...
    }
    
二、处理冷启动时的通知参数

应用关闭后点击通知启动,通知信息会被包含在启动选项里,这时候要先提取参数,再统一处理跳转:

1. AppDelegate中提取冷启动通知

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    UNUserNotificationCenter.current().delegate = self
    
    // 检查启动参数里的通知响应
    if let notificationResponse = launchOptions?[.notificationResponse] as? UNNotificationResponse {
        // 这里不要直接跳转,因为界面可能还没初始化完成
        // 可以用通知中心转发,或者延迟执行
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
            self.handleNotificationJump(notificationResponse)
        }
    }
    return true
}

2. SceneDelegate中提取冷启动通知

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    UNUserNotificationCenter.current().delegate = self
    
    // 检查Scene连接参数里的通知响应
    if let notificationResponse = connectionOptions.notificationResponse {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
            self.handleNotificationJump(notificationResponse)
        }
    }
}
三、统一跳转逻辑

把跳转代码抽成公共方法,不管是冷启动、前台还是后台收到通知,都调用同一个方法,避免重复代码:

// 公共跳转方法
private func handleNotificationJump(_ response: UNNotificationResponse) {
    // 从通知的userInfo里获取你预先设置的跳转标识(比如目标VC的Storyboard ID)
    let userInfo = response.notification.request.content.userInfo
    guard let targetVCId = userInfo["targetVCIdentifier"] as? String else { return }
    
    // 获取当前根控制器,这里根据你的项目结构调整(比如是NavigationController还是TabBarController)
    guard let window = UIApplication.shared.windows.first,
          let rootNav = window.rootViewController as? UINavigationController else { return }
    
    // 实例化目标ViewController并跳转
    let targetVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: targetVCId)
    rootNav.pushViewController(targetVC, animated: true)
}

然后在UNUserNotificationCenterDelegate的回调里也调用这个方法:

func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    handleNotificationJump(response)
    completionHandler() // 一定要调用完成回调
}
四、关键注意事项
  • 发送通知时,一定要在userInfo里添加唯一的跳转标识(比如目标VC的ID、页面类型枚举值),否则无法判断要跳转到哪个页面;
  • 冷启动时不要立刻跳转,因为界面可能还没初始化完成,用DispatchQueue.main.asyncAfter延迟一点执行会更稳妥;
  • 如果项目用了TabBarController,跳转前要先切换到对应的Tab,再push目标VC。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:09:02