Урок 3.9 Жизненный цикл контроллера представлений

Урок 3.9 Жизненный цикл контроллера представлений

Теперь, когда вы изучили основы Interface Builder, вы знаете, что контроллеры представлений являются основой внутренней структуры вашего приложения. Каждое приложение имеет по крайней мере один контроллер представления, а в большинстве приложений их несколько.

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


Что Вы Узнаете

  • Подходящее время для выполнения работы в рамках жизненного цикла контроллера представления
  • Как добавлять и удалять представления из иерархии представлений

Терминология


Связанные ресурсы

 

Когда вы закончите настройку представлений и иерархии представлений, вам нужно будет перейти к написанию логики вашего приложения. Классы контроллеров представлений отвечают за отображение данных пользователя и обработку взаимодействий с пользователем. В проекте Light вы использовали подкласс UIViewController, чтобы управлять событиями нажатия кнопок через действия (actions) и соответственно обновлять цвет фона представления. Контроллер представлений также контролирует создание своих представлений, обрабатывает события в зависимости от состояния представления по мере его продвижения через жизненный цикл и удаляет свои представления, когда они больше не нужны.

Жизненный цикл контроллера представлений

В iOS контроллеры представлений могут находиться в одном из нескольких различных состояний:

  • Представление не загружено
  • Представление появляется
  • Представление появилось
  • Представление исчезает
  • Представление исчезло

По мере того, как представление переходит из одного состояния в другое, iOS вызывает методы, определенные в SDK, которые вы можете реализовать в своем коде. На рисунке ниже вы можете увидеть название каждого метода, который вызывается при переходе состояния.

Вы, вероятно, заметили закономерность в названиях методов. После загрузки представления методы идут парами: “will” и “did”. (Сравните названия наблюдателей свойств willSet и didSet для переменных.) Этот стандартный шаблон Apple позволяет вам писать код до и после того, как происходит названное событие. Однако важно отметить, что возможен вызов “will” обратного вызова без соответствующего “did” обратного вызова. Например, метод viewWillDisappear(_:) может быть вызван без последующего вызова метода viewDidDisappear(_:).

Хотя каждое приложение отличается, вы узнаете рекомендации для каждого из методов жизненного цикла контроллера представлений и конкретные типы задач, связанных с ними.

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

В Xcode создайте новый проект, используя шаблон iOS App. Назовите проект “LifeCycle”.

View Did Load

После того как вы создали экземпляр контроллера представлений, будь то из storyboard или программно, контроллер представлений загрузит представление в память. Этот процесс создает представления, которые контроллер будет управлять.

После того как контроллер представлений закончил загрузку своих представлений, вызывается его функция viewDidLoad(), давая контроллеру возможность выполнить работу, которая зависит от того, что представление загружено и готово. Например, в проекте Light вызывалась функция updateUI(), чтобы синхронизировать цвет фона представления с состоянием lightOn. Другие типы задач по настройке, которые выполняются в viewDidLoad(), включают дополнительную инициализацию представлений, сетевые запросы и доступ к базе данных.

Чтобы добавить пользовательские реализации методов жизненного цикла контроллера представлений, вам нужно знать класс, связанный со сценой контроллера представлений. В проекте LifeCycle откройте основной storyboard, чтобы увидеть его контроллер представлений. Выберите контроллер представлений и используйте инспектор идентификации (Identity Inspector), чтобы увидеть его подкласс.

В поле Class вы увидите белую стрелку внутри серого круга, которая предоставляет вам ярлык для перехода к классу. Нажмите на стрелку, чтобы открыть связанный файл, ViewController.

Добавьте следующее в конец функции viewDidLoad():

 

print(”ViewController - View Did Load”)

 

Эта строка будет выводить в консоль строку "ViewController - View Did Load" каждый раз, когда вызывается viewDidLoad().

Соберите и запустите ваше приложение. В консольном окне вы должны увидеть ваше сообщение после загрузки представления. 

 

Управление событиями представления

Некоторые единицы работы могут выполняться только один раз, например, обновление шрифта, текста или цвета метки. Для таких задач viewDidLoad() является наиболее подходящим местом для их выполнения.

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

Эти методы включают:

  • viewWillAppear(_:)
  • viewDidAppear(_:)
  • viewWillDisappear(_:)
  • viewDidDisappear(_:)

