Test if app did become active from a UILocalNotification
Asked Answered
R

8

43

Is there a way to know if the application did become active from a local notification?

I know there is a way to test if the application was launched from a local notification alert; but if it just was sitting out there the background, and received a notification?

I need to run different code, when the app has become active:

  1. From a local notification.
  2. Just has become active :)

Is there a way to do it?

Removable answered 9/11, 2010 at 17:0 Comment(0)
E
30

I'm afraid Sylter is incorrect. When an app enters the foreground from the background, either by a direct user action or by a user response to a UILocalNotification, it does not trigger applicationDidFinishLaunchingWithOptions. It does, however, call applicationWillEnterForeground and applicationDidBecomeActive. This can be verified with a couple of NSLogs.

So, the problem remains: if an app is entering the foreground from the background, there is no way to discover if the app is entering the foreground in response to a user's response to a UILocalNotification, or if it is merely entering the foreground. Except...

Once the app has entered the foreground, it will receive the method application:DidReceiveLocalNotification: if the app entered the foreground in response to a UILocalNotification.

The problem is that any UI changes made within the application:DidReceiveLocalNotification: method in response to receiving the UILocalNotification occur after the app has already entered the foreground, creating a disturbing experience for the user.

Has anyone found a solution?

Estellaestelle answered 1/2, 2011 at 5:42 Comment(0)
S
63

I got the clue to the solution for this from @naveed's tip on checking the state of the application when the didReceiveNotification method is called. No need to check variables etc when the app resumes from the background.

On iOS7 and lower you handle the notifications like this:

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    if (application.applicationState == UIApplicationStateInactive ) {
         //The application received the notification from an inactive state, i.e. the user tapped the "View" button for the alert.
         //If the visible view controller in your view controller stack isn't the one you need then show the right one.
    }

    if(application.applicationState == UIApplicationStateActive ) { 
        //The application received a notification in the active state, so you can display an alert view or do something appropriate.
    }
}

Update for iOS 8: The following methods are now invoked when the app is opened from the background via a notification.

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void(^)())completionHandler {
}

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void(^)())completionHandler {
}

If the notifications are received while the app is in the foreground, use the methods:

- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *) userInfo {
}

- (void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
}

Note that there is no need to check application state unless you want to support older versions of the OS in your app.

Situate answered 19/5, 2011 at 10:47 Comment(7)
Bingo!!!!!!!!!! (needed the exclamation points to reach the minimum comment length...)Larrylars
I'm currently testing this on iOS 8 beta 5 and it seems that the behavior has been changed. When the application is running in background and you get a local notification, and open the app from it, didReceiveLocalNotification: is not called after applicationDidBecomeActive:.Sucre
I also observe what Markus is seeing... if the app is in the background and you get a local notification and open the app from it, didReceiveLocalNotification: is never called. I'm also on iOS 8 beta 5.Scharf
Oh! BUT, I was implementing the new iOS 8 actionable notifications, and I noticed that application:handleActionWithIdentifier:forLocalNotification:completionHandler: is called. (Not just if an action is tapped, but if the notification itself is tapped.) Hope this helps anyone else reading this in the future.Scharf
I'll update my answer to reflect this. Thanks for letting me know.Situate
Just to follow up on this: I've discovered that on iOS 8 GM, the behavior returns to the original behavior. That is, didReceiveLocalNotification: is called in response to a notification.Scharf
Yeah, this doesn't work in iOS 8.4, handleActionWithIdentifier: forLocalNotification: is not called when tapping a local notification.Amethyst
E
30

I'm afraid Sylter is incorrect. When an app enters the foreground from the background, either by a direct user action or by a user response to a UILocalNotification, it does not trigger applicationDidFinishLaunchingWithOptions. It does, however, call applicationWillEnterForeground and applicationDidBecomeActive. This can be verified with a couple of NSLogs.

