What does addChildViewController
actually do?
It is the first step of view containment, a process by which we keep the view hierarchy in sync with the view controller hierarchy, for those cases where we have a subview that has encapsulated its logic in its own view controller (to simplify the parent view controller, to enable a reusable child view with its own logic, etc.).
So, addChildViewController
adds the child view controller to an array of childViewControllers
, which keeps track of the children, facilitates them getting all the view events, keeps a strong reference to the child for you, etc.
But note, addChildViewController
is only the first step. You also have to call didMoveToParentViewController
, too:
- (void)showViewController:(UIViewController *)newViewController {
UIViewController* oldViewController = [self.childViewControllers objectAtIndex:0];
[oldViewController willMoveToParentViewController:nil]; // tell it you are going to remove the child
[oldViewController.view removeFromSuperview]; // remove view
[oldViewController removeFromParentViewController]; // tell it you have removed child; this calls `didMoveToParentViewController` for you
newViewController.view.frame = self.view.bounds; // use `bounds`, not `frame`
newViewController.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; // be explicit regarding resizing mask if setting `frame`
[self addChildViewController:newViewController]; // tell it that you are going to add a child; this calls `willMoveToParentViewController` for you
[self.view addSubview:newViewController.view]; // add the view
[newViewController didMoveToParentViewController:self]; // tell it that you are done
}
As an aside, please note the sequence of calls, in which the order that you call these is important. When adding, for example, you call addChildViewController
, addSubview
, and didMoveToParentViewController
, in that order. Note, as the documentation for didMoveToParentViewController
says:
If you are implementing your own container view controller, it must call the didMoveToParentViewController:
method of the child view controller after the transition to the new controller is complete or, if there is no transition, immediately after calling the addChildViewController:
method.
And, if you are wondering why we don't call willMoveToParentViewController
in this case, too, it is because, as the documentation says, addChildViewController
does that for you:
When your custom container calls the addChildViewController:
method, it automatically calls the willMoveToParentViewController:
method of the view controller to be added as a child before adding it.
Likewise, when removing, you call willMoveToParentViewController
, removeFromSuperview
, and removeFromParentViewController
. As the documentation for willMoveToParentViewController
says:
If you are implementing your own container view controller, it must call the willMoveToParentViewController:
method of the child view controller before calling the removeFromParentViewController
method, passing in a parent value of nil
.
And, again, if you are wondering why we don't call didMoveToParentViewController
when removing the child, that is because, as the documentation says, removeFromParentViewController
does that for you:
The removeFromParentViewController
method automatically calls the didMoveToParentViewController:
method of the child view controller after it removes the child.
FYI, if animating the removal of the subview, put the call to removeFromParentViewController
in the animation completion handler.
But if you perform the correct sequence of containment calls, outlined above, then the child will receive all of the appropriate view-related events.
For more information (in particular, why these willMoveToParentViewController
and didMoveToParentViewController
calls are so important), see WWDC 2011 video Implementing UIViewController Containment. Also see the Implementing a Container View Controller section of the UIViewController
documentation.
As a minor observation, make sure that when you are adding the child’s view as a subview, reference the bounds
of the parent view controller’s view, not the frame
. The frame
of the parent’s view is in the coordinate system of its superview. The bounds
is in its own coordinate system.
You might not notice the difference when the parent view occupies the full screen, but as soon as you employ this in a scenario where the parent’s view doesn't happen to take up the full screen, you will start to encounter frame misalignment. Always use bounds
when setting up coordinates for children. (Or use constraints, which gets you out of that silliness, altogether.)
Perhaps needless to say, if you want to just add the child when the parent is instantiated, one can do view controller containment entirely in storyboards without any of these add
/remove
and willMove
/didMove
calls at all. Just use “Container View” and pass whatever data is needed by the child during initialization using prepareForSegue
.
For example, if the parent had a property called bar
and you wanted to update a property called baz
in the child:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationViewController isKindOfClass:[ChildViewController class]]) {
ChildViewController *destination = segue.destinationViewController;
destination.baz = self.bar;
}
}
Now, if you want to programmatically add/remove children, then use as outlined above. But storyboard “Container View” can handle all the view containment calls for simple scenarios with very little code.