didReceiveRemoteNotification when in background
Asked Answered
B

6

57

These kind of question has been asked a number of times but i have some specific situation going on.

When my application is active and I receive a PUSH message i'm successfully able to parse the custom payloads and such.

However when my application is in the background and the PUSH arrives the user has to click on the 'View/Open' button in order to get the didReceiveRemoteNotification called and the didFinishLaunchingWithOptions is called after that.

I need to have my application decide if they have to prompt the user with an UIAlert when in the background or suppress the push message based on some local settings.

Any help would be appreciated,

Bummer answered 20/2, 2011 at 11:21 Comment(0)
S
154

You app needs to handle all the possible push notification delivery states:

  • Your app was just launched

  • Your app was just brought from background to foreground

  • Your app was already running in the foreground

You do not get to choose at delivery time what presentation method is used to present the push notification, that is encoded in the notification itself (optional alert, badge number, sound). But since you presumably are in control of both the app and the payload of the push notification, you can specify in the payload whether or not there was an alert view and message already presented to the user. Only in the case of the app is already running in the foreground do you know that the user did not just launch your app through an alert or regularly from the home screen.

You can tell whether your app was just brought to the foreground or not in didReceiveRemoteNotification using this bit of code:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    if ( application.applicationState == UIApplicationStateActive )
        // app was already in the foreground
    else
        // app was just brought from background to foreground
    ...
}
Shoop answered 20/2, 2011 at 12:12 Comment(5)
There is no "check with the app when running in the background" case, the cases available are listed in my answer (and diwup's answer, where the user taps "cancel").Shoop
If your app is in bg once the pn comes it, you will get noticed by the system. If you ignore the notification and bring your app to fg later on (because the badge is still there), you have no change to retrieve the pn. What i mean is: There is no call to didReceiveRemoteNotification while the app is in bgEdile
@neil: your use of "you" is ambiguous, you need to distinguish between actions of: the user, the system, and application. If the app is in the background or not launched and the user taps "cancel," then no notification is delivered to the app. If the user taps "view" then there are two cases: app in background, app not lauched. If the app is in the foreground, this consititues the 3rd delivery case, thus the 3 cases of delivery in my answer. Obviously, if app is in the background, and user taps "cancel", nothing is delivered, and there's nothing to handle.Shoop
In iOS 7, with the remote-notification background mode, I don't think this technique works anymore, because the app can handle a remote notification in the background without bringing it to the foreground. So then how do you tell if it's brought from the background to the foreground?Jordaens
with new iOS versions, it is not surprising that things change.Shoop
Q
20

You have to do several things in order to manage the received push notification when the app is in background.

First, in your server side, you have to set {"aps":{"content-available" : 1... / $body['aps']['content-available'] =1; in the push notification payload.

Second, in your Xcode project, yo have to habilitate "remote notifications". It is made by going to the project's target -> capabilities, then enable the capabilities switch, and check the remote notifications checkbox.

Third, instead of using didReceiveRemoteNotification, you have to call application:didReceiveRemoteNotification:fetchCompletionHandler:, this will allow you to do the tasks that you want in the background, at the moment of receiving the notification:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler 
{

 if(application.applicationState == UIApplicationStateInactive) {

     NSLog(@"Inactive - the user has tapped in the notification when app was closed or in background");
     //do some tasks
    [self manageRemoteNotification:userInfo];
     completionHandler(UIBackgroundFetchResultNewData);
 }
 else if (application.applicationState == UIApplicationStateBackground) {

     NSLog(@"application Background - notification has arrived when app was in background");
     NSString* contentAvailable = [NSString stringWithFormat:@"%@", [[userInfo valueForKey:@"aps"] valueForKey:@"content-available"]];

     if([contentAvailable isEqualToString:@"1"]) {
         // do tasks
         [self manageRemoteNotification:userInfo];
         NSLog(@"content-available is equal to 1");
         completionHandler(UIBackgroundFetchResultNewData);
     }
 }
 else {
     NSLog(@"application Active - notication has arrived while app was opened");
        //Show an in-app banner
         //do tasks
        [self manageRemoteNotification:userInfo];
         completionHandler(UIBackgroundFetchResultNewData);
     }
 }

Finally, you have to add this notification type: UIRemoteNotificationTypeNewsstandContentAvailability to the notifications settings when you set it.

Apart from this, if your app was closed when the notification arrived, you have to manage this in didFinishLaunchingWithOptions , and just if the user taps on the push notification: The way of do it is:

if (launchOptions != nil)
{
    NSDictionary *dictionary = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

    if (dictionary != nil)
    {
        NSLog(@"Launched from push notification: %@", dictionary);
        [self manageRemoteNotification:dictionary];
    }
}

launchOptions is != nil when you launch the app by tapping on the push notification, if you access it by tapping on the icon, launchOptions will be == nil.

I hope it will be useful. Here it is explained by Apple.

Quack answered 15/1, 2016 at 13:28 Comment(0)
G
16

Pass content-available = 1 with your payload, and will invoke didReceiveRemoteNotification even in background. e.g.

{
    "alert" : "",
    "badge" : "0",
    "content-available" : "1",
    "sound" : ""
}
Goddamn answered 30/12, 2014 at 15:21 Comment(3)
No, it doesn't. I've test it more than once, "content-available" is used with Background Fetch and it's not 100% reliable because iOS is the one who decides when to start the background fetchThoroughgoing
content-available is not associated with Background Fetch. It is for push notifications that launch the app in the background.Floyfloyd
in fact, content-available is irrelevantMylo
B
8

One thing to keep in mind, when your push message arrives at the user's iPhone and they click "cancel", except for the icon badge number (they'll be taken care of by the OS), there would be no way for your in-the-background app to know about this push event and take any further actions.

Bingen answered 20/2, 2011 at 12:10 Comment(1)
Email Apple and ask them to provide APIs to support what you want :-)Bingen
G
2

Word of warning

I think your app logic is basing behavior on custom data in your push notification. This is not what push notifications are intended for. What you should alternatively do on didbecomeactive in your application is just ask your server for the data you need and was sending as payload anyway, and rely on that instead of your payload.

Because the documentation also states that as best practice. Because Apple does not guarantee your push notification from being received 100% of the times anyway.

Important: Delivery of notifications is a “best effort”, not guaranteed. It is not intended to deliver data to your app, only to notify the user that there is new data available.

However, if you want to have some indication of whether for instance the badge has been changed without relying on a user opening the app by clicking on the badge you could something like this:

A. you add a (correct) badge number to the payload of the push notification sent by your server. It for instance could look like this:

{
    "aps" : {
        "alert" : "You got your emails.",
        "badge" : 9
    }
}

B. you keep track of that badge number persistently in your app, for instance by storing it in NSUserDefaults.

Then in applicationDidBecomeActive can compare the applicationIconBadgeNumber property of UIApplication with your previously stored badge count and see if it has been increased or decreased and do some updates based on that.

 - (void)applicationDidBecomeActive:(UIApplication *)application
    {

        NSNumber *badgecount = [[NSUserDefaults standardUserDefaults] objectForKey:@"badgecount"];
        if (!badgecount) badgecount = @(0);

        if ([UIApplication sharedApplication].applicationIconBadgeNumber != [badgecount integerValue]) {
            //store the new badge count
            badgecount = [NSNumber numberWithInteger:[UIApplication sharedApplication].applicationIconBadgeNumber];
            NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
            [defaults setObject:badgecount forKey:@"badgecount"];
            [defaults synchronize];

            // do some stuff here because it's different
        }

    }
Gardia answered 5/8, 2014 at 3:15 Comment(0)
L
0

As of recent iOS - I think 8 - if you've got remote notifications enabled as a background mode, one trick is to track whether you're entering the foreground as a flag.

@interface AppDelegate ()

@property (assign, atomic, getter=isEnteringForeground) BOOL enteringForeground;

@end

- (void) applicationWillEnterForeground: (UIApplication *) application
{
    self.enteringForeground = YES;
}

- (void) applicationDidBecomeActive: (UIApplication *) application
{
    self.enteringForeground = NO;
}

- (void) application: (UIApplication *) application didReceiveRemoteNotification: (NSDictionary *) userInfo fetchCompletionHandler: (void (^) (UIBackgroundFetchResult)) completionHandler
{
    const BOOL launchedFromBackground = !(application.applicationState == UIApplicationStateActive);
    const BOOL enteringForeground = self.enteringForeground;

    if (launchedFromBackground && enteringForeground) {
        // The user clicked a push while the app was in the BG
    }
}
Lehmann answered 6/4, 2016 at 21:37 Comment(1)
better check application.applicationState as described in the other answer from @BotatyrNadia

© 2022 - 2024 — McMap. All rights reserved.