Skip to content

UIKit view-controller lifecycle and containment

A view controller coordinates one screen or contained region. Lifecycle callbacks describe when its view loads, appears, lays out, and leaves. They are not interchangeable hooks.

Match work to callback frequency

Build the hierarchy in viewDidLoad. Refresh appearance-specific state in appearance callbacks only when repetition is intended. Respond to geometry changes in layout callbacks without starting business work there.

Contain complete states

private func show(_ child: UIViewController) {
current?.willMove(toParent: nil)
current?.view.removeFromSuperview()
current?.removeFromParent()
addChild(child)
view.addSubview(child.view)
child.view.frame = view.bounds
child.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
child.didMove(toParent: self)
current = child
}

The parent owns transitions among loading, empty, list, and error controllers. Each child owns one coherent region. The complete add and remove sequence preserves UIKit lifecycle and responder relationships.

Containment is useful when regions have independent behavior or reuse. A controller per label only adds ceremony. A single massive controller that owns storage, networking, formatting, navigation, and every child state hides boundaries.

Preserve task lifetime

Store tasks that belong to a controller and cancel obsolete work when the controller or represented input ends. Use weak captures where a long-lived callback should not own the controller. Keep UI updates on the main actor.

Validation boundary

The containment code was not compiled or exercised. Lifecycle order, appearance forwarding, rotation, memory behavior, task cancellation, and accessibility remain Not verified.

Series navigation

References