Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS 10 UNUserNotificationCenterDelegate not called. push notifications not working

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?

like image 564
Kex Avatar asked Oct 03 '16 07:10

Kex


People also ask

How do I fix Apple push notifications?

You can fix an iPhone that's not getting notifications by restarting it or making sure notifications are turned on. You should also make sure your iPhone is connected to the internet so apps can receive notifications. If all else fails, you should try resetting the iPhone — just make sure to back it up first.

How do I enable push notifications on iOS?

Go to Settings and tap Notifications. Select an app under Notification Style. Under Alerts, choose the alert style that you want. If you turn on Allow Notifications, choose when you want the notifications delivered — immediately or in the scheduled notification summary.

Does iOS support push notification?

Apple has been supporting push notifications on OS X via the Apple Push Notification Service (APNS). Developers also use APNS to deliver a notification to iOS devices.


3 Answers

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.

like image 129
Dave Wood Avatar answered Sep 24 '22 17:09

Dave Wood


Try making your AppDelegate class implement the UNUserNotificationCenterDelegate protocol instead of a separate class implementing the protocol.

I had a separate class for the delegate and it didn't work. This is how my code looked when I got it working:

import UIKit import UserNotifications  @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate {  var window: UIWindow?  func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {     // Override point for customization after application       UIApplication.shared.registerForRemoteNotifications()      let center = UNUserNotificationCenter.current()     center.delegate = self //DID NOT WORK WHEN self WAS MyOtherDelegateClass()      center.requestAuthorization(options: [.alert, .sound, .badge]) {         (granted, error) in             // Enable or disable features based on authorization.             if granted {                 // update application settings             }     }      return true }  func userNotificationCenter(_ center: UNUserNotificationCenter,                             willPresent: UNNotification,                             withCompletionHandler: @escaping (UNNotificationPresentationOptions)->()) {     withCompletionHandler([.alert, .sound, .badge]) }  func userNotificationCenter(_ center: UNUserNotificationCenter,                             didReceive: UNNotificationResponse,                             withCompletionHandler: @escaping ()->()) {     withCompletionHandler() }  // and so forth for your other AppDelegate stuff 
like image 36
PO Bengtsson Avatar answered Sep 20 '22 17:09

PO Bengtsson


I struggled with this for a full day. Sometimes I was getting notifications, sometimes not, but I could never get the userNotificationCenter(_:willPresent:completionHandler:) callback to to triggered.

It turned out there were two problems. The first I'm still a little confused by: my target deployment version was set to 11.0 but my project was set to 10.0. Changing the project to 11.0 was the first step. I don't fully understand this but maybe there is some difference in notification handling between 10.0 and 11.0?

The second part was the notification center delegate. I noticed Apple's note in the docs:-

Important

You must assign your delegate object to the UNUserNotificationCenter object before your app finishes launching. For example, in an iOS app, you must assign it in the application(:willFinishLaunchingWithOptions:) or application(:didFinishLaunchingWithOptions:) method of your app delegate. Assigning a delegate after these methods are called might cause you to miss incoming notifications.

I had been setting in a separate "notification manager" class, which is where the callbacks were also located (as delegate protocol implementations). I had instantiated this as an instance var in my app delegate, assuming this would be created early and not cause a problem.

I messed around with the instantiation etc for a while but only when I set the delegate in my application(_:didFinishLaunchingWithOptions:) method and implemented the delegate callbacks in the app delegate did I manage to fix it.

So my code became, basically:-

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

    // You MUST do this HERE and nowhere else!
    UNUserNotificationCenter.current().delegate = self

    // other stuff

    return true
}

extension AppDelegate: UNUserNotificationCenterDelegate {

    // These delegate methods MUST live in App Delegate and nowhere else!

    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
        if let userInfo = notification.request.content.userInfo as? [String : AnyObject] {
        }
        completionHandler(.alert)
    }

    @available(iOS 10.0, *)
    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        if let userInfo = response.notification.request.content.userInfo as? [String : AnyObject] {
        }
        completionHandler()
    }
}
like image 37
Echelon Avatar answered Sep 21 '22 17:09

Echelon