46

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?

YogevSitton
  • 10,068
  • 11
  • 62
  • 95
  • 1
    It's normal that the method won't be called when app is in the background. System will notify you the notification through prompting a alert view or a banner. – KudoCC Jul 16 '15 at 09:35
  • What's the iOS version of your device? Is it at least 7? Because `fetchCompletionHandler:` requires minimum iOS 7. – Islam Jul 16 '15 at 10:17

8 Answers8

84

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:

enter image description here

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 : ""
    };
}
Baris Akar
  • 4,895
  • 1
  • 26
  • 54
  • 1
    I'm already calling registerForRemoteNotificationTypes and Remote Notifications is enabled – YogevSitton Jul 16 '15 at 09:57
  • 7
    @godmoney See my update answer, you need to add `"content-available":"1"` to your notification payload... – Baris Akar Jul 16 '15 at 10:32
  • 1
    @BarisAkar Great. I did not know about `content-available` payload. +1 – Ashish Kakkad Jul 16 '15 at 12:24
  • 2
    I am enabled Background mode for remote notifications and Payload should contain content-available:1 key-value pair But not call my background method – priyanka gautam Nov 05 '15 at 07:20
  • 1
    @priyankagautam Sorry, but I don't know, what could cause your problem. Everything you need to do is actually described in this answer... – Baris Akar Nov 05 '15 at 09:22
  • what if I didn't want to call `didReceiveRemoteNotification` when I launch the app by icon? – pqteru Sep 21 '16 at 06:46
  • For those who are looking for this in new Xcode interface, take a look at your target -> Capabilities tab -> Background modes -> On – Kirk Hammett Jan 18 '17 at 12:39
  • @BarisAkar the link in the answer is broken. Could you help fix it? – Yuchen Jan 15 '18 at 23:09
  • That did it. 1+ – Baran Feb 20 '18 at 16:16
  • Without "sound": "" -> Thanks @onmyway133 It saved me..... iOS 10 its working fine... But not working on iOS 11, Any suggestions? – Gopik Jul 17 '18 at 21:28
  • Not Info.plist but Capabilities there you can add Background Modes – Michał Ziobro Oct 31 '19 at 11:16
  • 2
    The same issue is occurring for iOS 14. `didReceiveRemoteNotification` method never gets called when app is in background(not force close). Any suggestion for this? I followed the exact procedure and added "content-available" : 1 in the aps. `didReceiveRemoteNotification` only gets called in foreground. – Atikul Gazi Nov 01 '20 at 15:20
  • I *thought* I had this problem because the notification wasn't arriving immediately. Then I decided to wait, and wait, and wait. Over a full minute after receiving the "banner" notification while my app was in the background, my app delegate's didReceiveRemoteNotification method was called. No idea why it's taking so long, and I had to make a minor change to improve the UX. – ScottyB Jan 02 '21 at 02:19
  • 1
    Came back to add that the long delay in receiving the notification in the background was only with APNS Sandbox environment. The notification to Prod is very quick. – ScottyB Jan 03 '21 at 16:27
18
  1. Register for push notification in app delegate.
  2. Add background mode in app capabilities.
  3. Add "content-available"="1" while sending the push notification(if you are using firebase replace "content-available"="1" by "content_available"="true" while sending the push notification from server side).
Mahadev Mandale
  • 449
  • 5
  • 16
8

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

    }
}
ZaEeM ZaFaR
  • 1,508
  • 17
  • 22
  • Hi ZaEem, have you found any solution – Usman Nisar Nov 04 '16 at 12:58
  • 1
    when the app is the background, sending a remote push notification only this method is called `func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject])` – Wilson Nov 04 '16 at 14:15
  • @UsmanNisar I have added the workaround for my problem and its working fine for me. is it not working for you ? – ZaEeM ZaFaR Nov 07 '16 at 06:11
3

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://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CreatingtheNotificationPayload.html

https://firebase.googleblog.com/2017/01/debugging-firebase-cloud-messaging-on.html

AndyW58
  • 345
  • 2
  • 6
1

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.

ArunGJ
  • 2,685
  • 21
  • 27
0

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()
    
Mahdi Moqadasi
  • 2,029
  • 4
  • 26
  • 52
0

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:

  1. Off
  2. Wi-Fi
  3. Wi-Fi Cellular Data

!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

WilliamX
  • 357
  • 4
  • 17
-1

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.

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256