ViewController — the essence of the screen controller in iOS and its Lifecycle

Author: IT Sectr Published: 2026-02-22 Reading time: 7 min

UIViewController is the central class of iOS applications, managing the screen and its content. Every iPhone or iPad screen is managed by one ViewController, which coordinates display, lifecycle, and navigation. Learn more about UIKit architecture in Apple's official documentation.

Key Takeaways

  • UIViewController — the base class for managing a screen in UIKit with its own lifecycle
  • viewDidLoad — called once, the point of UI initialization and data subscription
  • viewWillAppear — the screen will soon become visible, updating data before display
  • Lifecycle includes five methods: viewDidLoad, viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear
  • Massive View Controller — the main iOS anti-pattern, solved via MVVM or Coordinator

What is a ViewController?

UIViewController is a class from the UIKit framework that manages a hierarchy of UIView and coordinates the display of data on the screen. Every iOS application contains at least one ViewController — the root controller of the window. The controller handles screen rotations, transitions between screens, and lifecycle events.

The MVC (Model-View-Controller) architecture in iOS is implemented precisely through UIViewController: the controller receives data from the model and updates the view. A ViewController is not a visual element — it manages the view property, which contains a hierarchy of subviews. According to Apple (2026), UIKit contains more than 40 built-in subclasses of UIViewController.

The first iPhone SDK (2008) included UIViewController with three lifecycle methods. Over 18 years, Apple added support for Container View Controller, adaptive presentations, UIViewControllerTransitioningDelegate for custom animations, and split-screen mode on iPad. UIViewController remains a mandatory component for UIKit applications.

The Lifecycle of UIViewController

The UIViewController lifecycle is a sequence of methods called by the system when creating, displaying, and hiding a screen. Understanding the Lifecycle is critically important: incorrect placement of code leads to memory leaks, unnecessary network requests, and UI flickering.

MethodCall MomentPurpose
viewDidLoadOnce, after view is loaded into memoryInitial UI setup, Combine subscription
viewWillAppearBefore the screen appearsData updates, hide/show navigation bar
viewDidAppearAfter the screen appearsStart animations, analytics, camera updates
viewWillDisappearBefore leaving the screenSave drafts, unsubscribe from notifications
viewDidDisappearAfter leaving the screenStop heavy processes, free resources

Call order when the screen appears

On the first display of the screen, the sequence is: init → loadView → viewDidLoad → viewWillAppear → viewDidAppear. On reappearance (returning from another screen): viewWillAppear → viewDidAppear. viewDidLoad is called only once during the lifetime of the controller.

viewDidLoad, init and UI Setup

The viewDidLoad method is the main point for configuring the user interface. It is called after the view is loaded into memory, when all IBOutlet connections are already established. Here, UI elements are created programmatically, constraints are configured, and initial data is loaded.

swift
final class ProfileViewController: UIViewController {

    private let tableView = UITableView()
    private let viewModel = ProfileViewModel()

    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
        bindViewModel()
    }

    private func setupUI() {
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
        ])
        tableView.register(ProfileCell.self,
                         forCellReuseIdentifier: ProfileCell.reuseId)
    }

    private func bindViewModel() {
        viewModel.$user
            .receive(on: DispatchQueue.main)
            .sink { [weak self] user in
                self?.title = user.name
            }
            .store(in: &cancellables)
    }
}

In SwiftUI, this code is equivalent to the View body. But UIViewController gives full control over the lifecycle and optimization. bindViewModel uses Combine for reactive subscription — data updates automatically when the model changes.

viewWillAppear and Data Updates

viewWillAppear is called every time before the screen appears, even if it was already in memory. This is the place to update data that might have changed on another screen: reloading a list, updating the notification badge, configuring the navigation bar for a specific screen.

swift
override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)

    // Hide navigation bar on this screen
    navigationController?.setNavigationBarHidden(true, animated: animated)

    // Update data when returning from another screen
    tableView.reloadData()
    badgeLabel.text = "\(CartManager.shared.itemCount)"
}

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    // Analytics: only after the user has seen the screen
    AnalyticsService.shared.logScreenView("Profile")
}

The difference between viewDidLoad and viewWillAppear is critical: viewDidLoad runs once and is suitable for static setup, viewWillAppear runs each time the screen is displayed and is suitable for dynamic updates. Placing network requests in viewDidLoad will result in showing stale data when returning to the screen.

Container View Controller: UINavigationController and UITabBarController

Container View Controller is a ViewController that manages one or more child ViewControllers. Apple provides three built-in containers: UINavigationController (screen stack), UITabBarController (tabs), and UISplitViewController (master-detail for iPad).

UINavigationController organizes transitions in a stack — push adds a screen, pop removes it. UITabBarController switches between independent sections of the application. UISplitViewController shows two controllers side by side on iPad and one on iPhone. A developer can create a custom container via addChild.