Взгляните на документацию. Вы можете заметить, что каждый из этих методов требует вызова версии суперкласса в какой-то момент вашей реализации. Один из способов понять, почему это необходимо, — это представить следующее: когда вы пишете свой собственный подкласс контроллера представлений и хотите использовать методы жизненного цикла (включая viewDidLoad()), вы получите ошибку, если не используете ключевое слово override. Это потому, что суперкласс уже содержит определения этих методов. Использование ключевого слова override переопределяет реализацию в суперклассе, в данном случае UIViewController, и позволяет вашей реализации этого метода выполняться вместо реализации суперкласса.

Однако, поскольку вы не знаете реализации методов жизненного цикла UIViewController, вы можете создать неожиданные проблемы, не позволяя коду суперкласса выполняться. UIViewController может выполнять важную работу, необходимую для правильного функционирования вашего приложения. Чтобы исправить это, вы можете явно вызвать версию метода суперкласса, используя ключевое слово super. Теперь будут выполняться и ваш код, и код UIViewController.

Обычно вызов реализации суперкласса будет первой строкой вашего переопределенного метода.

 

Пример:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    // Добавьте ваш код сюда
}

 

View Will Appear и View Did Appear

После viewDidLoad() следующий метод в жизненном цикле контроллера представлений — viewWillAppear(_). Он вызывается прямо перед тем, как представление появится на экране. Это отличное место для добавления работы, которую нужно выполнить перед тем, как представление будет показано пользователю (и каждый раз, когда оно отображается). Например, если ваше представление отображает информацию, связанную с местоположением пользователя, вы можете запросить местоположение в viewWillAppear(_). Таким образом, представление может быть обновлено с учетом нового местоположения. Другие задачи включают: запуск сетевых запросов, обновление или обновление представлений (таких как статус-бар, навигационная панель или таблицы) и адаптацию к новым ориентациям экрана.

Как и ожидалось, viewDidAppear(_:) вызывается после того, как представление появится на экране. Если ваша работа должна выполняться каждый раз, когда представление отображается, но может занять больше нескольких секунд, вам следует поместить ее в viewDidAppear(_:). Таким образом, ваше представление будет быстро отображаться, пока ваша функция продолжает выполняться.

Используйте метод viewDidAppear(_:) для запуска анимации или для другого длительного кода, например, получения данных.

Для дальнейшего изучения жизненного цикла переопределите функцию viewWillAppear(_).

 

override func viewWillAppear(_ animated: Bool) {
 
}

 

Поскольку вы пишете свою собственную реализацию viewWillAppear(_), не забудьте вызвать версию суперкласса viewWillAppear(_). Поскольку вызов viewWillAppear(_) требует свойства animated, вы можете передать параметр, предоставленный подклассу.

 

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

 

Далее добавьте оператор print, чтобы вы могли проверить порядок методов жизненного цикла:

 

print(”ViewController - View Will Appear”)

 

Теперь также добавьте аналогичное выражение print в viewDidAppear(_).

Соберите и запустите ваше приложение. Вы должны увидеть три сообщения, напечатанные в вашей консоли.  Обратитесь к диаграмме состояний контроллера представлений выше и посмотрите, можете ли вы проследить переходы представления.

View Will Disappear и View Did Disappear

Вы, вероятно, уже догадались, что viewWillDisappear(_:) вызывается перед тем, как представление исчезнет с экрана. Этот метод выполняется, когда пользователь уходит с экрана, нажимая кнопку назад, переключая вкладки или вызывая или закрывая модальное окно. Вы можете использовать метод viewWillDisappear(_:) для сохранения изменений, скрытия клавиатуры или отмены сетевых запросов.

Последний метод в жизненном цикле — viewDidDisappear(_), который вызывается после того, как представление исчезнет с экрана — обычно после того, как пользователь перешел к новому представлению. Если этот метод выполняется, это означает, что представление действительно исчезло. Таким образом, этот метод дает вам возможность остановить службы, связанные с представлением, например, воспроизведение аудио или удаление наблюдателей уведомлений.

Теперь вернитесь к проекту LifeCycle. В вашем классе ViewController добавьте и переопределите функции "will" и "did" для события исчезновения представления. Добавьте операторы print, чтобы вы могли видеть события жизненного цикла.

Чтобы ваше представление исчезло, вам нужно добавить второй контроллер представлений. В противном случае не будет способа перейти с текущего представления. Но сначала добавьте контроллер панели вкладок для управления навигацией.

Откройте основной storyboard. Выберите сцену ViewController и встроите ее в контроллер панели вкладок.

