Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

application:didFinishLaunchingWithOptions: firing notification before destination controller is created

Hello, I am writing an app that should respond with an UI update and an internal status change when a local notifcation is used to open it. I am using storyboards and I have set up my main view controller to observe status changes:

- (void)viewDidLoad
{
    [super viewDidLoad];
    // ...
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resumeByNotification:) name:@"Resume" object:nil];
}

In my app delegate I have this:

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    if (application.applicationState == UIApplicationStateInactive)
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"Resume" object:self userInfo:notification.userInfo];
    }
}

And this works just fine: if the app is running in the background, the view controller will intercept the notification and react accordingly. (If the app is running in the foreground, it's ignored because the UI is being taken care of directly.)

The problem arises when the app has been killed and the notification is received. I have written this in the didFinishLaunchingWithOptions method, making the phone vibrate as a quick debug technique :), and I do get the notification:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UILocalNotification *localNotification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
    if (localNotification)
    {
        [[NSNotificationCenter defaultCenter] postNotificationName:@"Resume" object:self userInfo:localNotification.userInfo];
        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
    }

    return YES;
}

The phone DOES vibrate so the notification IS there, but it doesn't seem to trigger the observer. I suppose that this is because the view controller's didViewLoad method hasn't been called yet. I am not sure how to work this around. I suppose I could use UIStoryboard's instantiateViewControllerWithIdentifier: method to make sure the view controller is actually there, but wouldn't I be getting an "extra" instance of it, in addition to the one that will eventually be instantiated by the storyboard's own life cycle? Judging from what the on the class reference documentation says, it's not exactly meant to do this kind of thing.

Am I missing something very obvious here? In fact, is my approach the correct one for this kind of situation?

Thanks!

like image 737
Jollino Avatar asked Feb 10 '13 19:02

Jollino


People also ask

What is the use of application willFinishLaunchingWithOptions and application didFinishLaunchingWithOptions?

application:didFinishLaunchingWithOptions:Tells the delegate that the launch process is almost done and the app is almost ready to run. So after having the event application:willFinishLaunchingWithOptions, the next event application:didFinishLaunchingWithOptions will be fired.

What if didFinishLaunchingWithOptions return false?

Return Value false if the app cannot handle the URL resource or continue a user activity, otherwise return true . The return value is ignored if the app is launched as a result of a remote notification.


1 Answers

The view controller doesn't load its view until something asks it for its view. At launch time, that normally happens after application:didFinishLaunchingWithOptions: returns.

You might wonder why. The answer is that you might instantiate several view controllers at launch time, some of which are hidden. For example, if your window's root view controller is a UINavigationController, you might load the navigation controller with a stack of view controllers (the stack that the user had pushed the last time the app ran). Only the top view controller of this stack is visible, so there's no need to load the views of the other view controllers. The system waits until application:didFinishLaunchingWithOptions: returns before loading any views so that only the necessary views will be loaded.

So one way to work around your problem would simply be to ask the view controller for its view, thus forcing it to load. If your view controller is the window's root view controller, you can do it this way:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UILocalNotification *localNotification = launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];
    if (localNotification) {
        [[self.window rootViewController] view];
        [[NSNotificationCenter defaultCenter] postNotificationName:@"Resume" object:self userInfo:localNotification.userInfo];
        AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
    }

    return YES;
}

A different workaround would be to start observing the notification in your view controller's initWithCoder: method:

- (id)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resumeByNotification:) name:@"Resume" object:nil];
    }
    return self;
}

This is called when the view controller is instantiated from the MainStoryboard, which happens before the application:didFinishLaunchingWithOptions: message.

like image 180
rob mayoff Avatar answered Nov 10 '22 10:11

rob mayoff