swift
// Custom Container View Controller
final class ContainerViewController: UIViewController {

    private let sidebarVC = SidebarViewController()
    private let contentVC = ContentViewController()

    override func viewDidLoad() {
        super.viewDidLoad()

        // Adding a child controller
        addChild(sidebarVC)
        view.addSubview(sidebarVC.view)
        sidebarVC.didMove(toParent: self)

        addChild(contentVC)
        view.addSubview(contentVC.view)
        contentVC.didMove(toParent: self)
    }
}

Proper work with Container View Controller requires calling addChild, adding the view, and didMove(toParent:) in that order. When removing: willMove(toParent: nil), removeFromSuperview, removeFromParent. Violating the sequence leads to memory leaks.

Solving Massive View Controller with MVVM and Coordinator

The Massive View Controller problem occurs when a UIViewController contains hundreds of lines of code with business logic, network requests, navigation, and UI code. Apple acknowledges the problem and recommends MVVM (Model-View-ViewModel) together with Coordinator for extracting navigation.

MVVM moves business logic from the controller into a ViewModel. The Controller only binds the ViewModel to the View via Combine or a delegate. Coordinator extracts navigation logic — creating and transitioning between controllers — into a separate class. This approach has been adopted in Apple best practices since 2024.

swift
// Coordinator — navigation management
protocol Coordinator {
    var childCoordinators: [Coordinator] { get set }
    func start()
}

final class MainCoordinator: Coordinator {

    var childCoordinators = [Coordinator]()
    private let navigationController: UINavigationController

    init(navigationController: UINavigationController) {
        self.navigationController = navigationController
    }

    func start() {
        let vc = ListViewController()
        vc.didSelectItem = { [weak self] item in
            self?.showDetail(item)
        }
        navigationController.pushViewController(vc, animated: false)
    }

    private func showDetail(_ item: Item) {
        let vc = DetailViewController(item: item)
        navigationController.pushViewController(vc, animated: true)
    }
}

UIViewController vs SwiftUI: When to Choose What

The choice between UIViewController and SwiftUI View depends on the project start year, customization requirements, and the minimum supported iOS version. UIKit with UIViewController remains the foundation for projects started before 2020 and for applications with deep interface customization.

SwiftUI is suitable for new projects with iOS 17+, standard interfaces, and prototypes. However, custom transitions, camera work, MapKit, complex CALayer animations require UIViewController. Apple recommends combining approaches via UIHostingController (SwiftUI inside UIKit) and UIViewRepresentable (UIKit inside SwiftUI).

ScenarioUIKit (UIViewController)SwiftUI (View)
Custom animationFull control via UIViewPropertyAnimatorLimited through Animation
Camera workAVCaptureSession + UIViewPreviewVia UIViewControllerRepresentable
CollectionViewUICollectionView + UICollectionViewLayoutLazyVGrid/LazyHGrid
iPad adaptationUISplitViewController + UITraitCollectionNavigationSplitView + sizeClass
Development speedSlower (manual layout)Faster (declarative)

Frequently Asked Questions

How is UIViewController different from UIView?

UIViewController is a controller that manages the screen and its lifecycle. UIView is a view that displays content. A ViewController contains a hierarchy of UIViews but is not itself a visual element. One controller manages multiple views.

What is a Massive View Controller?

Massive View Controller is an anti-pattern where UIViewController contains too much logic: data, navigation, network requests, animation. The solution is extracting code into separate services, coordinators, and ViewModel (MVVM).

How to pass data between ViewControllers?

Four ways: via a property in prepare(for:sender:) (Segue), via a delegate (Delegate), via a closure (Closure), via a shared service. For loose coupling, use Coordinator + Delegate or Combine.

What is a Container View Controller?

Container View Controller is a controller that manages child ViewControllers. Examples: UINavigationController, UITabBarController, UISplitViewController. The parent controller adds children via addChild, switches between them, and manages their layout.

When to use UIViewController instead of SwiftUI View?

UIViewController — for complex custom animations, camera work, maps, video, UICollectionView with custom layout. SwiftUI View — for standard interfaces on iOS 13+. Combining via UIHostingController is acceptable.

Summary

  • UIViewController — the central UIKit class for managing the screen, UIView hierarchy, and lifecycle
  • Lifecycle consists of five methods: viewDidLoad, viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear
  • viewDidLoad — the point of initial UI setup, called once during the controller's lifetime
  • viewWillAppear — called every time before display, suitable for data updates and navigation bar configuration
  • Container View Controller (UINavigationController, UITabBarController) manages the hierarchy of child controllers
  • Massive View Controller is solved via MVVM (extracting logic into ViewModel) and Coordinator (extracting navigation)
  • UIViewController and SwiftUI can be combined via UIHostingController and UIViewRepresentable for hybrid applications

We will develop a mobile application turnkey

IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.

Discuss the project

Read also