So, the problem remains: if an app is entering the foreground from the background, there is no way to discover if the app is entering the foreground in response to a user's response to a UILocalNotification, or if it is merely entering the foreground. Except...

Once the app has entered the foreground, it will receive the method application:DidReceiveLocalNotification: if the app entered the foreground in response to a UILocalNotification.

The problem is that any UI changes made within the application:DidReceiveLocalNotification: method in response to receiving the UILocalNotification occur after the app has already entered the foreground, creating a disturbing experience for the user.

Has anyone found a solution?

Estellaestelle answered 1/2, 2011 at 5:42 Comment(0)
F
10

You can check the scenarios of either application is running or not when application received by following.

- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif {
    if (app.applicationState == UIApplicationStateInactive ) {
        NSLog(@"app not running");
    }else if(app.applicationState == UIApplicationStateActive )  {
        NSLog(@"app running");      
    }

    // Handle the notificaton when the app is running
    NSLog(@"Recieved Notification %@",notif);
}
Folks answered 18/4, 2011 at 20:26 Comment(0)
N
7

What I did was, I tested two scenarios, one is to put the app back to the foreground by clicking on the icon, another by URL sys call, and compared all the variables inside UIApplication, and surprisingly I finally found what I was looking for in UIApplication.h:

struct {
    unsigned int isActive:1;
    unsigned int isSuspended:1;
    unsigned int isSuspendedEventsOnly:1;
    unsigned int isLaunchedSuspended:1;
    unsigned int calledNonSuspendedLaunchDelegate:1;
    unsigned int isHandlingURL:1;
    unsigned int isHandlingRemoteNotification:1;
    unsigned int isHandlingLocalNotification:1;
    unsigned int statusBarShowsProgress:1;
    unsigned int statusBarRequestedStyle:4;
    unsigned int statusBarHidden:1;
    unsigned int blockInteractionEvents:4;
    unsigned int receivesMemoryWarnings:1;
    unsigned int showingProgress:1;
    unsigned int receivesPowerMessages:1;
    unsigned int launchEventReceived:1;
    unsigned int isAnimatingSuspensionOrResumption:1;
    unsigned int isResuming:1;
    unsigned int isSuspendedUnderLock:1;
    unsigned int isRunningInTaskSwitcher:1;
    unsigned int shouldExitAfterSendSuspend:1;
    unsigned int shouldExitAfterTaskCompletion:1;
    unsigned int terminating:1;
    unsigned int isHandlingShortCutURL:1;
    unsigned int idleTimerDisabled:1;
    unsigned int deviceOrientation:3;
    unsigned int delegateShouldBeReleasedUponSet:1;
    unsigned int delegateHandleOpenURL:1;
    unsigned int delegateOpenURL:1;
    unsigned int delegateDidReceiveMemoryWarning:1;
    unsigned int delegateWillTerminate:1;
    unsigned int delegateSignificantTimeChange:1;
    unsigned int delegateWillChangeInterfaceOrientation:1;
    unsigned int delegateDidChangeInterfaceOrientation:1;
    unsigned int delegateWillChangeStatusBarFrame:1;
    unsigned int delegateDidChangeStatusBarFrame:1;
    unsigned int delegateDeviceAccelerated:1;
    unsigned int delegateDeviceChangedOrientation:1;
    unsigned int delegateDidBecomeActive:1;
    unsigned int delegateWillResignActive:1;
    unsigned int delegateDidEnterBackground:1;
    unsigned int delegateWillEnterForeground:1;
    unsigned int delegateWillSuspend:1;
    unsigned int delegateDidResume:1;
    unsigned int userDefaultsSyncDisabled:1;
    unsigned int headsetButtonClickCount:4;
    unsigned int isHeadsetButtonDown:1;
    unsigned int isFastForwardActive:1;
    unsigned int isRewindActive:1;
    unsigned int disableViewGroupOpacity:1;
    unsigned int disableViewEdgeAntialiasing:1;
    unsigned int shakeToEdit:1;
    unsigned int isClassic:1;
    unsigned int zoomInClassicMode:1;
    unsigned int ignoreHeadsetClicks:1;
    unsigned int touchRotationDisabled:1;
    unsigned int taskSuspendingUnsupported:1;
    unsigned int isUnitTests:1;
    unsigned int requiresHighResolution:1;
    unsigned int disableViewContentScaling:1;
    unsigned int singleUseLaunchOrientation:3;
    unsigned int defaultInterfaceOrientation:3;
} _applicationFlags;

