ViewController — суть, контроллер экрана в iOS и его Lifecycle

Автор: IT Sectr Опубликовано: 2026-02-22 Время чтения: 7 мин

UIViewController — центральный класс iOS-приложения, управляющий экраном и его содержимым. Каждый экран iPhone или iPad управляется одним ViewController, который координирует отображение, жизненный цикл и навигацию. Подробнее об архитектуре UIKit читайте в официальной документации Apple.

Главное

  • UIViewController — базовый класс для управления экраном в UIKit с собственным жизненным циклом
  • viewDidLoad — вызывается один раз, точка инициализации UI и подписки на данные
  • viewWillAppear — экран скоро станет видимым, обновление данных перед показом
  • Lifecycle включает пять методов: viewDidLoad, viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear
  • Massive View Controller — главный антипаттерн iOS, решается через MVVM или Coordinator

Что такое ViewController?

UIViewController — класс из фреймворка UIKit, который управляет иерархией UIView и координирует отображение данных на экране. Каждое приложение iOS содержит минимум один ViewController — корневой контроллер окна. Контроллер обрабатывает повороты экрана, переходы между экранами и события жизненного цикла.

Архитектура MVC (Model-View-Controller) в iOS реализована именно через UIViewController: контроллер получает данные от модели и обновляет представление. ViewController не является визуальным элементом — он управляет свойством view, которое содержит иерархию subview. По данным Apple (2026), UIKit содержит более 40 встроенных подклассов UIViewController.

Первый iPhone SDK (2008) включал UIViewController с тремя методами жизненного цикла. За 18 лет Apple добавила поддержку Container View Controller, адаптивные презентации, UIViewControllerTransitioningDelegate для кастомных анимаций и режим разделённого экрана на iPad. UIViewController остаётся обязательным компонентом для приложений на UIKit.

Жизненный цикл UIViewController

Жизненный цикл UIViewController — последовательность методов, вызываемых системой при создании, отображении и скрытии экрана. Понимание Lifecycle критически важно: неправильное размещение кода приводит к утечкам памяти, лишним сетевым запросам и мерцанию интерфейса.

МетодМомент вызоваНазначение
viewDidLoadОдин раз, после загрузки view в памятьНачальная настройка UI, подписка на Combine
viewWillAppearПеред появлением экранаОбновление данных, скрытие/показ navigation bar
viewDidAppearПосле появления экранаЗапуск анимаций, аналитика, обновление камеры
viewWillDisappearПеред уходом с экранаСохранение черновиков, отписка от уведомлений
viewDidDisappearПосле ухода с экранаОстановка тяжёлых процессов, освобождение ресурсов

Порядок вызова при появлении экрана

При первом показе экрана последовательность: init → loadView → viewDidLoad → viewWillAppear → viewDidAppear. При повторном появлении (возврат с другого экрана): viewWillAppear → viewDidAppear. viewDidLoad вызывается только один раз за время жизни контроллера.

viewDidLoad, init и настройка UI

Метод viewDidLoad — основная точка настройки пользовательского интерфейса. Вызывается после загрузки view в память, когда все IBOutlet уже связаны. Здесь создаются элементы UI программно, настраиваются constraints, загружаются начальные данные.

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)
    }
}

В SwiftUI этот код эквивалентен телу View. Но UIViewController даёт полный контроль над жизненным циклом и оптимизацией. bindViewModel использует Combine для реактивной подписки — данные обновляются автоматически при изменении модели.

viewWillAppear и обновление данных

viewWillAppear вызывается каждый раз перед появлением экрана, даже если он уже был в памяти. Это место для обновления данных, которые могли измениться на другом экране: перезагрузка списка, обновление счётчика уведомлений, настройка navigation bar под конкретный экран.

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

    // Скрываем navigation bar на этом экране
    navigationController?.setNavigationBarHidden(true, animated: animated)

    // Обновляем данные при возврате с другого экрана
    tableView.reloadData()
    badgeLabel.text = "\(CartManager.shared.itemCount)"
}

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

    // Аналитика: только после того, как пользователь увидел экран
    AnalyticsService.shared.logScreenView("Profile")
}

Разница между viewDidLoad и viewWillAppear критична: viewDidLoad выполняется один раз и подходит для статичной настройки, viewWillAppear — каждый раз при показе, подходит для динамических обновлений. Помещение сетевых запросов в viewDidLoad приведёт к показу устаревших данных при возврате на экран.

Container View Controller: UINavigationController и UITabBarController

Container View Controller — ViewController, который управляет одним или несколькими дочерними ViewController. Apple предоставляет три встроенных контейнера: UINavigationController (стек экранов), UITabBarController (вкладки) и UISplitViewController (мастер-детейл для iPad).

