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:


    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")
    }