当应用程序处于非活动状态并运行代码时,Swift iOS 应用程序会收到推送通知

Posted

技术标签:

【中文标题】当应用程序处于非活动状态并运行代码时,Swift iOS 应用程序会收到推送通知【英文标题】:Swift iOS app receive push notification when app is inactive and run code 【发布时间】:2020-05-15 19:09:01 【问题描述】:

平台

斯威夫特 5

ios 13+

xCode 11

节点 v14.2.0

Firebase/Firestore 最新

设置

Alice 向 Bob 发送推送通知,而 Bob 的电话是 .inactive.background。 Bob 的手机应该会收到通知并立即触发代码。

问题

这个问题有很多答案,但我能找到的大部分内容都围绕着破解 PushKit 和 CallKit 原生 API 以发送 .voIP 推送。根据这个问题 (iOS 13 not getting VoIP Push Notifications in background),Apple 不再允许您发送 .voIP 推送而不触发 CallKit 的本机电话响铃例程。

在 iOS 端,我在 AppDelegate.swift 中有以下位

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool 
        
        registerForPushNotifications()
 
    
 
    func application(_ application: UIApplication,
                     didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void)
    
        
        print(">>> I would like this to be triggered even if the app is asleep")
        
        switch application.applicationState 
            case .active:
                print(">>>>>>> the app is in [FOREGROUND]: \(userInfo)")
                break
            case .inactive, .background:
                print(">>>>>>>> the app is in [BACKGROUND]: \(userInfo)")
                break
            default:
                break
        
    
    

    func registerForPushNotifications() 

        UNUserNotificationCenter.current().delegate = self
        
        UNUserNotificationCenter
            .current()
            .requestAuthorization(options:[.alert, .sound, .badge]) [weak self] granted, error in
                guard granted else  return 
                self?.getNotificationSettings()
        
    
        
    func getNotificationSettings() 
    
        UNUserNotificationCenter.current().getNotificationSettings  settings in
            
            guard settings.authorizationStatus == .authorized else  return 
            
            Messaging.messaging().delegate = self

            DispatchQueue.main.async 
                // Register with Apple Push Notification service
                UIApplication.shared.registerForRemoteNotifications()
                
                /// cache token client side and save in `didRegisterForRemoteNotificationsWithDeviceToken`
                if let token = Messaging.messaging().fcmToken 
                    self.firebaseCloudMessagingToken = token
                
            
        
    
    
    //@Use: listen for device token and save it in DB, so notifications can be sent to this phone
    func application(_ application: UIApplication,
        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) 
        
        if (firebaseCloudMessagingToken != nil)
            self.updateMyUserData(
                  name   : nil
                , pushNotificationToken: firebaseCloudMessagingToken!
            )
        
    

    func application(_ application: UIApplication,
        didFailToRegisterForRemoteNotificationsWithError error: Error) 
        ///print(">>> Failed to register: \(error)")
    


    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) 
        
        // @NOTE: this fires when the app is open. So you can go the call screen right away
        let payload = notification.request.content.userInfo as! [String:Any?]
        let type = payload["notificationType"]
        
        print(">> this fires if the app is currently open")

    
    
    /// @NOTE: we are using backward compatible API to access user notification when the app is in the background
    /// @source: https://firebase.google.com/docs/cloud-messaging/ios/receive#swift:-ios-10
    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter,
                                    didReceive response: UNNotificationResponse,
                                    withCompletionHandler completionHandler: @escaping () -> Void) 

        print(" this fires when the user taps on the notification message")
    

在服务器/Node.js 端,我以这种方式发送推送通知:

// Declare app push notification provider for PushKit
const _ApnConfig_ = 
    token: 
          key   : fileP8
        , keyId : "ABCDEFG"
        , teamId: "opqrst"
    ,
    production: false
;
 
var apnProvider = new apn.Provider(_ApnConfig_);


exports.onSendNotification = functions.https.onRequest((request, response) => 

    var date = new Date();
    var timeStamp = date.getTime();

    const deviceTok = "..."

    var recepients = [apn.token( deviceTok )] 

    const notification = new apn.Notification();    
    notification.topic = "com.thisisnt.working"


    notification.body = "Hello, world!";    


    notification.payload = 
          from: "node-apn"
        , source: "web"
        , aps: 
              "content-available": 1
            , "data" :  "custom_key":"custom value", "custom_key_2":"custom value 2" 
               
    ;


    notification.body = "Hello, world @ " + timeStamp;

    return apnProvider.send(notification, recepients).then(function(res)   


        console.log("res.sent: ", res.sent)
        console.log("res.failed: ", res.failed)

        res.failed.forEach( (item) => 
            console.log(" \t\t\t failed with error:", item.error)
        )

        return response.send("finished!");

    ).catch( function (error) 

        console.log("Faled to send message: ", error)
        return response.send("failed!");

    )
)

两者都很标准。我已将content-availabe 设置为1。现在消息正在通过 Apple 推送通知中心显示并显示,它们只是没有按预期触发带有 didReceiveRemoteNotification 的块。

【问题讨论】:

【参考方案1】:

您需要启用后台模式 - 远程通知功能。

要接收后台通知,您必须将远程通知后台模式添加到您的应用中。在 Signing & Capability 选项卡中,添加 Background Modes 功能,然后选中 Remote notification 复选框。

启用远程通知后台模式:

对于 watchOS,将此功能添加到您的 WatchKit 扩展中。

来源:Pushing Background Updates to Your App | Apple Developer Documentation

【讨论】:

以上是关于当应用程序处于非活动状态并运行代码时,Swift iOS 应用程序会收到推送通知的主要内容,如果未能解决你的问题,请参考以下文章

通过Swift中的触摸检测应用程序何时处于活动状态或非活动状态[重复]

当应用程序处于非活动状态时,GCM 推送通知不适用于 iOS

当应用程序处于非活动状态时可以接收 UIAccelerometer 更新吗?

当用户/选项卡/浏览器处于非活动状态时 JavaScript 播放声音

Ios:当应用程序处于非活动状态时,如何让我的应用程序返回根视图

使按钮处于非活动状态并更改不透明度