44
votes

When my App is not running and receives a Push Notification, if I click on that notification, the App is launched - but then it doesn't prompt the user with the Alert-View I set up, asking them whether they want to view the Notification's contents or not. It just launches, and sits there.

The Push Notifications do work perfectly when the App is running - either as the Active app or while in the background - but nothing works correctly when the app is not running.

I tried logging-out the launchOptions NSDictionary in application: didFinishLaunchingWithOptions: to see what load its bringing - but it comes up as "(null)". So It basically contains nothing - which doesn't make sense cause shouldn't it contain the Notification's load?

Anybody have any ideas how to make Push Notifications work when they arrive while the App was NOT running?

EDIT: here's the code I'm using in application: didReceiveRemoteNotification just to see what's what:

if (UIApplicationStateBackground) {

    NSLog(@"===========================");
    NSLog(@"App was in BACKGROUND...");
}
else if (UIApplicationStateActive == TRUE) {
    NSLog(@"===========================");
    NSLog(@"App was ACTIVE");
}
else {
    [[UIApplication sharedApplication] setApplicationIconBadgeNumber: 99]; 
    UIAlertView *BOOM = [[UIAlertView alloc] initWithTitle:@"BOOM"
                                                   message:@"app was INACTIVE"
                                                  delegate:self
                                         cancelButtonTitle:@"a-ha!"
                                         otherButtonTitles:nil];
    [BOOM show];
    NSLog(@"App was NOT ACTIVE");
}

So this is supposed to take care of all the application's states - but its not. Push Notifications are only working when the app is running - either in the background or in the foreground...

================================================

UPDATE/EDIT#2: as per "@dianz" suggestion (below,) I modified the code of my application: didFinishLaunchingWithOptions to include the following:

UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (localNotif) {
    NSString *json = [localNotif valueForKey:@"data"]; 

    UIAlertView *bam = [[UIAlertView alloc] initWithTitle:@"appDidFinishWithOptions"
                                                  message:json   
                                                 delegate:self
                                        cancelButtonTitle:@"cool"
                                        otherButtonTitles:nil];
    [bam show];

}

This does make the AlertView box appear, but there seems to be no payload: the title of the AlertView shows up ("appDidFinishWithOptions"), but the json NSString comes up EMPTY... Will keep tweaking...

======================

EDIT #3 - its now working almost 100%
So, in didFinishLaunchingWithOptions:

UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
    if (localNotif) {
        // Grab the pushKey:
        pushKey = [localNotif valueForKey:@"pushKey"];
        // "pushKey" is an NSString declared globally
        // The "pushKey" in the "valueForKey" statement is part of my custom JSON Push Notification pay-load.
        // The value of pushKey will always be a String, which will be the name of the 
        // screen I want the App to navigate to. So when a Notification arrives, I go
        // through an IF statement and say "if pushKey='specials' then push the                                      
        // specialsViewController on, etc.

        // Here, I parse the "alert" portion of my JSON Push-Notification:
        NSDictionary *tempDict = [localNotif valueForKey:@"aps"];
        NSString *pushMessage = [tempDict valueForKey:@"alert"];


        // Finally, ask user if they wanna see the Push Notification or Cancel it:
        UIAlertView *bam = [[UIAlertView alloc] initWithTitle:@"(from appDidFinishWithOptions)"
                                                      message:pushMessage  
                                                     delegate:self
                                            cancelButtonTitle:@"Cancel"
                                            otherButtonTitles:@"View Info", nil];
        [bam show];

    }

I next implement the alertView: clickedButtonAtIndex method to see what the user chose on the AlertView and proceed accordingly.

This, along with the didReceiveRemoteNotification logic works perfectly.

HOWEVER... when the app is NOT running, and I send it a Push Notification, if I DON'T click on the Push Notification alert as soon as it arrives and instead wait for it to fade out (which happens after like 3-4 seconds), and then I click on the App's icon - which now has a BADGE of 1 on it - the app launches, but I don't get the Push Notification alert at all when it launches. It just sits there.

