iOS:后台/已杀死状态下接收推送打开ViewController失败,求解决方案
我尝试在收到推送通知时打开指定的ViewController,当前代码在App处于前台时可以正常工作,但在后台或被杀死的状态下无法生效。以下是我的实现代码:
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) { guard let aps = payload.dictionaryPayload[AnyHashable("aps")] as? NSDictionary else { return } startCustomCallController(apnData: aps) } func startCustomCallController(apnData: NSDictionary) { let storyBord = UIStoryboard(name: "PrivateUi", bundle: nil) guard let controller = storyBord.instantiateViewController(withIdentifier: "NewIncomingCallViewController") as? NewIncomingCallViewController else{ print("Error instantiating view controller") return } controller.apnData = apnData guard let appdelegate = UIApplication.shared.delegate as? AppDelegate else { print("appdelegate is missing") return } UIView.transition(with: appdelegate.window!, duration: 0.5, options: .transitionCrossDissolve, animations: { appdelegate.window!.rootViewController = controller }, completion: nil) }请问这是不可实现的功能,还是需要进行相关修改?
这当然是可以实现的!你的代码在前台正常工作,但后台/杀死状态下失效,主要是因为这两种状态下App的启动和回调逻辑有差异,加上一些配置和线程处理的问题。下面是具体的修改方案:
1. 确保推送Payload配置正确
从你用PKPushRegistry来看,这应该是VoIP推送(如果是普通远程推送逻辑会不一样)。VoIP推送要唤醒后台/杀死状态的App,Payload必须满足:
- 包含
aps字典,且里面要有content-available: 1 - 如果你需要弹出通知提醒,还要加上
alert字段(比如alert: { "title": "来电", "body": "新的通话请求" })
示例Payload结构:
{ "aps": { "content-available": 1, "alert": { "title": "来电提醒", "body": "有新的通话请求" }, "sound": "default" }, // 你的自定义业务字段 }
2. 处理App被杀死后的启动场景
当App处于杀死状态时,收到VoIP推送会触发App启动,此时pushRegistry:didReceiveIncomingPushWith不会立即调用,而是需要在AppDelegate的application(_:didFinishLaunchingWithOptions:)里先处理启动参数中的推送数据:
在AppDelegate中添加这段逻辑:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 初始化你的VoIP推送注册(如果还没做的话) setupVoIPPushRegistry() // 检查是否是通过推送启动App if let remoteNotification = launchOptions?[.remoteNotification] as? [String: Any], let aps = remoteNotification["aps"] as? NSDictionary { // 延迟一点执行,确保window已经初始化完成 DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { self.startCustomCallController(apnData: aps) } } return true }
3. 后台状态下确保UI操作在主线程
pushRegistry:didReceiveIncomingPushWith的回调可能在后台线程执行,直接操作UI会导致异常或不生效。修改你的startCustomCallController方法,把UI操作包裹在主线程里:
func startCustomCallController(apnData: NSDictionary) { DispatchQueue.main.async { // 新增主线程包裹 let storyBord = UIStoryboard(name: "PrivateUi", bundle: nil) guard let controller = storyBord.instantiateViewController(withIdentifier: "NewIncomingCallViewController") as? NewIncomingCallViewController else{ print("Error instantiating view controller") return } controller.apnData = apnData guard let appdelegate = UIApplication.shared.delegate as? AppDelegate else { print("appdelegate is missing") return } // 确保window存在 guard let window = appdelegate.window else { print("Window not initialized") return } UIView.transition(with: window, duration: 0.5, options: .transitionCrossDissolve, animations: { window.rootViewController = controller }, completion: nil) } }
4. 检查后台权限配置
确保你的项目已经开启了VoIP后台模式:
- 打开Xcode项目的
Signing & Capabilities标签 - 添加
Background Modes权限 - 勾选
Voice over IP选项
另外,还要确保用户已经授权了通知权限(包括VoIP推送权限),在App启动时请求对应的权限:
func requestNotificationPermissions() { UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound, .badge]) { granted, error in if granted { DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() } } } }
最后补充
VoIP推送有严格的系统规则:如果你的App处理了VoIP推送但没有展示来电界面(比如CXCallController相关的系统来电UI,或者自定义的来电界面),可能会被系统限制推送权限。所以如果是通话场景,建议结合CallKit框架来实现,这样系统会更稳定地唤醒App并展示来电界面。
内容的提问来源于stack exchange,提问作者Afsar Ahamad

