application:didFinishLaunchingWithOptions: firing notification before destination controller is created - ios

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!

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.

Related

[iOS]: detect when view controller appears after back from another external app

This is my escenario:
I have a view controller where the user can go to another application (Settings) when push a button in this way:
-(void) goToSettings{
[[UIApplication sharedApplication] openURL:[NSURL URLWithString:UIApplicationOpenSettingsURLString]];
}
So, this code open the app's screen settings and it shows in the upper left corner a legend like this:
Back to myApplication
I wish to detect when the view controller where user push the button is active again. I know you can detect when app is active again with this method in the delegate file
- (void)applicationWillEnterForeground:(UIApplication *)application
But I need detect in specific the view controller. I have tried with -(void)viewWillAppear:(BOOL)animated but It not works. Anyone have any idea about this?
Setup your view controller to listen for the UIApplicationDidBecomeActiveNotification notification.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(becomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
Then add the becomeActive: method:
- (void)becomeActive:(NSNotification *)notification {
// App is active again - do something useful
}
And be sure to remove the observer at the appropriate point.
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
Of course your app may become active again for lots of reasons, not just returning from the Settings app.

Dealing with the same NSNotification in multiple view controllers

I'm using the below notifications to reload ViewControllerA when my app comes back from background mode. It works correctly, but the applicationEnteredForeground: method gets called every time when I open the app again. For example if I close the app when ViewControllerB or ViewControllerC is on the screen and open it again the method will be called despite the viewDidLoad of ViewControllerB doesn't contain applicationEnteredForeground: method. I would like to know that how could solve this issue? My goal is to use applicationEnteredForeground: only when ViewControllerA was on the screen before I closed the app.
As a possible solution I would just remove the NSNotificationCenter in the viewDidDisappear, but since the observer is in the viewDidLoad it won't work when the user navigates back, because viewDidLoad won't be called again. Is there any fix for this?
- (void)viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationEnteredForeground:)
name:UIApplicationWillEnterForegroundNotification
object:nil];
}
- (void)applicationEnteredForeground:(NSNotification *)notification {
// do stuff...
}
You should remove ViewController A's event listener on viewWillDisappear and add it in viewWillAppear. That way, VC A will only be listening when it is the visible view controller.
You can check if a view controller is on screen by checking the window property of it's view. It will work in most standard cases.
- (void)applicationEnteredForeground:(NSNotification *)notification
{
if (self.view.window == nil) {
// Not on screen
return;
}
// do stuff...
}

How to navigate to another view controller in push notifications

