Search code examples
swiftapple-push-notificationsios13xcode11pushkit

iOS 13 Xcode 11: PKPushKit and APNS in one App


After 30th April 2020, Apple is not accepting build from Xcode 10. It asks to upload the build for iOS 13 SDK. I tried same and now I am getting crashes that with following error.

[PKPushRegistry _terminateAppIfThereAreUnhandledVoIPPushes]

My application is a social media app which contains audio/video calls from Twilio, chat, feeds post and many other functionalities. It contains Push Notifications for several purpose. Now the app is either not receiving pushes or crashing when it receives push (in background or killed state). When I search, I found that I am not allowing to use PushKit if my app is not presenting Callkit incoming call screen OR app is not handling VOIP notification. My app contains both kind of notifications i.e VOIP and Non VOIP. So, it means that I have to use both notifications i.e PushKit and APNS.

Could you please help me how to implement both notifications in single app? Can I achieve my target through PushKit only? What changes do I need to make in my app to implement? Any other turn around solution?

Looking for your suggestions.


Solution

  • The short answer is:

    You will need to implement both pushes in your app

    You may only use PushKit for pushes that represent new incoming calls to your app, and you must ALWAYS present the CallKit screen with a new incoming call when you receive a push through PushKit.

    For other notifications you may want to send, you must use regular pushes.


    How to implement that?

    Firstly, your app will need to register for both pushes with apple and get both push tokens.

    To register for VoIP, you will use PushKit:

    class PushService {
        private var voipPushRegistry: PKPushRegistry?
    
        func registerForVoipPushes() {
            voipPushRegistry = PKPushRegistry(queue: DispatchQueue.main)
            voipPushRegistry!.delegate = self
            voipPushRegistry!.desiredPushTypes = Set([PKPushType.voIP])
        }
    }
    

    Using a PKPushRegistryDelegate, you get the VoIP token:

    extension PushService: PKPushRegistryDelegate {
        func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
            print("VoIP token: \(pushCredentials.token)")
        }
    }
    

    To register for regular pushes:

    let center = UNUserNotificationCenter.current()
    let options: UNAuthorizationOptions = [.alert, .badge, .sound];
    center.requestAuthorization(options: options) {
        (granted, error) in
        guard granted else {
            return
        }
            
        DispatchQueue.main.async {
            UIApplication.shared.registerForRemoteNotifications()
        }
    }
    

    You will get your regular pushes token in you AppDelegate:

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print("Regular pushes token: \(deviceToken)")
    }
    

    Now that you have both tokens, you will send them both to your server. You will have to refactor your server side to accept both tokens and choose the correct one for each type of push that you are sending to your users.

    There are 4 different kinds of pushes you can send:

    • VoIP (token: VoIP): Use it only for notifying about an incoming call. No exceptions.

    • Regular (token: Regular): Use it when all the information you need to write the notification message is available in your server side. Your app will not run any code when receiving this push, the iOS will only present the notification and will not wake up your app.

    • Notification Service Extension (token: Regular): You can use this push when you need some information that's only available on the client side. To use it, simply add the flag mutable-content: 1 to your push (on your server side), and implement a Notification Service App Extension in your app. When the iOS receives a push with this flag, it will wake up your app extension and let you run some code there. It will not wake up your app, but you can share info between your app and its extension using app groups or the keychain. This notification will ALWAYS present an alert banner.

    • Silent (token: Regular): This push will wake up your app in background, letting you run some code, and you may not present a notification banner if you don't want to. This means that you can use this push to run some code and the user won't even notice. To use it, add the flag content-available: 1 to your push. But beware: this push's priority is really low. Silent pushes may be delayed and even completely ignored.


    How to handle the pushes in your app?

    VoIP pushes will be handled by your PKPushRegistryDelegate implementation.

    extension PushService: PKPushRegistryDelegate {
        [...]
    
        func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
            print("VoIP push received")
            //TODO: report a new incoming call through CallKit
        }
    }
    

    Mutable-content notifications will be handled by your Notification Service Extension.

    Silent pushes will be handled by your AppDelegate:

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        print("Silent push received")
    }