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:
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?
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.