Add a Child View Controller's View to a Subview of the Parent View Controller

iOS : Why do we need to add child view controller when adding view as subview does the work?

Certainly viewDidLoad was called. That happened instantly as soon as you referred to ViewController2's view in your code.

But let's say your ViewController2 does other things besides load a view. Suppose its view contains a button that is hooked through an action to a function in ViewController2. If you now tap that button nothing happens.

That's because the ViewController2 itself is dead: it has vanished in a puff of smoke.

You can see that by implementing deinit in ViewController2. You will see that, just as viewDidLoad is called, so is deinit. You are left with a view controller's view that has no view controller. That is bad.

There is a view controller hierarchy that is responsible for maintaining relations between view controllers. When you add ViewController2 as a child view controller of ViewController1, you maintain that hierarchy, and you maintain it correctly according to the rules, which say:

If VC2's view is somewhere inside VC1's view, then VC2 needs to be a child (at some depth) of VC1.

In other words, the view hierarchy and the view controller hierarchy must run together. Otherwise, the responder chain will break and life will become chaos.


(There are other requirements when you make one view controller the child of another, like sending didMoveToParent to the child as part of the opening dance, along with other message forwarding responsibilities later, so as to ensure that the child view controller gets other messages like viewDidAppear at the right time. It's a complex business. However, I've focussed my answer on the very basic part of what you asked.)


I should add: if your goal was merely to fetch a view out of a nib and stuff it into your own view, you can certainly do that, no problem. What you must not do is use a view controller as a kind of magnet or vacuum cleaner to fetch a view for you if your intention is then to let go of the view controller itself.

How to add child view as a subview of a view controllers view?

For my situation I had avoided having multiple views as part of the same view controller for UI design in IB however I reverted to this method after fixing my design issues. This eliminates all of the code needed for using child view controllers and renders my need for a solution to this problem void. I did this shortly before staticVoidMan posted his possible solution and I have no intention of going through the trouble of seeing if his solution works.

despite my decision to use other methods I greatly appreciate all the help I received on the matter.

Adding a view controller as a subview in another view controller

A couple of observations:

  1. When you instantiate the second view controller, you are calling ViewControllerB(). If that view controller programmatically creates its view (which is unusual) that would be fine. But the presence of the IBOutlet suggests that this second view controller's scene was defined in Interface Builder, but by calling ViewControllerB(), you are not giving the storyboard a chance to instantiate that scene and hook up all the outlets. Thus the implicitly unwrapped UILabel is nil, resulting in your error message.

    Instead, you want to give your destination view controller a "storyboard id" in Interface Builder and then you can use instantiateViewController(withIdentifier:) to instantiate it (and hook up all of the IB outlets). In Swift 3:

    let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")

    You can now access this controller's view.

  2. But if you really want to do addSubview (i.e. you're not transitioning to the next scene), then you are engaging in a practice called "view controller containment". You do not just want to simply addSubview. You want to do some additional container view controller calls, e.g.:

    let controller = storyboard!.instantiateViewController(withIdentifier: "scene storyboard id")
    addChild(controller)
    controller.view.frame = ... // or, better, turn off `translatesAutoresizingMaskIntoConstraints` and then define constraints for this subview
    view.addSubview(controller.view)
    controller.didMove(toParent: self)

    For more information about why this addChild (previously called addChildViewController) and didMove(toParent:) (previously called didMove(toParentViewController:)) are necessary, see WWDC 2011 video #102 - Implementing UIViewController Containment. In short, you need to ensure that your view controller hierarchy stays in sync with your view hierarchy, and these calls to addChild and didMove(toParent:) ensure this is the case.

    Also see Creating Custom Container View Controllers in the View Controller Programming Guide.


By the way, the above illustrates how to do this programmatically. It is actually much easier if you use the "container view" in Interface Builder.

enter image description here

Then you don't have to worry about any of these containment-related calls, and Interface Builder will take care of it for you.

For Swift 2 implementation, see previous revision of this answer.

How to set a view over a child view controller's view?

Yes, you can, you just need to add the button to the view hierarchy or move the button up in the hierarchy after you add the child view controller.

Child view controller's viewDidLoad is being called before parent view controller's?

What you are forgetting is loadView(), the lifecycle method that comes before viewDidLoad(). loadView() is responsible for view and subview construction and if you print in this method you'll find that the parent's loadView() is called before the child's, and that is because the parent's view must be constructed before it can add subviews, like a child's view (you're correct). loadView() is also where the parent-child relationship is established (in the parent's loadView()) and the parent must get its children to viewDidLoad() before getting itself to viewDidLoad() because the child's view is (in effect) the parent's view. The parent has no view, in some sense, it only displays its children's views. Therefore, the parent must get its children to viewDidLoad() before it can claim its own view did load.

Child view controllers view doesn't fit parent's view on iPhone X

Why don't you try adding explicit frame to subview, i.e.

func add(_ child: UIViewController) {
addChild(child)
child.view.frame = (your expected frame)
view.addSubview(child.view)
child.didMove(toParent: self)
}


Related Topics



Leave a reply



Submit