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

Рассмотрим когда вызываются методы контроллера и что можно делать внутри них. Когда настраивать вьюхи и данные.

Читается за 2 минуты Обновлено 2 месяца назад

View не создается после инициализации контроллера.

Системе нужна причина, чтобы создать view. Концепция жизненного цикла строится вокруг этой особенности. Просто держите в уме, что view создаётся по необходимости.

 Про жизненный цикл UIViewController
Про жизненный цикл UIViewController
Инициализируем UIViewController

Рассмотрим UIViewController. Доступно два инициализатора:

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
    super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
    
required init?(coder: NSCoder) {
    super.init(coder: coder)
}

Ещё есть инициализатор без параметров init(), но это обёртка над первым инициализатором.

На этом этапе контроллер инициализирует проперти и отрабатывает тело инициализатора. View не загружается, аутлеты не активны. В инициализаторе с nib сохраняется только имя файла, а сам файл не подгружается.

Загружаем View

Когда разработчик презентует контроллер, для системы это причина загрузить view. В контроллере есть методы жизненного цикла, с помощью которых мы следим за процессом и добавляем свою логику.

override func loadView() {}

Метод loadView() вызывается системой. Его не нужно вызывать вручную. Но можно переопределить, чтобы подменить корневую view. Если нужно загрузить view вручную (и вы уверены, что это нужно), то держите красную кнопку loadViewIfNeeded(). Флаг isViewLoaded показывает загружена view или нет.

Второй метод легендарен, как Стив Джобс. Он вызывается, когда view закончила загрузку.

override viewDidLoad() {
    super.viewDidLoad()
}

Разработчики не просто так настраивают контроллер и view-хи в методе viewDidLoad(). До вызова этого метода корневой view не существует, а после - контроллер готов появиться на экране. Во viewDidLoad() память под view выделена, view загружена и готова к настройкам.

View нельзя настраивать в инициализаторе: если вызывать controller.view - она загрузится. Но контроллер сейчас не виден, а может быть вообще никогда не покажется. Зря потратите память и займете главный поток.

Проект от такого не развалится, но элементы интерфейса расходуют память — не нужно тратить её раньше, чем нужно. Делайте это по необходимости.

Раньше я делал проперти-вьюхи контроллера так:

class ViewController: UIViewController {
    
    let redView = UIView()
}

Но когда подготавливал статью, понял ошибку. Проперти инициализируется вместе с контроллером, а значит, память для view выделится сразу. Правильно отложить это до требования, пометьте проперти как lazy.

В методе viewDidLoad() размеры view-х неверные - привязываться к высоте и ширине нельзя. Делайте настройку, которая не зависит от размеров.

Есть метод viewDidUnload(). Корневая view может выгружаться из памяти, а это означает кое-что невероятное!

Метод viewDidLoad() может вызываться несколько раз.

Если модальный контроллер закрыть, view выгрузится из памяти, но контроллер будет жив. Аутлеты здесь активны, но уже не имеют смысла — их можно ресетить. Если показать контроллер ещё раз, view загрузится снова. Если система выгрузила view, значит, у неё была причина. Не нужно обращаться к корневой view в этом методе — это загрузит view.

В вашем проекте ничего не сломается, viewDidLoad() несколько раз вызывается редко. Разделите настройку данных и view-х в следующем проекте.

Показываем и прячем View

Появление контроллера начинается с метода viewWillAppear:

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

Появление контроллера в модальном окне или переход в UINavigationController-e вызовут viewWillAppear до анимации, а viewDidAppear — после. При вызове viewWillAppear view уже находится в иерархии.

Оба метода в связке. Тут делать настройку не нужно, но можно спрятать или показать view-хи, или добавить несложное поведение. В методе viewDidAppear() начинайте сетевой запрос или крутите индикатор загрузки. Оба метода могут вызываться несколько раз.

Есть методы, которые сообщают, что view пропадает с экрана. Вот схема:

 Схема жизненного цикла ViewController.
Схема жизненного цикла ViewController.

Обратите внимание на пару антагонистов viewWillDisappear() и viewDidDisappear(). Они вызываются, когда view удаляется из иерархии представлений. Если вы показываете другой контроллер поверх, то методы не вызываются.

Layout

Методы лейаута привязаны к жизненному циклу view. Доступно 3 метода:

override func viewWillLayoutSubviews() {
    super.viewWillLayoutSubviews()
}
    
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
}

Первый метод вызывается до layoutSubviews() корневой view, второй - после. Во втором методе размеры корректные, а view размещены правильно — можно подвязываться к размерам корневой view.

Есть отдельный метод про изменение размеров view. Он вызывается и для поворота устройства:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
    super.viewWillTransition(to: size, with: coordinator)
}

После него вызываются методы viewWillLayoutSubviews() и viewDidLayoutSubviews().

Кончилась память

Если вы не очистите объекты, из-за которых это происходит, iOS принудительно крашнет приложение. Этот метод - предупреждение, у вас есть шанс освободить немного памяти.

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
}