Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When using a UINavigationController the viewWillAppear or viewDidAppear methods of my controller are not called

Here is the pitch.

  • I have a UIViewController subclass which does something in its viewWillAppear and viewDidAppear methods.
  • I want to nest this view controller in a UINavigationViewController.
  • Depending on the view hierarchy complexity the two methods viewWillAppear and viewDidAppear of my controller may not be called.

What should I do then to make sure these two methods are always called regardless of my view hierarchy?

Example of a "complex" view hierarchy:

UIViewController subclass containing a UITabBarController
     |_ Each tab containing a UINavigationViewController
         |_ Each UINavigationController controller containing a custom UIViewController

When you present the TabBarController as a modal view the viewWillAppear and viewDidAppear methods of the TabBarController are called but not those of the custom UIViewControllers nested under the UINavigationViewControllers.

like image 338
MiKL Avatar asked Oct 06 '11 13:10

MiKL


1 Answers

NOTE: this was written in 2013. Changes to the way iOS handles view hierarchies nowadays may render this solution useless and/or dangerous. So use at your own risk.

Original Answer When nesting a custom UIViewController under a UINavigationController the methods viewWillAppear and viewDidAppear of the custom viewController may not be called depending on the complexity of your view controller hierarchy (think modal views, navigation controller inside tab view controller...). So if you find yourself in this situation what can you do to ensure these two methods are called?

The answer...

Use the UINavigationControllerDelegate methods

This is a very elegant method to implement for it does not rely on any assumptions regarding when the controller will be loaded by the navigation controller.

There are two methods available:

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated

Here is how the code would change.

You need to declare that your CustomViewController implements the UINavigationControllerDelegate protocol:

@interface CustomViewController : UIViewController <UINavigationControllerDelegate>

You need to set your CustomViewController as the delegate of the UINavigationController where you initialize it.

Last you must also add your custom implementation of the UINavigationControllerDelegate methods to your CustomViewController class implementation. For instance you can implement the navigationController:willShowViewController:animated: method so that:

  • when the UINavigationController is about to show the view controller itself your viewWillAppear method is called
  • when the UINavigationController is about to show another view controller the delegate of the UINavigationController is set to this other view controller, provided that this view controller implements the UINavigationViewControllerDelegate method.

List item

- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if ([viewController isEqual:self]) {
            [viewController viewWillAppear:animated];
    } else if ([viewController conformsToProtocol:@protocol(UINavigationControllerDelegate)]){
            // Set the navigation controller delegate to the passed-in view controller and call the UINavigationViewControllerDelegate method on the new delegate.
            [navigationController setDelegate:(id<UINavigationControllerDelegate>)viewController];
            [[navigationController delegate] navigationController:navigationController willShowViewController:viewController animated:YES];
    }
}

And the navigationController:didShowViewController:animated: can be implemented simply as follows:

- (void)navigationController:(UINavigationController *)navigationController 
       didShowViewController:(UIViewController *)viewController 
                    animated:(BOOL)animated
{
    if ([viewController isEqual:self]) {
        [self viewDidAppear:animated];
    }
}

The benefit of this approach is really that you solely rely on the way the UINavigationViewController is supposed to work and you make your calls just at the right time. It also allows you to pass the delegation around as you move up and down the navigation controller hierarchy right before the viewWillAppear method is called.

Again for simple hierarchy this may not be required. But if you ever find yourself in a situation where your viewWillAppear and viewDidAppear methods are not called you now know what to do...

like image 200
MiKL Avatar answered Nov 15 '22 19:11

MiKL