Guess I need to figure that permutation next...

8
so were you able to get notification when you are not clicking push notification and clicking the app icon directly?Vaibhav Saran
Do you get this working. ? I mean how to handle the Push notifications when the App is in not running state ? What if, if you receive many notifications & you did not open the app, neither did you tap the system's notification panel. How are you preserving those push for a later retrieval.Balram Tiwari
Did you happen to get the last permutation work?Vijay Tholpadi
Does this work in iOS 8.X? With my experimentation it appears that unless user clicks on the notification, the content is lost forever (i.e it is not like it is stored and can be retrieved when the user launches the app). Also, the weird thing I am seeing is that even if I terminate the app and click on an incoming notification after this, the notification is handled in - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handlerSmart Home
did you solve it out? @sirab33,I also need answer for that too?I still cant get the data when the application is not running and when the notification comes in,without tapping it,still cant get the data.Any Help?Thiha Aung

8 Answers

37
votes

When your app is not running or killed and you tap on push notification this function will trigger;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

so you should handle it like this,

UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (localNotif) {
    NSString *json = [localNotif valueForKey:@"data"];
    // Parse your string to dictionary
}
11
votes

Better be if you override didReceiveRemoteNotification Here you go

NSDictionary * pushNotificationPayload = [launchOptions valueForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if(pushNotificationPayload) {
    [self application:application didReceiveRemoteNotification:pushNotificationPayload];
}

This will again fire the didReceiveRemoteNotification method and your push notification code will execute as same as it runs during application run time.

7
votes

I tried @dianz's answer, It's not worked for me but I got a help from the answer.

In my case;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

NSDictionary *apnsBody = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
if (apnsBody) {
    // Do your code with apnsBody
}
5
votes

Although this has been answered, none of the provided answer actually worked correctly. Here is my implementation that I was able to get working.

This code goes in application:didFinishLaunchingWithOptions:

Swift:

if let options = launchOptions, notification = options[UIApplicationLaunchOptionsRemoteNotificationKey] as? [NSObject : AnyObject] {
    // NOTE: (Kyle Begeman) May 19, 2016 - There is an issue with iOS instantiating the UIWindow object. Making this call without
    // a delay will cause window to be nil, thus preventing us from constructing the application and assigning it to the window.
    Quark.runAfterDelay(seconds: 0.001, after: {
        self.application(application, didReceiveRemoteNotification: notification)
    })
}

Objective-C:

if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
    [self application:application didReceiveRemoteNotification:launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]];
}

A couple notes:

  • Possibly due to the way iOS queues allocations, the root UIApplication.sharedApplication().window object is nil when launching the application after being killed. Adding a 1 millisecond delay fixed this issue. Did not test this in Objective-C

  • Casting to [NSObject: AnyObject] was required to keep from having type conflicts but I did not experiment with this much; keep it in your mind when implementing in your own project. Objective-C does not require this.

  • Quark is just a fun little library I use in all my projects that contains helpful and commonly used extensions and methods. Simply use any form of delay that fits your application needs.

4
votes

I had the same problem but I finally could handle Push Notification when the is Inactive (not running) using Notification Service Extension. This solution was recommended by Apple Team Support and it is only for iOS 10, I implemented it and it works well! I leave the link:

https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/ModifyingNotifications.html

This is the procedure:

  1. Create a new Notification Service Extension for your app. Open your project and press File - New - Target and select on iOS section, the option "Notification Service Extension". Put name of extension and the info that is required. After that, Xcode will added two files with Objective C language: the .h and .m file.

