Reputation: 10108
I'm working on a big app with a huge chunk of legacy code. Currently - there's an implementation for:
- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
The problem is that it is only called when the app is in the foreground OR when the user taps the the notification while the app is in the background. I tried to implement:
- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
But the app behaves the same. In any case - this method is not called when the app is in the background. What could be the problem?
Upvotes: 47
Views: 47172
Reputation: 444
test it on a real device!!
After spending hours trying all the answers posted here I decided to test it on a real device (it didn't work on the simulator) and it finally worked!
Upvotes: 1
Reputation: 447
There is another scenario that most of the replies are not identifying and the docs are not mentioning either.
In my case, I had my device configured with a special uncommon/unrelated setting.
Go to Settings -> General -> Background App Refresh
You are presented with 3 options:
!IMPORTANT! There are a viral videos on social network recommending setting this to "Off" or "Wi-Fi" in order to save battery life / data plan. Thats when I set this to "OFF", and didReceiveRemoteNotification was never being called in the background, but strangely enough it was called once I opened the app.
Even if you have it on "Wi-Fi / Cellular Data", if you don't have internet, it wont be called (duhh), but most important, I think the guides should recommend having this small detail covered in case there are users that fall in the "condition" of having this set to "Off" or "Wi-Fi" and being on Cellular network <- this is more common case.
The conditional for this can be retrieved at: UIApplication.shared.backgroundRefreshStatus
https://developer.apple.com/documentation/uikit/uiapplication/1622994-backgroundrefreshstatus
And the funny thing is that it will mess up your logic once the device is in low-power mode, because it will automatically change the status to "Off".
There is nowhere to find that this impacts notifications arriving when app is in background mode, so thats odd. Anyways, hope this helps -roberto
Upvotes: 1
Reputation: 2479
I fix this by adding two methods one for tap and one for foreground in appDelegate:
func application(_ application: UIApplication,
didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult)
-> Void) {
print(">>>NOTIF RECEIVED: ACTION:\(userInfo["action"] ?? "nil") DATA: \(userInfo["data"] ?? "nil")<<<")
completionHandler(UIBackgroundFetchResult.newData)
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
print(">>>TAP NOTIF RECEIVED: ACTION:\(userInfo["action"] ?? "nil") DATA: \(userInfo["data"] ?? "nil")<<<")
completionHandler()
}
Important Prerequisites:
inyourTarget/Signing&Capabilities
add background modes
and check remote notifications
check that initialize done before, in appDelegate/didfinishLaunchingWithOptions like this:
FirebaseApp.configure()
UNUserNotificationCenter.current().delegate = self
Messaging.messaging().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: { _, _ in }
)
application.registerForRemoteNotifications()
Upvotes: 0
Reputation: 2683
My device was in a bad state. I had to restart the device to get it working although I had done all the pre-requisites mentioned here.
Upvotes: 1
Reputation: 19572
I'm using iOS 14
and I did everything from the accepted answer but nothing. I needed the userInfo
from didReceiveRemoteNotification
but it would never get called from the background even when I would press the actual notification. I had to use the below method and inside that method I use UIApplication.shared.applicationState
to check for the background state. I get the userInfo
from there.
// called when notification is tapped
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
if let dict = userInfo as? [String: Any] {
let state = UIApplication.shared.applicationState
if state == .inactive || state == .background {
print("app is in BACKGROUND - userInfo: ", dict) // do something with dict when app is in the background and user taps the notification
} else {
print("app is in FOREGROUND - userInfo: ", dict) // do something with dict when app is in the foreground and user taps the notification
}
}
completionHandler()
}
Assuming you have everything else setup correctly, when you tap the notification while the app is in the background, you will get the correct print statement with the userInfo
.
Upvotes: -1
Reputation: 4965
Implementing didReceiveRemoteNotification
and didReceiveRemoteNotification:fetchCompletionHandler
is the correct way, but you also need to do the following:
Make sure to register for remote notifications, see documentation here:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
return YES;
}
Also make sure to edit Info.plist
and check the "Enable Background Modes" and "Remote notifications" check boxes:
Additionally, you need to add "content-available":1
to your push notification payload, otherwise the app won't be woken if it's in the background (see documentation here updated):
For a push notification to trigger a download operation, the notification’s payload must include the content-available key with its value set to 1. When that key is present, the system wakes the app in the background (or launches it into the background) and calls the app delegate’s application:didReceiveRemoteNotification:fetchCompletionHandler: method. Your implementation of that method should download the relevant content and integrate it into your app
So payload should at least look like this:
{
aps = {
"content-available" : 1,
sound : ""
};
}
Upvotes: 85
Reputation: 345
I am creating an iOS project that will use Firebase Cloud Messaging (FCM) to deliver custom data elements, sent via Firebase/APNS notifications, from a company server to the iOS device.
The first thing I had to understand is that unlike Android, there is no similar type of 'Service' that will be able to capture and save information I'm sending, regardless if the app is in the foreground (active), background (still in memory) or not active (not in memory). Therefore, I have to use Notification messages NOT Data messages like I had designed for Android.
After much reading to understand both Apple APNS and the Firebase interface between the iOS app and APNS server, looking at countless posts on stackoverflow and other web resources, I finally figured out how to get this to work for my requirements.
When a Firebase Cloud Messaging (FCM) Notification message is sent from the server (or Firebase Console as the FCM defaults to Notification NOT Data messages), it is delivered via APNS and presented as a notification on the iOS device. When the user taps on the notification banner, iOS does the following: if the app is not running/loaded iOS launches the app, if the app is loaded/running but in the background iOS brings the app to the foreground OR if the app is in the foreground (all three cases), the message content is then delivered via func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {}.
One thing for sure, you Must Enable Background Modes and check Remote Notification, you DO NOT have to include {"content-available" : 1} in the payload.
1) Go through the APNS and Firebase setup, pretty straight forward, generate and register certificates and such.
2) In appDelegate, didFinishLaunchingWithOptions, add:
Messaging.messaging().delegate = self as? MessagingDelegate
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
}
else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
3) Then add these call back functions to appDelegate:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
// Print message ID.
if let messageID = userInfo["gcm.message_id"] {
print("\n*** application - didReceiveRemoteNotification - fetchCompletionHandler - Message ID: \(messageID)")
}
// Print full message.
print("\n*** application - didReceiveRemoteNotification - full message - fetchCompletionHandler, userInfo: \(userInfo)")
myNotificationService?.processMessage(title: userInfo["Title"] as! String
, text: userInfo["Text"] as! String, completion: { (success) in
if success {
completionHandler(.newData)
}
else {
completionHandler(.noData)
}
})
}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler([.alert, .badge, .sound])
}
Very Helpful:
https://firebase.googleblog.com/2017/01/debugging-firebase-cloud-messaging-on.html
Upvotes: 3
Reputation: 447
Upvotes: 17
Reputation: 1536
I had the same problem. Notification banner appeared, but -application:didReceiveRemoteNotification:fetchCompletionHandler: method was not called. The solution for me that worked was to add implementation of - application:didReceiveRemoteNotification: method and forward call to -application:didReceiveRemoteNotification:fetchCompletionHandler::
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
self.application(application, didReceiveRemoteNotification: userInfo) { (UIBackgroundFetchResult) in
}
}
Upvotes: 8