Далее перетащите контроллер представлений из библиотеки объектов и добавьте его в качестве второй вкладки в контроллер панели вкладок. Вы можете организовать storyboard и обновить цвет фона контроллеров представлений по своему усмотрению.

Чтобы написать пользовательский код для нового контроллера представлений, вам нужно добавить новый файл. В меню Xcode выберите File > New > File (или Command-N). Затем выберите Cocoa Touch Class и нажмите Next.

Назовите новый класс "SecondViewController" и убедитесь, что поле Subclass установлено на UIViewController. 

Добавьте новый файл в папку LifeCycle. 

Далее вам нужно будет указать storyboard идентичность вашего нового контроллера представлений. Вернитесь к Main storyboard и выберите второй контроллер представлений. Откройте инспектор идентификации (Identity Inspector) и обновите класс до SecondViewController. Убедитесь, что нажали Return для подтверждения этого изменения. 

Чтобы увидеть, как iOS переходит между контроллерами представлений, вы также будете использовать методы жизненного цикла SecondViewController. Вернитесь в ваш файл класса SecondViewController и добавьте те же пять методов жизненного цикла, что и в ViewController. Не забудьте вызвать реализацию суперкласса. Добавьте операторы print для каждого события. Рассмотрите что-то вроде этого: "SecondViewController - View Did Load".

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

Но почему вы не видите никаких сообщений от SecondViewController? Storyboard инициализировал SecondViewController, так что происходит? Почему представление не загружено? Контроллер представлений не загружает свое представление до тех пор, пока представление не нужно отобразить пользователю. И это хорошо. Отсрочив загрузку, приложение экономит память, которая является ограниченным ресурсом в мобильной среде.

Чтобы загрузить экземпляр SecondViewController, вернитесь в Simulator и нажмите вкладку для второго контроллера представлений. На этом этапе вы должны увидеть следующие операторы print в таком порядке:

ViewController - View Did Load

ViewController - View Will Appear

ViewController - View Did Appear

SecondViewController - View Did Load

SecondViewController - View Will Appear

ViewController - View Will Disappear

ViewController - View Did Disappear

SecondViewController - View Did Appear

При переключении обратно на первую вкладку порядок операторов print будет таким:

ViewController - View Will Appear

SecondViewController - View Will Disappear

SecondViewController - View Did Disappear

ViewController - View Did Appear

Порядок вызова этих функций помогает объяснить, как представления контроллера представлений добавляются и удаляются из иерархии представлений:

  • Если представление контроллера представлений должно быть добавлено в иерархию, UIKit сначала проверяет, было ли представление загружено. Если нет, оно загружает представление и вызывает viewDidLoad().
  • Перед добавлением представления контроллера представлений в иерархию UIKit вызывает viewWillAppear(_).
  • Представления контроллеров представлений, которые больше не будут отображаться, удаляются, и вызываются методы viewWillDisappear(_) и viewDidDisappear(_) этих контроллеров.
  • Наконец, UIKit отображает новое представление и вызывает viewDidAppear(_).

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

Задание

Нарисуйте диаграмму состояний по памяти.

Лабораторная работа — Порядок событий

Цель

Цель этой лабораторной работы — углубить ваше понимание жизненного цикла представления. Вы создадите приложение, которое добавляет текст к метке на основе событий жизненного цикла контроллера представлений.

Создайте новый проект под названием "OrderOfEvents" с использованием шаблона iOS App.

Шаг 1

Создание новых подклассов контроллера представлений

  • Перетащите еще два контроллера представлений из библиотеки объектов и разместите их рядом с существующим контроллером представлений на storyboard. Первый контроллер представлений будет просто стартовой точкой для приложения с кнопкой для перехода на следующий экран. Второй контроллер представлений будет иметь метку, отображающую порядок различных событий жизненного цикла контроллера представлений. Третий контроллер представлений предоставит способ навигации от второго контроллера представлений.
  • Создайте два новых файла, по одному для каждого нового контроллера представлений, выбрав в меню Xcode File > New > File (или нажмите Command-N), затем выбрав Cocoa Touch Class. Назовите файлы “MiddleViewController” и “LastViewController”. Убедитесь, что оба они являются подклассами UIViewController.
  • Поочередно выберите средний и последний контроллеры представлений на storyboard и свяжите их с только что созданными файлами, используя инспектор идентификации, чтобы установить их класс на MiddleViewController и LastViewController.
  • Обратите внимание, что оригинальный контроллер представлений на вашем storyboard (предоставленный шаблоном проекта) уже имеет соответствующий файл ViewController, и его класс правильно назначен в инспекторе идентификации.

