iOS
Swift
ContainerViewController
AppDevelopment
UIViewController

Container View Controller Examples

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

A container view controller is a parent controller whose main job is to host and coordinate child controllers. UIKit already uses this pattern in UINavigationController and UITabBarController, but you can build a custom container when your app needs a layout or interaction model that the built-in containers do not provide.

When to Use a Custom Container

Custom containers make sense when one screen is really a composition of multiple independently managed pieces.

Common examples include:

  • a split layout with a menu and detail area
  • a dashboard made of reusable child sections
  • a host controller that swaps different content controllers into one region

If simple push navigation or tabs already solve the problem, stick with the built-in container types. A custom container is worthwhile only when the behavior is genuinely custom.

The Correct Embed Sequence

The most important rule is that you must create a real parent-child relationship, not just add one controller's view inside another.

The correct sequence is:

  1. call addChild
  2. add the child view to the hierarchy
  3. size or constrain the child view
  4. call didMove(toParent:)

Here is a minimal example:

swift
1import UIKit
2
3final class HostViewController: UIViewController {
4    private let containerView = UIView()
5
6    override func viewDidLoad() {
7        super.viewDidLoad()
8
9        view.backgroundColor = .systemBackground
10        containerView.translatesAutoresizingMaskIntoConstraints = false
11        view.addSubview(containerView)
12
13        NSLayoutConstraint.activate([
14            containerView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
15            containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
16            containerView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
17            containerView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
18        ])
19
20        let child = DetailViewController()
21        embed(child)
22    }
23
24    private func embed(_ child: UIViewController) {
25        addChild(child)
26        child.view.translatesAutoresizingMaskIntoConstraints = false
27        containerView.addSubview(child.view)
28
29        NSLayoutConstraint.activate([
30            child.view.topAnchor.constraint(equalTo: containerView.topAnchor),
31            child.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
32            child.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
33            child.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)
34        ])
35
36        child.didMove(toParent: self)
37    }
38}
39
40final class DetailViewController: UIViewController {
41    override func viewDidLoad() {
42        super.viewDidLoad()
43        view.backgroundColor = .systemBlue
44    }
45}

This is the smallest correct version of custom containment in UIKit.

Swapping One Child for Another

Many containers show one child at a time and replace it when state changes. In that case, remove the old child cleanly and finish the new one properly.

swift
1func switch(from old: UIViewController, to new: UIViewController) {
2    old.willMove(toParent: nil)
3    addChild(new)
4
5    transition(
6        from: old,
7        to: new,
8        duration: 0.25,
9        options: [.transitionCrossDissolve, .curveEaseInOut],
10        animations: nil
11    ) { _ in
12        old.removeFromParent()
13        new.didMove(toParent: self)
14    }
15}

Using transition(from:to:duration:options:animations:completion:) keeps the lifecycle flow aligned with the view animation.

Example Designs

A few common custom-container patterns are:

  • side menu host
  • paged content host
  • dashboard shell with permanent child regions

In each case, the parent controller owns layout and routing while each child owns its own screen logic. That separation is the main architectural benefit of containment.

Built-In Containers Still Matter

Do not build a custom container just because you can. UIKit already gives you:

  • 'UINavigationController for drill-down flows'
  • 'UITabBarController for top-level sections'
  • 'UISplitViewController for multi-column layouts'

Use a custom container only when those controllers do not express the interaction cleanly.

Common Pitfalls

  • Adding a child controller's view without calling addChild and didMove(toParent:).
  • Forgetting willMove(toParent: nil) and removeFromParent() when removing a child.
  • Managing containment correctly but forgetting to constrain the child view, which creates layout bugs.
  • Building a custom container when a standard UIKit container already matches the design.
  • Letting the parent controller absorb all child logic instead of keeping responsibilities separated.

Summary

  • A container view controller hosts child controllers, not just child views.
  • Use the full containment API: addChild, add the view, constrain it, then call didMove(toParent:).
  • Remove children cleanly when swapping content.
  • Custom containers are useful for app-specific composition patterns such as side menus or dashboards.
  • Prefer built-in UIKit containers unless your layout or interaction model truly requires a custom parent.

Course illustration
Course illustration

All Rights Reserved.