NOTE: Consider that you will use three identifiers: 1) Identifier for your app (you already have) 2) Identifier for your extension (you will create) 3) Identifier for App Group. (you will create)

  1. Is necessary to enable App Groups to create a resource where you can save and read info for your app and the extension. Click on "Show Project Navigator". Into the list of targets select your main project. Then, click on "Capabilities" and switch on the option named "App Groups". Please add the identifier for App Group to identify share resources. You should do the same step for the target of extension that you created (Select target of extension - Capabilities - Switch on at "App Groups" - Add the same identifier for App Group)

  2. You should to add the Identifier for your extension into Apple Developer Site for identify the Notification Service Extension, also you should to make new Provisional Profiles (Development, AdHoc and/or Production) and associate it with the new Provisional Profiles.

  3. On both identifiers (App and Extension) you should edit them and enable "App Groups" Service in both of them. You should to add the Identifier for App group into the App Groups Services.

NOTE: Identifier for App and Identifier for Extension SHOULD HAVE THE SAME IDENTIFIER FOR APP GROUP.

  1. Download the new Provisional Profile on Xcode and associate them with your Notification Service Extension. Please ensure you everything is ok with them.

  2. After that, into "Capabilities" of your app and extension, open "App Groups" section and update them. The three steps - 1)Add the App Groups entitlements to your entitlements file, 2) App the App Groups feature to your App ID and 3) Add App Groups to your App ID - should be checked.

  3. Come back to Project navigator and select the folder of you extension. Open the .m file. You will see a method called didReceiveNotificationRequest:(UNNotificationRequest *)request. Into this method you will create a different NSUserDefaults with SuiteName exactly equal to the Identifier for app group like this:

    NSUserDefaults *defaultsGroup = [[NSUserDefaults alloc] initWithSuiteName: @"identifier for app group"];

  4. Into this same method, get the body of the notification and save it into a NSMutableArray, then save it in the share resources. Like this:

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler{
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];

    NSMutableArray *notifications = [NSMutableArray new];
    NSUserDefaults *defaultsGroup = [[NSUserDefaults alloc] initWithSuiteName: @"Identifier for app group"];
    notifications = [[defaultsGroup objectForKey:@"notifications"] mutableCopy];
    if (notifications != nil){
        [notifications addObject:self.bestAttemptContent.userInfo];
    }else{
        notifications = [NSMutableArray new];
        [notifications addObject:self.bestAttemptContent.userInfo];
    }
    [defaultsGroup setObject:notifications forKey:@"notifications"];    
}
  1. Finally, into the App Delegate of your main project, in your didFinishLaunchingWithOptions method, recover the Array into the share resources with this code:
NSUserDefaults *defaultsGroup = [[NSUserDefaults alloc] initWithSuiteName: @"Identifier for app group"];
NSMutableArray *notifications = [[defaultsGroup objectForKey:@"notifications"] mutableCopy];
  1. Enjoy it!

I hope to be clear with each step. I going to write a post to implement this solution with images in my page. Another point that you should consider is it doesn't work with Silent Push Notification.

1
votes

Try going to the didReceiveRemoteNotification method and handling it there. In there you can check for the application states by doing conditionals for the applicationState, for example checking if it's UIApplicationStateActive or others.

0
votes

try this

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    [self.window makeKeyAndVisible];

    //register notifications
    if([application respondsToSelector:@selector(registerUserNotificationSettings:)]) // ios 8
    {
        [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert|UIUserNotificationTypeBadge|UIUserNotificationTypeSound categories:nil]];
        [application registerForRemoteNotifications];
    }
    else // ios 7
    {
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge];
    }

    // open app from a notification
    NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

    if (notification) 
    {
        //your alert should be here
    }

    // Set icon badge number to zero
    application.applicationIconBadgeNumber = 0;

    return YES;
}
0
votes

Swift 3 and xCode 8.3.2:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // your code here

    // Check if app was not running in background and user taps on Push Notification
    // This will perform your code in didReceiveRemoteNotification where you should handle different application states
    if let options = launchOptions, let notification = options[UIApplicationLaunchOptionsKey.remoteNotification] as? [NSObject : AnyObject] {
        self.application(application, didReceiveRemoteNotification: notification)
    }

    return true
}