Шаг 2

Настройка Storyboard

  • Встроите первый контроллер представлений в навигационный контроллер.
  • На этом контроллере представлений добавьте кнопку с надписью: "Show me the life cycle". Создайте Show segue от кнопки к среднему контроллеру представлений.
  • Добавьте кнопку в нижней части среднего контроллера представлений. Создайте Show segue от этой кнопки к последнему контроллеру представлений. Поскольку информация не передается между контроллерами представлений, вам не нужно беспокоиться об установке идентификатора перехода.
  • Добавьте метку в верхней части среднего контроллера представлений. Замените ее текст на: "Nothing has happened yet". Поскольку эта метка потребуется для большего количества строк позже, используйте инспектор атрибутов, чтобы установить Lines на 0. Этот атрибут позволит метке расширяться по мере необходимости.
  • Добавьте метку в последний контроллер представлений и замените ее текст на: "Go back and see if anything happened."

Шаг 3

Обновление метки на основе событий жизненного цикла

  • На storyboard создайте outlet от метки в среднем контроллере представлений к MiddleViewController.
  • Добавьте свойство переменной типа Int сразу под outlet для вашей метки. Назовите его "eventNumber" и установите равным 1. В конце каждого события жизненного цикла ваш код будет добавлять 1 к этому свойству — нумеруя события по мере их добавления в метку.
  • В MiddleViewController добавьте метод для обновления вашей метки для данного события. Используйте условное связывание, чтобы извлечь существующий текст в метке. Установите текст метки равным тому, что уже было, плюс заявление о событии жизненного цикла, которое только что произошло, затем обновите eventNumber. Это заявление должно использовать eventNumber для отслеживания порядка событий. Ваш код может выглядеть следующим образом:

 

func addEvent(from: String) {
    if let existingText = label.text {
        label.text = "\(existingText)\nEvent number \(eventNumber) was \(from)"
        eventNumber += 1
    }
}

  • Что происходит? Приведенный выше код извлекает текст из метки, что необходимо сделать, потому что значение является опциональным. Затем он добавляет новую строку (\n) к тексту метки (начиная новую строку), описание события, которое только что произошло, и его номер события. Код увеличивает eventNumber на один, чтобы при следующем доступе к eventNumber он описывал следующий номер события в последовательности.
  • В MiddleViewController реализуйте все пять методов жизненного цикла, которые вы изучили в этом уроке: viewDidLoad(), viewWillAppear(_), viewDidAppear(_), viewWillDisappear(_) и viewDidDisappear(_).

 

override func viewDidLoad() {
    super.viewDidLoad()
    addEvent(from: "viewDidLoad")
}

  • Когда вы закончите заполнение тела всех пяти методов жизненного цикла, запустите приложение и переходите с экрана на экран. Что происходит, когда вы переходите к последнему контроллеру представлений и возвращаетесь к среднему контроллеру представлений? Все ли события жизненного цикла происходят снова? Если нет, то почему? А что происходит, когда вы возвращаетесь к начальному контроллеру представлений, а затем к среднему контроллеру представлений? Все ли события жизненного цикла происходят снова? Почему это отличается от навигации с последнего контроллера на средний?
  • Отличная работа! Вы создали приложение, которое отображает порядок событий жизненного цикла контроллера представлений по мере их возникновения. Обязательно сохраните его в своей папке проекта.

 

 

 


Отрывок из книги
Develop in Swift Fundamentals
Apple Education
https://books.apple.com/ru/book/develop-in-swift-fundamentals/id1581182804

Information

Apple, the Apple logo, Apple Books, Apple TV, Apple Watch, Cocoa, Cocoa Touch, Finder, Handoff, HealthKit, iPad, iPad Pro, iPhone, iPod touch, Keynote, Mac, macOS, Numbers, Objective-C, Pages, Photo Booth, Safari, Siri, Spotlight, Swift, tvOS, watchOS, and Xcode are trademarks of Apple Inc., registered in the U.S. and other countries. App Store and iBooks Store are service marks of Apple Inc., registered in the U.S. and other countries. ​
The Bluetooth® word mark and logos are registered trademarks owned by Bluetooth SIG, Inc. and any use of such marks by Apple is under license. ​
IOS is a trademark or registered trademark of Cisco in the U.S. and other countries and is used under license. ​
Other product and company names mentioned herein may be trademarks of their respective companies.