View Controller containment is a set of UIViewController APIs that were introduced with iOS 5. I didn’t know much about it, or even really what the purpose of such a thing was. I’ve written a few container view controllers, mostly custom tab bar controllers, by using what I would call the “old and busted” way:

[selectedViewController viewWillDisappear:NO];  
selectedViewController.view.hidden = YES;  
[selectedViewController viewDidDisappear:NO];
	
vc = viewControllers[index];  
selectedViewController = vc;
	
vc.view.frame = self.view.bounds;  
[self.view addSubview:vc.view];
	
[vc viewWillAppear:NO];  
vc.view.hidden = NO;  
[vc viewDidAppear:NO];

This works, but it’s unreliable. I’m manually calling the appearance methods, but will rotation methods be called on every sub-viewcontroller? Memory warnings? What is the value of parentViewController ?

What do we get?

Forwarded methods

It turns out, using container view controllers cleans all that up for you, at a small upfront cost. The main purpose of it is to forward methods:

  • Appearance methods: -viewWillAppear and friends
  • Rotation methods: -willRotateToInterfaceOrientation, etc
  • The memory warning method: -didReceiveMemoryWarning

These methods are forwarded by default, and can be modified in iOS 5 and above with:

- (BOOL) automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers  
and iOS 6 and above with:
- (BOOL) shouldAutomaticallyForwardRotationMethods  
- (BOOL) shouldAutomaticallyForwardAppearanceMethods

Properties

View controller containment also helps set properties like navigationController, parentViewController, and interfaceOrientation correctly.

New stuff

We also get a few things we couldn’t really get before, also for free:

– isMovingFromParentViewController;  
– isMovingToParentViewController;  
- isBeingPresented;  
– isBeingDismissed;  

These faux-properties can be called during transitions, and give your app a better sense of its state.

So what is view controller containment?

View controller containment is simply a way of telling your parent and child view controllers which is which. They give us some methods, which are unfortunately very unclear and don’t tell us which are required and which are not.

- addChildViewController:  
- removeFromParentViewController  
- willMoveToParentViewController:  
- didMoveToParentViewController:  
-transitionFromViewController:toViewController:duration:options:animations:completion:  

So, to explore containment, I wrote two small replacements for the UIKit view controller containers, SKTabBarController, and SKNavigationController.

Both examples are in ARC.

SKTabBarController

SKTabBarController can be found at github.com/khanlou/SKTabBarController.

The tab bar controller is very simple.

  • It has a tab bar, and it registers as the delegate of that tab bar.
  • The viewControllers property can be set, and that adds all of the view controllers as child view controllers.
  • When a tab bar button is tapped, the delegate method is called, and
    • If the already-selected button is being tapped, it pops to the root of that navigation controller.
    • If a different button is being pressed, it removes the old view, sets the frame, and adds the new view.

Child View Controllers

The addChildViewController: and removeFromParentViewController can be thought of as view controller counterparts to addSubview: and removeFromSuperview. They’re asymmetric, meaning that one is called on the parent (“add”) and one is called on the child (“remove”). The view controller methods add view controllers to the childViewControllers property, in the same way that the view methods add to the subviews property.

The view controller methods MUST be called before the view methods.

Once a child is a member of parent view controller, simply calling addSubview: or removeFromSuperview will automatically call the appropriate appearance methods.

Child Callbacks

When calling addChildViewController: and removeFromParentViewController, you must let the view controller know that you are about to move it, and that’s where these methods come in:

  • willMoveToParentViewController:
  • didMoveToParentViewController:
    When adding a child view controller, willMoveToParentViewController: is automatically called, but didMoveToParentViewController: is not, and you must manually call it.

The reverse is the case for removing a child view controller. willMoveToParentViewController: should be passed nil, but didMoveToParentViewController: will be called for you.

Proper Use Of childViewControllers

Originally, I had this container class adding children only when the button was tapped and removing them when a different button was tapped, but that feels wrong. A child is still a child, even if it’s not visible. You can browse the incorrect code at commit 297c2dc7a2. The current code is updated, and adds child view controllers when the viewControllers property is set.

SKNavigationController

SKTabBarController can be found at github.com/khanlou/SKNavigationController.

I didn’t use any animations in SKTabBarController, so things got more interesting when I tried to make a navigation controller.

The navigation controller still pretty straightforward.

  • It has a navigation bar, and it registers as the delegate of that navigation bar.
  • The viewControllers property can be set, and that adds all of the view controllers as child view controllers, and pushes their navigation items onto the navigation bar.
  • The push and pop methods of the navigation controller use -transitionFromViewController:toViewController:duration:options:animations:completion: for animations.

Pushing a New View Controller

When pushing a new view controller, we must do several things.

  • Add it to the parent view controller.
  • Create a navigation item for it, and add that item to the navigation bar.
  • If animated, we need to set up a transition.
  • At the end of the animation, we need to tell the pushed view controller that it has been fully moved to the parent.

Transitions are weird. They’re like traditional UIView animations, but they do more things for you.

[self transitionFromViewController:oldViewController  
	toViewController:newViewController  
	duration:0.35f  
	options:0  
	animations:^{  
		newViewController.view.center = oldViewController.view.center;  
		oldViewController.view.center = CGPointMake(oldViewController.view.center.x - self.view.bounds.size.width, oldViewController.view.center.y);  
	}  
	completion:^(BOOL finished) {  
		[newViewController didMoveToParentViewController:self];  
	}
];  

Let’s go through it step-by-step.

  1. fromViewController and toViewController must exist, and have the same parent.
  2. options can include any UIView animation options, including several of the built in animation transitions.
  3. animations can be used for custom animations.
  4. completion is used to call any didMoveToParentViewController: callback methods, and cleanup for your animation.
Adding and Removing the Subview

transitionFromViewController: automatically calls addSubview: with toViewController.view. Calling it yourself will result in the incredibly frustrating >

Unbalanced calls to begin/end appearance transitions for

It will also automatically call removeFromSuperview on fromViewController.view.

Popping an View Controller off the Stack

Popping is very similar to pushing.

  1. Tell the view controller coming off the stack that it is being moved to a nil parent.
  2. Pop the navigation item off the navigation bar.
  3. Set up the transition.
  4. Upon completion, fully remove the popped navigation controller from the parent.

I won’t include the code for the sake of brevity.

Takeaways

There are three takeaways:

  1. Adding a child view controller must be followed by didMoveToParentViewController:self.
  2. Removing a child view controller must be preceded by willMoveToParentViewController:nil.
  3. Transitions automatically handle adding and removing of the child view controllers’ views.