UINavigationController организует переходы в стек — push добавляет экран, pop удаляет. UITabBarController переключает между независимыми разделами приложения. UISplitViewController показывает два контроллера рядом на iPad и один на iPhone. Разработчик может создать собственный контейнер через addChild.

swift
// Кастомный Container View Controller
final class ContainerViewController: UIViewController {

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

    override func viewDidLoad() {
        super.viewDidLoad()

        // Добавление дочернего контроллера
        addChild(sidebarVC)
        view.addSubview(sidebarVC.view)
        sidebarVC.didMove(toParent: self)

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

Правильная работа с Container View Controller требует вызова addChild, добавления view и didMove(toParent:) в этом порядке. При удалении — willMove(toParent: nil), removeFromSuperview, removeFromParent. Нарушение последовательности приводит к утечкам памяти.

Решение Massive View Controller через MVVM и Coordinator

Проблема Massive View Controller — когда UIViewController содержит сотни строк кода с бизнес-логикой, сетевыми запросами, навигацией и UI-кодом. Apple осознаёт проблему и рекомендует MVVM (Model-View-ViewModel) вместе с Coordinator для выноса навигации.

MVVM переносит бизнес-логику из контроллера в ViewModel. Controller только связывает ViewModel с View через Combine или делегат. Coordinator выносит навигационную логику — создание и переход между контроллерами — в отдельный класс. Этот подход внедрён в лучшие практики Apple с 2024 года.

swift
// Coordinator — управление навигацией
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: когда что выбрать

Выбор между UIViewController и SwiftUI View зависит от года начала проекта, требований к кастомизации и минимальной поддерживаемой версии iOS. UIKit с UIViewController остаётся основой для проектов, начатых до 2020 года, и для приложений с глубокой кастомизацией интерфейса.

SwiftUI подходит для новых проектов с iOS 17+, стандартных интерфейсов и прототипов. Однако для кастомных переходов, работы с камерой, MapKit, сложной анимации CALayer требуется UIViewController. Apple рекомендует сочетать подходы через UIHostingController (SwiftUI внутри UIKit) и UIViewRepresentable (UIKit внутри SwiftUI).

СценарийUIKit (UIViewController)SwiftUI (View)
Кастомная анимацияПолный контроль через UIViewPropertyAnimatorОграниченная through Animation
Работа с камеройAVCaptureSession + UIViewPreviewЧерез UIViewControllerRepresentable
CollectionViewUICollectionView + UICollectionViewLayoutLazyVGrid/LazyHGrid
Адаптация iPadUISplitViewController + UITraitCollectionNavigationSplitView + sizeClass
Скорость разработкиМедленнее (ручной layout)Быстрее (декларативный)

Часто задаваемые вопросы

Чем UIViewController отличается от UIView?

UIViewController — контроллер, управляющий экраном и его жизненным циклом. UIView — представление, отображающее контент. ViewController содержит иерархию UIView, но сам не является визуальным элементом. Один контроллер управляет множеством вью.

Что такое Massive View Controller?

Massive View Controller — антипаттерн, когда UIViewController содержит слишком много логики: данные, навигацию, сетевые запросы, анимацию. Решение — вынос кода в отдельные сервисы, координаторы и ViewModel (MVVM).

Как передать данные между ViewController?

Четыре способа: через свойство при prepare(for:sender:) (Segue), через делегат (Delegate), через замыкание (Closure), через общий сервис. Для слабой связанности используют Coordinator + Delegate или Combine.

Что такое Container View Controller?

Container View Controller — контроллер, управляющий дочерними ViewController. Примеры: UINavigationController, UITabBarController, UISplitViewController. Родительский контроллер добавляет дочерние через addChild, переключается между ними и управляет их layout.

Когда использовать UIViewController вместо SwiftUI View?

UIViewController — для сложной кастомной анимации, работы с камерой, картой, видео, UICollectionView с кастомным layout. SwiftUI View — для стандартных интерфейсов iOS 13+. Допустимо сочетание через UIHostingController.

Итоги

  • UIViewController — центральный класс UIKit для управления экраном, иерархией UIView и жизненным циклом
  • Жизненный цикл состоит из пяти методов: viewDidLoad, viewWillAppear, viewDidAppear, viewWillDisappear, viewDidDisappear
  • viewDidLoad — точка начальной настройки UI, вызывается один раз за время жизни контроллера
  • viewWillAppear — вызывается каждый раз перед показом, подходит для обновления данных и настройки navigation bar
  • Container View Controller (UINavigationController, UITabBarController) управляет иерархией дочерних контроллеров
  • Massive View Controller решается через MVVM (вынос логики в ViewModel) и Coordinator (вынос навигации)
  • UIViewController и SwiftUI можно сочетать через UIHostingController и UIViewRepresentable для гибридных приложений

Мы разработаем мобильное приложение под ключ

IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также