This contains possibly all the information a programmer wish they have access to when the application returns to the foreground, to be in particular, I would like to access the flag "isHandlingURL" which says 1 if the app is put into foreground by a sys-call, 0 if the app is put into foreground by the user.

Next, I looked at the address of "application" and "_applicationFlags", noticed that they are offset by 0x3C, which is 60, so I decided to use address operations to get my needed bit:

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    /*
     Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
     */
    id* app = [UIApplication sharedApplication];
    app = app+15; //address increments by long words, don't know if it will be the same on device
    NSLog(@"Test:%x",*app);
}

which prints out test:4a40012, or 0x04a40012 if I write in a complete long word format. This gives me in binary 0000 0100 1010 0100 0000 0000 0001 0010. Looking back into _applicationFlags, this will give us "isHandlingURL" on 6th bit from LSB, which is 0. Now if I try to put the app into background and bring it back with a URL sys call, I get a print out of 4a40032 which in binary is 0000 0100 1010 0100 0000 0000 0011 0010 and I have my isHandlingURL bit turned on! So all that there is left to do is to complete the statement by bit-shift operations, and the final code will look like:

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    /*
     Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
     */
    id* app = (id*)[UIApplication sharedApplication]+15;
    BOOL isHandlingURL = ((Byte)*app>>5&0x1);
    if (isHandlingURL) {
        //do whatever I wanna do here
    }
}

I can go on and write a complete function to parse out all the _applicationFlag, but then it is at this point uncertain if the address increment is fixed to be 15 on both the simulator and the target, my next goal will be to replace with magic number '15' by some macro defines or values from the system so I can be sure that it will always shift 0x3C as required, and I need to look into UIApplication header to make sure that the _applicationFlag will always shift by 0x3C.

That's all for now!

Narrative answered 17/5, 2011 at 21:45 Comment(1)
This is the kind of C knowledge you want to save for debugging and avoid when writing code. Please, please, please don't code against exact memory offsets in a library you don't control (UIKit) that doesn't guarantee ABI stability for these private members.Allergist
P
4

In your AppDelegate:

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

    // Override point for customization after application launch.

    UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];

    if (localNotif) {
        NSLog(@"Recieved Notification %@",localNotif);
    //Do Something
    } else {
    //Do Something else if I didn't recieve any notification, i.e. The app has become active
    }

    return YES;
}

Or, if you want to know when the application is in foreground or in background you can use this method:

- (void)applicationWillResignActive:(UIApplication *)application {
    /*
     Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
     Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
     */
} 

- (void)applicationDidEnterBackground:(UIApplication *)application {
    /*
     Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
     If your application supports background execution, called instead of applicationWillTerminate: when the user quits.
     */
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
    /*
     Called as part of  transition from the background to the active state: here you can undo many of the changes made on entering the background.
     */
}


- (void)applicationDidBecomeActive:(UIApplication *)application {
    /*
     Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
     */
}