I am doing one application.In that i have some view controllers as like A->B,A->C,A->D and B->E,C->F,D->G and E->H,F->I and G->J.So i am in E view controller,whenever notification comes,i have to move to G view controller.And if i am in any view controller except G,i need to move to G view controller.So please tell me how to do it.
You need to first set some code in your AppDelegate.m to respond to pushes when the app is open:
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
//If opening app from notification
if ( application.applicationState == UIApplicationStateInactive || application.applicationState == UIApplicationStateBackground )
{
//restore push
[[NSNotificationCenter defaultCenter] postNotificationName:#"appRestorePush" object:nil];
}
//If app is already open
else {
[[NSNotificationCenter defaultCenter] postNotificationName:#"appOpenPush" object:nil];
}
}
Here we fire a NSNotification if the app is open from a push (i.e. you slide on it from the lock screen) and there is also a different notification for if the app is already open. You don't have to use two different types of NSNotification but it might be useful to you. I'm not really sure what your view controller setup is but assuming you are using a UINavigation controller for everything in the root controller you just set it up to listen in the ViewDidLoad i.e:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appOpenPush) name:#"appOpenPush" object:nil];
and in the method you call something like this:
-(void)appOpenPush {
NSLog(#"got a push while app was open");
//Get the view controller
UIViewController *lastViewController = [[self.navigationController viewControllers] lastObject];
if([lastViewController isKindOfClass:[MyViewController class]]) {
//do your segue here
}
else if//////do this for all your cases...
}
Here we are checking what type of class the view controller is and then choosing the appropriate segue.
Hope you have done a little work at least and understand what I wrote..

NSNotification is being called multiple times from UITabBarController

I have a UITabBarController, which has 4 tabs. Each one of those tabs is a separate UIViewController. I have objects on each one of those 4 VC's that use NSNotification's to perform actions upon the press of a certain object. The 4 VC's all respond to the notification in the same way because it is a similar object on each page. When this object is pressed it presents a view onto the current view controller. The problem is that if I move to any of the other 3 tabs now that view is also on their VC. That is because the notification is being responded to on all 4 tabs when it is pressed on any of the VC's. I am needing it to only respond to the VC that the user is currently on and not any of the others that are in the tab bar.
Is there a way to get this to work properly? Maybe a threshold where you can set how many times the notification can perform its selector after being called? That way I could set it to 1 and at any given time if that notification is called the selector can only be called 1 time.
The type of object implementation that I'm using requires me to use NSNotification's so there is no way to change how I interact.
edit:
This viewDidLoad method is on the top level VC for the 4 VC's in my tab bar. All 4 of them either use this directly or inherit from it.
- (void) viewDidLoad
{
...
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(didSelectItemFromCollectionView:) name:#"didSelectItemFromCollectionView" object:nil];
}
Action Handler:
- (void) didSelectItemFromCollectionView:(NSNotification *)notification
{
NSDictionary *cellData = [notification object];
if (cellData)
{
NewVC *pushToVC = [self.storyboard instantiateViewControllerWithIdentifier:#"PushToVC"];
[self.navigationController pushViewController:pushToVC animated:YES];
}
}
Each of the 4 VC's is a UITableViewController and have cells with an object that can be pressed. This NSNotificationCenter action is what allows the operation to work.
You must have implemented the NSNotificationCenter's -addObserver:selector:name:object: method in the -viewDidLoad of every viewController
Example:
- (void)viewDidLoad
{
//...
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doSomething:)
name:#"TestNotification"
object:nil];
}
Instead of having this in -viewDidLoad, move it within -viewWillAppear and implement removeObserver:name:object: in -viewWillDisappear.
This way, only the viewController that is currently on will respond to the notification.
Example:
- (void)viewWillAppear:(BOOL)animated
{
//...
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(doSomething:)
name:#"TestNotification"
object:nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
//...
[NSNotificationCenter defaultCenter] removeObserver:self
name:#"TestNotification"
object:nil];
}
- (void)doSomething:(NSNotification *)userInfo
{
//...
//if you push a viewController then the following is all you need
[self.navigationController pushViewController:vcSomething
animated:YES];
//however.... if you're instead presenting a viewController modally then
//you should implement "-removeObserver:name:object: in this method as well
//[NSNotificationCenter defaultCenter] removeObserver:self
// name:#"TestNotification"
// object:nil];
//[self presentViewController:vcSomething
// animated:YES
// completion:nil];
//OR... in the completion parameter as:
//[self presentViewController:vcSomething
// animated:YES
// completion:^{
// [NSNotificationCenter defaultCenter] removeObserver:self
// name:#"TestNotification"
// object:nil];
// }];
}
EDIT:
You (#Jonathan) commented:
I really appreciate your answer and it has helped me out a lot! I
actually ran into one more scenario where this issue occur's and I'm
not sure how to figure it out. Right now I have a VC that presents
another VC modally. Each one has observers for the same
NSNotification. Everything performs perfectly well when I'm in the
modally presented VC, but once I dismiss that VC and return to the
underlying one I have the same issue where the notification is being
called multiple times. Do you have an idea for a solution in this
case?
Now... regarding this...
FIRSTLY... NOTE:
Multiple -addObserver:selector:name:object: will register the specified notification multiple times (means... same notification being registered for N times will call invoke the target selector N times)
Presenting a ViewController (call it Child) from, say, Parent viewController will NOT invoke the -viewWillDisappear: of the Parent
where as...
Dismissing the Child viewController will still invoke -viewWillAppear: of the Parent
This creates an imbalance in the logic and if not handled (as per the commented lines in the code example of the doSomething method above), it results in the Parent registering for the notification multiple times (as it's -viewWillAppear: method is called more often than not -viewWillDisappear:)
also see: similar question
it should be called only once so that it never gets called again
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(methodName:) name:#"name" object:nil];
});

removeObserver with NSNotification... what am I doing wrong?

Basically, I have a view1 which at some point, calls view2 (via presentModalViewController:animated:). When a certain UIButton in view2 is pressed, view2 is calls a notification method in view1 and immediately afterward is dismissed. The notification method pops up an alert.
The notification method works fine and is called appropriately. The problem is, every time view1 is created (only one view1 should exist at a time), I presumably get another NSNotification being created because if I go from view0 (the menu) to view1, then back and forth a few times, I get a series of the same alert message, one after another, from the notification method as many times as I opened a view1.
Here is my code, please tell me what I'm doing wrong:
View1.m
-(void) viewDidLoad {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(showAlert:)
name:#"alert"
object:nil];
}
-(void) showAlert:(NSNotification*)notification {
// (I've also tried to swap the removeObserver method from dealloc
// to here, but it still fails to remove the observer.)
// < UIAlertView code to pop up a message here. >
}
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
View2.m
-(IBAction) buttonWasTapped {
[[NSNotificationCenter defaultCenter] postNotificationName:#"alert"
object:nil];
[self dismissModalViewControllerAnimated:YES];
}
-(void) dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[super dealloc];
}
Calling -dealloc doesn't automatically happen after the view controller is dismissed — there can still be some "life" left in the view controller's lifetime. In that timeframe, that view controller is still subscribed for that notification.
If you remove the observer in -viewWillDisappear: or -viewDidDisappear:, this will have a more immediate effect:
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:#"alert"
object:nil];
}
If you implement the removal of Observer in the viewWillDisappear: or viewDidDisappear: then you should not leave the addition of the observer in the viewDidLoad.
Instead put the addition of the observer in the viewWillAppear:. The problem you are having is because when any view is shown onto of the UIViewController view the removal of your observer will occur and since you added observer in viewDidLoad which will happen only once, it will be lost.
Keep in mind that this approach works well for objects you do not wish to observer while your main view is not in the fore front.
Also Keep in mind that viewDidUnload has been depreciated too.
There is nothing wrong putting removeObserver: in dealloc. Just the fact it's not called means view1 not properly releases after dismissing. Looks like something holds pointer to your view1, check for retain cycles.
Also, you shouldn't call dealloc on super.

Resources