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.
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
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()
}
}
As well as making sure you use the AppDelegate
for the UNUserNotificationCenter
delegate, and setting the delegate in the application:didFinishLaunchingWithOptions
method on launch, make sure you also remember to import UserNotifications
and UserNotificationsUI
.
Maybe not set UNNotificationCategory
's intentIdentifiers
, if code like this:
UNNotificationCategory* expiredCategory = [UNNotificationCategory
categoryWithIdentifier:@"TIMER_EXPIRED"
actions:@[snoozeAction, stopAction]
intentIdentifiers:@[]
options:UNNotificationCategoryOptionCustomDismissAction];
it will not called UNUserNotificationCenter's delegate methods, so you must set intentIdentifiers like this :
UNNotificationCategory* expiredCategory = [UNNotificationCategory
categoryWithIdentifier:@"TIMER_EXPIRED"
actions:@[snoozeAction, stopAction]
intentIdentifiers:@[@"a",@"b"]
options:UNNotificationCategoryOptionCustomDismissAction];