- (void)applicationWillTerminate:(UIApplication *)application {
    /*
     Called when the application is about to terminate.
     See also applicationDidEnterBackground:.
     */
}
Pedicel answered 9/11, 2010 at 17:42 Comment(6)
Is this method (applicationDidFinishLaunchingWithOptions) called when the app restores from the background in the iPhone 4?Removable
No, that would be applicationWillEnterForeground, followed by applicationDidBecomeActive.Flagellate
Right! You have to use applicationWillEnterForeground and applicationDidBecomeActive: in these methods you can undo many of the changes made on entering the background or you can refresh the UIPedicel
Yes, but how do I know if the app has entered foreground from a local notification or not?Removable
If the app has entered foreground from a local notification, it calls - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method, so with the code that I've posted you can know everything: - applicationDidEnterBackground: app entered in background - applicationWillEnterForeground/applicationDidBecomeActive: app become active, so users can use it. - - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions: when the app is launched, so even if it was launched by a Local Notification.Pedicel
UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey]; if (localNotif) { NSLog(@"local notification detected!"); [self application:application didReceiveLocalNotification:localNotif]; }Lillielilliputian
N
0

Ok here is my final and elegant solution for you to be able to access the _applicationFlags declared as a private struct within UIApplication. First create a header "ApplicationFlag.h":

//
//  ApplicationFlag.h
//  PHPConnectDemo
//
//  Created by Paul on 5/18/11.
//  Copyright 2011 [email protected]. All rights reserved.
//

#import <Foundation/Foundation.h>
#ifndef APP_FLAG
#define APP_FLAG
#define APP_FLAG_OFFSET 15
#endif

struct appFlag {
    unsigned int isActive:1;
    unsigned int isSuspended:1;
    unsigned int isSuspendedEventsOnly:1;
    unsigned int isLaunchedSuspended:1;
    unsigned int calledNonSuspendedLaunchDelegate:1;
    unsigned int isHandlingURL:1;
    unsigned int isHandlingRemoteNotification:1;
    unsigned int isHandlingLocalNotification:1;
    unsigned int statusBarShowsProgress:1;
    unsigned int statusBarRequestedStyle:4;
    unsigned int statusBarHidden:1;
    unsigned int blockInteractionEvents:4;
    unsigned int receivesMemoryWarnings:1;
    unsigned int showingProgress:1;
    unsigned int receivesPowerMessages:1;
    unsigned int launchEventReceived:1;
    unsigned int isAnimatingSuspensionOrResumption:1;
    unsigned int isResuming:1;
    unsigned int isSuspendedUnderLock:1;
    unsigned int isRunningInTaskSwitcher:1;
    unsigned int shouldExitAfterSendSuspend:1;
    unsigned int shouldExitAfterTaskCompletion:1;
    unsigned int terminating:1;
    unsigned int isHandlingShortCutURL:1;
    unsigned int idleTimerDisabled:1;
    unsigned int deviceOrientation:3;
    unsigned int delegateShouldBeReleasedUponSet:1;
    unsigned int delegateHandleOpenURL:1;
    unsigned int delegateOpenURL:1;
    unsigned int delegateDidReceiveMemoryWarning:1;
    unsigned int delegateWillTerminate:1;
    unsigned int delegateSignificantTimeChange:1;
    unsigned int delegateWillChangeInterfaceOrientation:1;
    unsigned int delegateDidChangeInterfaceOrientation:1;
    unsigned int delegateWillChangeStatusBarFrame:1;
    unsigned int delegateDidChangeStatusBarFrame:1;
    unsigned int delegateDeviceAccelerated:1;
    unsigned int delegateDeviceChangedOrientation:1;
    unsigned int delegateDidBecomeActive:1;
    unsigned int delegateWillResignActive:1;
    unsigned int delegateDidEnterBackground:1;
    unsigned int delegateWillEnterForeground:1;
    unsigned int delegateWillSuspend:1;
    unsigned int delegateDidResume:1;
    unsigned int userDefaultsSyncDisabled:1;
    unsigned int headsetButtonClickCount:4;
    unsigned int isHeadsetButtonDown:1;
    unsigned int isFastForwardActive:1;
    unsigned int isRewindActive:1;
    unsigned int disableViewGroupOpacity:1;
    unsigned int disableViewEdgeAntialiasing:1;
    unsigned int shakeToEdit:1;
    unsigned int isClassic:1;
    unsigned int zoomInClassicMode:1;
    unsigned int ignoreHeadsetClicks:1;
    unsigned int touchRotationDisabled:1;
    unsigned int taskSuspendingUnsupported:1;
    unsigned int isUnitTests:1;
    unsigned int requiresHighResolution:1;
    unsigned int disableViewContentScaling:1;
    unsigned int singleUseLaunchOrientation:3;
    unsigned int defaultInterfaceOrientation:3;
};

