UINavigationController with Push and Pop Blocks

 

jumbo_push_pops_18_pack-large

When creating view controllers programmatically, one of the options is to use a UINavigationController as a container. This allows you to push and pop view controllers on the navigation stack. However, here are some common developer complaints:

  1. You can’t easily capture the ‘pop’ event. That includes ‘back’ button presses. A common solution is to rely on your top view controller’s viewWillDisappear event. However, you still need logic to check whether this event was fired in response to a new controller being pushed instead of it itself being popped.
  2. Showing the navigation bar and toolbar is tricky. This can result in some unexpected animation effects. If setNavigationBarHidden:NO or setToolbarHidden:NO is called too early, the bars will appear before the the controller starts pushing. A solution is to set this in the pushed view controllers’ viewWillAppear event.

The trouble with both these solutions is that they attempt to manipulate UINavigationController from inside a UIViewController. This is a big no-no because it violates the separation of concerns principle. View controllers should not access, set, update, or know anything about their containers! Write that down a hundred times, because it is key to making your iOS code decoupled, modular, and reusable.

The Solution

I created a subclass called MRNavigationController with a pushViewController method that accepts push and pop blocks, as well as navigation bar and toolbar visibility settings, for each pushed controller. Here is a sample:

#import "MRNavigationController.h"

self.vc1 = [[UIViewController alloc] init];
self.vc2 = [[UIViewController alloc] init];
self.nav = [[MRNavigationController alloc] initWithRootViewController:self.vc1 navigationBarHidden:YES toolbarHidden:YES];
[self.nav pushViewController:self.vc2 animated:YES navigationBarHidden:NO toolbarHidden:NO push:^{
	NSLog(@"VC1 pushed VC2");
}
pop:^{
	NSLog(@"VC2 popped");
	self.vc2 = nil;
}];

In this example, the navigation controller will display the vc1 view controller with no navigation bar and no toolbar. The first log statement will then be printed. The vc2 view controller will then be animated onto the screen with a navigation bar and toolbar. When its back button is pressed, the second log statement will be printed, the vc2 view controller will be animated away and cleared from memory, and the vc1 view controller will once again appear with no navigation bar and no toolbar.

The pop block is executed whether the back button is pressed or if the view controller is popped manually via one of the standard pop methods:

  • popViewControllerAnimated:
  • popToViewController:animated:
  • popToRootViewControllerAnimated:

The following superclass methods can still be used, but MRNavigationController will assign a default of navigationBarHidden:NO and toolbarHidden:YES and no push or pop blocks will be executed:

  • initWithRootViewController:
  • pushViewController:animated:

For a live example, download the Xcode project on Github.

How it Works

Internally, MRNavigationController keeps track of all pushed view controllers inside an internal stack, along with the following metadata:

  • What code to execute when the controller is pushed
  • What code to execute when the controller is popped
  • Whether the navigation bar should be visible or not
  • Whether the toolbar should be visible or not

The class sets itself as the UINavigationController superclass’ delegate and listens to the willShowViewController and didShowViewController events. You can still use your own delegate thanks to a trick from a fantastic library called HTDelegateProxy. It then executes the appropriate push and pop blocks for each controller accordingly. It also shows or hides the navigation bar and toolbar for each controller at the correct time, keeping in sync with existing push and pop animations.

Memory Management

MRNavigationController holds weak references to all view controllers. That said, it is still your responsibility to set popped controllers to nil when using any of the pop methods (if that is what you want). This is one of the things that Storyboards do automatically. However, the power that comes with manually managing view controller lifecycles is one of the arguments for not using Storyboards!

Installation

I’ve published this library as a CocoaPod, which is the easiest way to install it. Otherwise, simply manually copy these class files and HTDelegateProxy files into your project.

Enjoy!

About Martin Rybak

I am a New York area software developer and MBA with 10+ years of server-side experience on the Microsoft stack. I've also been a native iOS developer since before the days of ARC. I architect and develop full-stack web applications, iOS apps, database systems, and backend services.

One response to “UINavigationController with Push and Pop Blocks

Leave a comment