application:didFinishLaunchingWithOptions: firing notification before destination controller is created
Asked Answered
R

1

11

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!

Rizo answered 10/2, 2013 at 19:48 Comment(0)
U
12

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.

Unbolted answered 10/2, 2013 at 20:3 Comment(3)
Thank you, using initWithcoder: worked like a charm. All these steps in an object's lifecycle can get quite confusing. :)Rizo
Thanks, this works also if you're using push notifications instead of local notifications and no storyboard. Just use the init methods.Claypoole
init(coder:)Unbolted

© 2022 - 2024 — McMap. All rights reserved.