programming
iOS development
container view
tutorial
Swift

How to add a Container View programmatically

Master System Design with Codemia

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

Introduction

In UIKit, adding a container view programmatically usually means embedding one view controller inside another without using Interface Builder. The important part is not just adding a UIView. Proper containment requires adding the child view controller, attaching its view, setting constraints, and calling the lifecycle methods in the right order.

Container View Versus Child View Controller

A plain UIView can act as the visual container, but the real architectural feature is view-controller containment.

The correct sequence is:

  1. create or obtain the child view controller
  2. call addChild
  3. add the child view into a container UIView
  4. constrain the child view
  5. call didMove(toParent:)

Skipping those steps can lead to broken lifecycle behavior.

A Working Example

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

This gives you a programmatic container view that hosts a full child controller.

Why addChild and didMove Matter

addChild tells UIKit that the parent now owns the child controller. didMove(toParent:) completes the transition.

Without this containment contract, appearance callbacks, trait propagation, and hierarchy behavior can be inconsistent.

That is why simply adding the child view as a subview is not enough when the child really is a controller.

Removing or Replacing a Child

If you later need to swap container content, remove the old child correctly.

swift
1func removeEmbeddedChild(_ child: UIViewController) {
2    child.willMove(toParent: nil)
3    child.view.removeFromSuperview()
4    child.removeFromParent()
5}

This keeps the containment lifecycle balanced.

Storyboard Container View Versus Code

Interface Builder has a "Container View" object, but programmatic containment is more flexible when:

  • the child controller type is chosen dynamically
  • the layout depends on runtime state
  • you want reusable embedding helpers in code

The UIKit rules are the same either way.

Common Layout Issues

Most bugs come from one of these:

  • forgetting translatesAutoresizingMaskIntoConstraints = false
  • constraining the child view incorrectly so it does not fill the container
  • adding the child view before calling addChild
  • forgetting didMove(toParent:)

If the child controller appears visually but behaves strangely, check the containment sequence first.

When a Plain UIView Is Enough

Sometimes you only need a visual placeholder and not a real child controller. In that case, adding a plain UIView is enough and simpler.

swift
1let container = UIView()
2container.translatesAutoresizingMaskIntoConstraints = false
3container.backgroundColor = .tertiarySystemBackground
4view.addSubview(container)

The extra containment steps are required only when the embedded content is managed by another UIViewController. Keeping that distinction clear prevents overengineering small layouts.

Common Pitfalls

A common mistake is thinking a container view is just a decorated UIView. In UIKit architecture, the child controller relationship matters just as much as the view hierarchy.

Another mistake is embedding the child visually without calling the containment lifecycle methods, which can break appearance and parent-child behavior.

Developers also often forget the symmetric removal sequence when replacing the embedded controller.

Summary

  • A programmatic container view is usually a UIView plus proper child-view-controller containment.
  • Use addChild, add the view, apply constraints, then call didMove(toParent:).
  • Make the child view fill the container with Auto Layout.
  • Remove child controllers with willMove, removeFromSuperview, and removeFromParent.
  • The visual container and the controller lifecycle are both required for correct UIKit behavior.

Course illustration
Course illustration

All Rights Reserved.