I'm just dipping my feet for the first time into iOS development, and one of the first things I've had to do is implement a custom container view controller - lets call it SideBarViewController
- that swaps out which of several possible child view controllers it shows, almost exactly like a standard Tab Bar Controller. (It's pretty much a Tab Bar Controller but with a hideable side menu instead of a tab bar.)
As per the instructions in the Apple documentation, I call addChildViewController
whenever I add a child ViewController to my container. My code for swapping out the current child view controller being shown by the SideBarViewController
looks like this:
- (void)showViewController:(UIViewController *)newViewController {
UIViewController* oldViewController = [self.childViewControllers
objectAtIndex:0];
[oldViewController removeFromParentViewController];
[oldViewController.view removeFromSuperview];
newViewController.view.frame = CGRectMake(
0, 0, self.view.frame.size.width, self.view.frame.size.height
);
[self addChildViewController: newViewController];
[self.view addSubview: newViewController.view];
}
Then I started trying to figure out just what addChildViewController
does here, and I realised that I have no idea. Besides sticking the new ViewController
in the .childViewControllers
array, it seems to have no effect on anything. Actions and outlets from the child controller's view to the child controller that I've set on the storyboard still work just fine even if I never call addChildViewController
, and I can't imagine what else it could affect.
Indeed, if I rewrite my code to not call addChildViewController
, and instead look like this...
- (void)showViewController:(UIViewController *)newViewController {
// Get the current child from a member variable of `SideBarViewController`
UIViewController* oldViewController = currentChildViewController;
[oldViewController.view removeFromSuperview];
newViewController.view.frame = CGRectMake(
0, 0, self.view.frame.size.width, self.view.frame.size.height
);
[self.view addSubview: newViewController.view];
currentChildViewController = newViewController;
}
... then my app still works perfectly, so far as I can tell!
The Apple documentation doesn't shed much light on what addChildViewController
does, or why we're supposed to call it. The entire extent of the relevant description of what the method does or why it should be used in its section in the UIViewController
Class Reference is, at present:
Adds the given view controller as a child. ... This method is only intended to be called by an implementation of a custom container view controller. If you override this method, you must call super in your implementation.
There's also this paragraph earlier on the same page:
Your container view controller must associate a child view controller with itself before adding the child’s root view to the view hierarchy. This allows iOS to properly route events to child view controllers and the views those controllers manage. Likewise, after it removes a child’s root view from its view hierarchy, it should disconnect that child view controller from itself. To make or break these associations, your container calls specific methods defined by the base class. These methods are not intended to be called by clients of your container class; they are to be used only by your container’s implementation to provide the expected containment behavior. Here are the essential methods you might need to call: addChildViewController: removeFromParentViewController willMoveToParentViewController: didMoveToParentViewController:
but it doesn't offer any clue as to what the 'events' or 'expected containment behavior' that it's talking about are, or why (or even when) calling these methods is 'essential'.
The examples of custom container view controllers in the "Custom Container View Controllers" section of the Apple documentation all call this method, so I assume that it serves some important purpose beyond just popping the child ViewController onto an array, but I can't figure out what that purpose is. What does this method do, and why should I call it?
I think an example is worth a thousand words.
I was working on a library app and wanted to show a nice notepad view that appears when the user wants to add a note.
https://i.stack.imgur.com/0iAFB.png
After trying some solutions, I ended up inventing my own custom solution to show the notepad. So when I want to show the notepad, I create a new instance of NotepadViewController
and add its root view as a subview to the main view. So far so good.
Then I noticed that the notepad image is partially hidden under the keyboard in landscape mode.
https://i.stack.imgur.com/IN72v.png
So I wanted to change the notepad image and shift it up. And to do so, I wrote the proper code in willAnimateRotationToInterfaceOrientation:duration:
method, but when I ran the app nothing happened! And after debugging I noticed that none of UIViewController
's rotation methods is actually called in NotepadViewController
. Only those methods in the main view controller are being called.
To solve this, I needed to call all the methods from NotepadViewController
manually when they're called in the main view controller. This will soon make things complicated and create an extra dependency between unrelated components in the app.
That was in the past, before the concept of child view controllers is introduced. But now, you only need to addChildViewController
to the main view controller and everything will just work as expected without any more manual work.
Edit: There are two categories of events that are forwarded to child view controllers:
1- Appearance Methods:
- viewWillAppear:
- viewDidAppear:
- viewWillDisappear:
- viewDidDisappear:
2- Rotation Methods:
- willRotateToInterfaceOrientation:duration:
- willAnimateRotationToInterfaceOrientation:duration:
- didRotateFromInterfaceOrientation:
You can also control what event categories you want to be forwarded automatically by overriding shouldAutomaticallyForwardRotationMethods
and shouldAutomaticallyForwardAppearanceMethods
.
I was wondering about this question too. I watched Session 102 of the WWDC 2011 videos and Mr. View Controller, Bruce D. Nilo, said this:
viewWillAppear:, viewDidAppear:, etc have nothing to do with addChildViewController:. All that addChildViewController: does is to say "This view controller is a child of that one" and it has nothing to do with view appearance. When they get called is associated with when views move in and out of the window hierarchy.
So it seems that the call to addChildViewController:
does very little. The side effects of the call are the important part. They come from the parentViewController
and childViewControllers
relationships. Here are some of the side effects that I know:
Forwarding appearance methods to child view controllers
Forwarding rotation methods
(Possibly) forwarding memory warnings
Avoiding inconsistent VC hierarchies, especially in transitionFromViewController:toViewController:… where both VCs need to have the same parent
Allowing custom container view controllers to take part in State Preservation and Restoration
Taking part in the responder chain
Hooking up the navigationController, tabBarController, etc properties
-[UIViewController addChildViewController:]
only adds the passed in view controller in an array of viewControllers that a viewController (the parent) wants to keep reference of. You should actually add those viewController's views on screen yourself by adding them as a subviews of another view (e.g. the parentViewController's view). There's also a convenience object in Interface Builder to use childrenViewControllers in Storyboards.
Previously, to keep reference of other viewControllers of which you used the views of, you had to keep manual reference of them in @properties. Having a build-in property like childViewControllers
and consequently parentViewController
is a convenient way to manage such interactions and build composed viewControllers like the UISplitViewController that you find on iPad apps.
Moreover, childrenViewControllers also automatically receive all the system events that the parent receives: -viewWillAppear, -viewWillDisappear, etc. Previously you should have called this methods manually on your "childrenViewControllers".
That's it.
iOS "system events"
doesn't throw much up; it doesn't seem to be a term that Apple uses?
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
.
https://i.stack.imgur.com/6raG3.gif
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.
Success story sharing
addChildViewController
to the parent controller.