Browse Questions
  • SOLID is a set of five design guidelines for maintainable OOP code: | Principle | Idea | Swift application | |--|--|--| | Single Responsibility | A type should have one reason to change | Split a…
  • SOLID is five principles for writing maintainable, extensible code.
  • View controller containment is the API for embedding one view controller inside another so the child keeps its own lifecycle.
  • Delegation is a pattern where one object hands off responsibility to another through a protocol, letting a child communicate back to its owner without knowing its concrete type.

Answer: View controller containment is the API for embedding one view controller inside another so the child keeps its own lifecycle. It requires a specific three-step sequence — addChild, add the view, then didMove(toParent:) — with a mirrored sequence for removal. It's how UINavigationController and UITabBarController are built, and how you break a large screen into independently owned pieces instead of a 1,000-line view controller.

Code Example:

extension UIViewController {

    func add(_ child: UIViewController, to container: UIView) {
        addChild(child)                       // 1. establish the parent-child relationship
        container.addSubview(child.view)      // 2. add the view and lay it out
        child.view.frame = container.bounds
        child.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        child.didMove(toParent: self)         // 3. tell the child the move finished
    }

    func remove() {
        guard parent != nil else { return }
        willMove(toParent: nil)               // 1. tell the child it's about to leave
        view.removeFromSuperview()            // 2. remove the view
        removeFromParent()                    // 3. break the relationship
    }
}

// Swapping content — e.g. loading → loaded → error states
func showState(_ state: State) {
    children.forEach { $0.remove() }
    switch state {
    case .loading: add(LoadingViewController(), to: containerView)
    case .loaded(let items): add(ListViewController(items: items), to: containerView)
    case .error(let e): add(ErrorViewController(error: e), to: containerView)
    }
}

// Hosting SwiftUI is containment too
let host = UIHostingController(rootView: ProfileView())
add(host, to: containerView)

Key Points:

  • Skipping addChild/didMove breaks viewWillAppear, trait collection changes, rotation, and preferredStatusBarStyle forwarding to the child
  • The asymmetry trips people up: adding calls didMove(toParent:) at the end; removing calls willMove(toParent: nil) at the start (addChild and removeFromParent call the other half for you)
  • Use it for reusable sub-screens, state swapping, and hosting SwiftUI via UIHostingController — not for anything that's just a view with no lifecycle needs
  • The parent strongly retains children in its children array, so always remove() when swapping