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Обмежена через 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 та життєвим циклом
  • Lifecycle складається з п'яти методів: 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 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

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