@interface ApplicationFlag : NSObject {
    struct appFlag* _flags;
}

@property (nonatomic,assign) struct appFlag* _flags;

@end

Then create an implimentation "ApplicationFlag.m":

//
//  ApplicationFlag.m
//  PHPConnectDemo
//
//  Created by Paul on 5/18/11.
//  Copyright 2011 [email protected]. All rights reserved.
//

#import "ApplicationFlag.h"

@implementation ApplicationFlag

@synthesize _flags;

- (id)init
{
    self = [super init];
    if (self) {
        // Custom initialization
        _flags = (id*)[UIApplication sharedApplication]+APP_FLAG_OFFSET;
    }
    return self;
}

@end

Then do the usual initialization in your Application delegate along with the property, synthesize, includes... whatever:

applicationFlags = [[ApplicationFlag alloc] init];

Then you can start referring to the flags:

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    /*
     Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
     */
    if (!applicationFlags._flags->isHandlingURL) {
        //Do whatever you want here
    }
}
Narrative answered 18/5, 2011 at 5:48 Comment(2)
i thing apple dont allow you to access _applicationFlags, and no metter if you want access it by its pointer.Devy
Did you submit this to the app store? Was it approved?Backfield
S
0

To shed more light into the problem, I just tested launching my app from a local notification and monitored the order at which app delegate methods were called. My test devices were an iPod Touch 5th generation running iOS 7.1.1, and an iPhone 4S running iOS 7.1.1. The order of method calls were the same for both devices.

If the app has merely gone to the background, tapping on a UILocalNotification to launch the app calls applicationWillEnterForeground:, then application:didReceiveLocalNotification:, and finally, applicationDidBecomeActive:. Note that the sequence of method calls is different from @jaredsinclair's answer, which was written a few years ago and was probably tested on a different version of iOS.

If the app, however, is terminated (by iOS or by the user swiping out the app from the multitasking side-scroller), tapping on a UILocalNotification to launch the app again only calls applicationDidBecomeActive:. The method application:didReceiveLocalNotification: IS NOT CALLED.

How I tested the app delegate method callback sequence: In the app delegate, I created an NSMutableArray and populated it with a string whenever applicationWillEnterForeground:, application:didReceiveLocalNotification:, and applicationDidBecomeActive: were called. Then, I displayed the contents of the array from the last two methods since I wasn't sure in what order they would be called. When the app comes from the background, that's only when I get two UIAlertViews, but only because the two said methods are called one after the other.

In any case, I'd also like to push forward the conclusion that there is no way to track whether the app was launched from a UILocalNotification if the app is coming from a terminated state. Someone wants to help confirm by reproducing the test?

Shericesheridan answered 1/7, 2014 at 9:35 Comment(1)
I tested it on ios8 and by using UIMutableUserNotificationActiion. If I clicked on notification text instead of action button in local notification, In didFinishLaunchingWithOptions method, I was able to extract notification info UILocalNotification *localNotification = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey]; which means I could tell that it was launched with local notificaiton. But if I clicked on action button then it was not possible to tell. P.S UIMutableUserNotificationAction will not work in iOS 10.Leges
S
0
- (void)application:(UIApplication *)application didReceiveLocalNotification:(NSDictionary *)userInfo
{
    if ( application.applicationState == UIApplicationStateActive )
        // app was already in the foreground
    else
        // app was just brought from background to foreground

}
Suave answered 11/9, 2015 at 7:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.