popToRootViewControllerAnimated not always firing viewWillAppear in rootViewController - ios

My application handles opening email attachments. To do this in my AppDelegate I call my dedicated ViewController which is the RootViewController of my App thanks to popToRootViewControllerAnimated:.
I do some treatments in viewWillAppear and viewDidAppear of RootViewController but they are not called if the view controller that was displayed before switching to the mail application was my RootViewController!
Case 1 : Usual viewWillAppear / viewDidAppear methods aren't called :
(RootViewController -> switch to Mail App -> Open attachement in my app -> in AppDelegate popToRootViewControllerAnimated -> RootViewController)
Case 2 : Usual viewWillAppear / viewDidAppear methods are called :
(OtherViewController -> switch to Mail App -> Open attachement in my app -> in AppDelegate popToRootViewControllerAnimated -> RootViewController)
Found on the internet this trick but it doesn't work in case 1: http://www.idev101.com/code/User_Interface/UINavigationController/viewWillAppear.html
How can I fix this and call usual viewWillAppear / viewDidAppear methods all the time?

Your view controller is already visible so it's normal that viewWillAppear doesn't get called.
If you want to know when the user gets back to your app just add the following code in RootViewController init method :
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(willEnterForeground:)
name: UIApplicationWillEnterForegroundNotification
object:nil];
and implement what you want in
- (void)willEnterForeground:(NSNotification *)notification
Don't forget to stop observing on when RootViewController will be deallocated :
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}

Sounds like it is because in Case 1, within the context of your app your RootViewController is already 'visible' and therefor popToRootViewController doesn't have to do anything.

viewWillAppear: and viewDidAppear: methods are called only when such a thing happens relative to your application. These methods are not called when you switch between applications. You should depend on the applicationWillEnterForeground: and applicationDidBecomeActive: methods in your appDelegate.
One more thing to note is that when you are doing
RootViewController -> popToRootViewControllerAnimated -> RootViewController
Your root view controller's view is already visible and thus it will not fire the viewWillAppear: and viewDidAppear: methods.
I recommend you do something similar to this
RootViewController.m
-(void)viewDidAppear:(BOOL)animated
{
[self doSomething];
}
-(void)doSomething
{
//Your functionality
}
AppDelegate.m
- (void)applicationDidBecomeActive:(UIApplication *)application
{
[self.window.rootViewController doSomething];
}
You can also add your root view controller as a listener to the UIApplicationDidBecomeActiveNotification or UIApplicationWillEnterForegroundNotification

Related

navigationController popToRootViewControllerAnimated:YES does not call viewWillAppear

If I run, within IOS not swift,
[[navigationController popViewControllerAnimated:YES] viewWillAppear:YES]
it calls viewWillAppear on the previous VC.
I need to jump back to the root VC and have that ViewWillAppear called; however,
[[navigationController popToRootViewControllerAnimated:YES] viewWillAppear:YES] gives me a coding error.
Is there a way to do this without applicationWillEnterForeground: and applicationDidBecomeActive: methods in my appDelegate
I have tried within init
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(willEnterForeground:)
name: UIApplicationWillEnterForegroundNotification
object:nil];
And in body
-(void) willEnterForeground:(Notification*)NotificationCenter
{
(self viewWillAppear:YES);
}
This doesn't seem to call viewWillAppear either.
Your code seems quite incorrect...
First, you should never call viewWillAppear - that is the system notifying your controller that the view will appear.
Second, just calling:
[self.navigationController popViewControllerAnimated:YES];
will navigate to the previous view controller in the stack, or:
[self.navigationController popToRootViewControllerAnimated:YES];
will navigate to the Root view controller.
In both cases, viewWillAppear is called by the system when the view, well, will appear.

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...
}

Removing NSNotification in a UIViewController

So I have a VC that i register for a notification in viewDidAppear: Even when the VC is not the main focus, as when I push another VC on the stack, I still want that VC to receive the notification. But after that VC is no longer needed, i.e. it is popped off the stack, I want to remove it as an observer for that notification.
Where do I do that? viewDid/WillUnload: are no more, and i tried dealloc, but it never gets called. So this seems to mean that the NotificaitonCenter will retain the VC, and it will never get released when it is popped off the stack.
In the dealloc method. That is the established convention nowadays.
The NotificationCenter is not retaining the VC. Where did you get that impression?
You should put the code in the dealloc method of the UIViewController
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
If you are using Swift then you can put it in the deinit method of the UIViewController
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self)
}

Xcode 5, How do I call a method when the user leaves the app?

Hello I do not have very much programming experience and I'm trying to call a method when the user leaves the app. I know that I am going to use the app delegate methods applicationDidEnterBackground, but how do I make that call a method that is in my ViewController class?
Thanks so much for any help!
ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <ADBannerViewDelegate>
{
//Images and buttons
}
-(void)stop;
#end
You can use a notification here. Create a listener for this notification in the viewDidLoad of the viewController and assign the function to it.
eg:
in yourView controller add the following in the viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(notificationEnabled) name:UIApplicationDidEnterBackgroundNotification object:nil];
You can either
observe UIApplicationDidEnterBackgroundNotification inside your ViewController, or
invoke your ViewController method inside appDelegates applicationdidEnterBackground: method. App delegate should have a pointer that points to the rootViewController, ie: your ViewController
goodluck!
edit:
...
-(void)applicationDidEnterBackground:(UIApplication *)application {
UIViewController *uvc= [UIApplication sharedApplication].keyWindow.rootViewController;
ViewController *myvc = (ViewController*) uvc;
[myvc stop];
}
.
When you close or leave your App, AppDelegate methods automatically called, you not need to call them in some specific ViewController.
For doing something when your App is in background you can implement your logic in AppDelegate's applicationDidEnterBackground: method.
Or if your App is not running(means closed) AppDelegate Method didFinishLaunchingWithOptions: is called.
I didn't get the purpose of calling the method inside UIViewController. Eventhough you can create an instance of your viewcontroller inside applicationDidEnterBackground and call the corresponding method in viewcontroller using that object
you can do this by creating an instance of that View and then calling specific method of that view
otherwise if your view is opened then you can find it by follwing code & then call specific method by using that obj as follow in your applicationDidEnterBackground
NSArray *AllViewControllers = [self.navigationController viewControllers];
for (UIViewController *aViewController in AllViewControllers) {
//NSLog(#" >> Nav Stack %#", [aViewController class]);
if ([aViewController isKindOfClass:[YourViewController class]]) {
[(YourViewController *)aViewController yourMethodToCall];
break;
}
}

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!
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.

Resources