iOS 10 UNUserNotificationCenterDelegate not called. push notifications not working

Kex picture Kex · Oct 3, 2016 · Viewed 28.8k times · Source

Tearing my hair out tying to get push notifications to work in iOS10. Current setup:

in func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool:

if #available(iOS 10.0, *) {

            let center = UNUserNotificationCenter.current()
            center.delegate = self
            center.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in

                if error == nil {
                    print("DID REQUEST THE NOTIFICATION")
                    UIApplication.shared.registerForRemoteNotifications()
                }
            }
            print("DID SET DELEGATE")
        }

In func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data):

print("DID REGISTER FOR A REMOTE NOTIFICATION AND THE TOKEN IS \(deviceToken.base64EncodedString())"           
let request = UpdatePushNotificationSubscription_Request(deviceToken: deviceToken)
updatePushNotificationSubscriptionWorker.updateSubscription(request)

I have checked the token is uploaded to the backend correctly and it does indeed match.

I have also implemented:

    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {

        print("GOT A NOTIFICATION")

    }


    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

        //This is for the user tapping on the notification
        print("GOT A NOTIFICATION")
    }

I have set the entitlements for all targets and enabled push:

enter image description here

Now when I try to send a message from the backend the device just receives nothing. Delegates are not being called. Have no idea what I'm doing wrong here. Push is working for iOS9 and android devices. Any pointers to what I might be doing wrong?

Answer

Dave Wood picture Dave Wood · Jan 21, 2017

This answer pertains to iOS 10+, using the UserNotifications framework.

You need a class to conform to the UNUserNotificationCenterDelegate protocol. It doesn't matter if you create a new class just for this, or add it on to your AppDelegate class. I'd recommend creating a dedicated class though. For the purposes of this answer, let's assume you create a UserNotificationController class for it.

The class can have the following methods:

optional func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)

optional func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void)

Then in your AppDelegate.application(_:didFinishLaunchingWithOptions:) method, you need to set the delegate on the UNUserNotificationCenter.current() object to an instance of your UserNotificationController class. You'll probably want to use a shared instance.

Request authorization from the user to enable notifications using the UNUserNotificationCenter.requestAuthorization(options:completionHandler:) method, and in the completionHandler, check the granted value. If true, register for remote notifications by calling UIApplication.shared.registerForRemoteNotifications().

Now, when the app receives a push notification, there are several different situations that can happen. I'll try and list the most common cases here.

Local Notifications:

If the app is in the foreground, the app will call UserNotificationController .userNotificationCenter(_:willPresent:withCompletionHandler:).

If the app is in the background (running or not), nothing is called until the user taps the notification, at that point, the app will open and call UserNotificationController .userNotificationCenter(_:didReceive:withCompletionHandler:).

Remote Notifications:

The content of the payload will affect what happens. There are three cases for the payload, a) just the normal alert, badge, and sound options b) including the content-available option (set to 1 or true) c) including the mutable-content option (set to 1 or true). Plus there's technically d) where you have both content-available and mutable-content, but that just triggers both cases.

For a) just alert, sound, badge info:

This works the same as a local notification.

For b) content-available == true:

If the app is in the foreground, UserNotificationController .userNotificationCenter(_:willPresent:withCompletionHandler:) is called.

If the app is in the background, (running or not), AppDelegate.application(_:didReceiveRemoteNotification:fetchCompletionHandler:) is called, not one of the methods in your UserNotificationController class.

For c) mutable-content == true:

If you've added a UNNotificationServiceExtension to your application, it will handle the notification and can modify the content. This happens regardless of the state of your main application. If the (optionally modified) notification is tapped by the user it is then handled like a local notification above.

Without a UNNotificationServiceExtension, the notification is treated like a normal remote notification above.

Additional Notes:

When using mutable-content, you must include alert info in the payload, or the system will treat it as immutable and not call into your UNNotificationServiceExtension. Your modified notification must still include alert info, or the original notification payload will be used. Sadly there's no way to prevent the notification from appearing to the user.

When using content-available, if the user force-quit the app the last time they used it, the system will not relaunch the app or call AppDelegate.application(_:didReceiveRemoteNotification:fetchCompletionHandler:). Though it will still display any alert, play the sound, and update the badge as indicated in the payload.