简体   繁体   中英

Swift iOS app receive push notification when app is inactive and run code

Platform

Swift 5

iOS 13+

xCode 11

Node v14.2.0

Firebase/Firestore latest

Setting

Alice send push notification to Bob, while Bob's phone is .inactive or .background . Bob's phone should get notification and immediately trigger code.

Problem

This question has plenty of answers, but most of what I can find revolves around hacking the PushKit and CallKit native API to send .voIP pushes. Per this question ( iOS 13 not getting VoIP Push Notifications in background ), Apple no longer allow you to send .voIP pushes w/o triggering CallKit's native phone ring routine.

On iOS side, I have the following bits in 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")
    }

On the server/Node.js side, I send push notification this way:

// 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!");

    })
})

Both are pretty standard. I have set the content-availabe to 1 . Right now the messages are coming through and displayed by Apple Push Notification center, they're just not triggering the block with didReceiveRemoteNotification as intended.

You need to enable the background mode - remote notifications capability.

To receive background notifications, you must add the remote notifications background mode to your app. In the Signing & Capability tab, add the Background Modes capability, then select the Remote notification checkbox.

Enabling the remote notifications background mode:

在项目的 Capabilities 选项卡中启用远程通知后台模式

For watchOS, add this capability to your WatchKit Extension.

Pushing Background Updates to Your App | 将后台更新推送到您的应用程序 | Apple Developer Documentation

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM