В этом туториале мы создадим простое, но полнофункциональное приложение под watchOS 4. А конкретнее, мы будем работать над приложением придуманной авиакомпании Air Aber для Apple Watch.
Из этого туториала вы узнаете:
- Как добавлять целевую сборку watchOS 4 в приложение iOS.
- Как обмениваться данными между двумя целевыми сборками.
- Как добавить в Storyboard контроллер интерфейса watchOS и расположить объекты интерфейса.
- Как создать подкласс
WKInterfaceController
и соединить всё вместе. - Как добавить новый контроллер интерфейса, добавить к нему таблицу и создать прототип из строк.
- Как создать подкласс класса
WKInterfaceController
, чтобы заполнить таблицу, настроить строки и обрабатывать выбор. - Как сделать контроллер интерфейса модальным и передавать ему данные для отображения.
- Как создавать анимации на основе изображений.
- Как использовать API анимации watchOS 4.
Часть 1: начало работы
Из этой части вы узнаете:
- Как добавлять целевую сборку watchOS 4 в приложение iOS.
- Как обмениваться данными между двумя целевыми сборками.
- Как добавить в Storyboard контроллер интерфейса watchOS и расположить объекты интерфейса.
- Как создать подкласс
WKInterfaceController
и соединить всё вместе.
Приступаем
Начнём со скачивания начального проекта для этого туториала.
Откройте его в Xcode, выполните сборку и запустите. Вы должны увидеть пустой белый экран:
Как видно, пока в этом проекте довольно мало всего: в нём есть несколько нужных вспомогательных файлов, и на этом всё. Мы скоро решим эту проблему!
Добавляем приложение WatchKit
Выберите File\New\Target…. В открывшемся диалоговом окне выберите watchOS\Application\WatchKit App, затем нажмите Next:
На следующем экране введите в качестве Product Name слово Watch, убедитесь, что в поле Language выбрано Swift и снимите все установленные флажки. Нажмите на Finish:
Система спросит, нужно ли активировать схему «watch» — она нам понадобится, так что выберите Activate:
Поздравляю, вы только что создали ваше первое приложение для Watch! Всё на самом деле так просто.
Можно заметить, что это действие на самом деле создало две целевых сборки, а не одну, и две соответствующие группы в Project navigator. Так получилось, потому что код приложения Watch на самом деле выполняется как Extension (расширение), заключённое внутри приложения Watch, почти так же, как работают Today Extension в iOS.
Разверните группы Watch и Watch Extension в Project navigator, и вы увидите, что storyboard находится в группе Watch, а созданные шаблоном целевой сборки классы находятся в группе Watch Extension:
Вот какого порядка мы будем придерживаться в работе: любой добавляемый код должен храниться внутри группы Watch Extension, и добавляться в целевую сборку Watch Extension, в то время как любые ресурсы или storyboard должны добавляться в группу Watch.
Немного приберёмся
Прежде чем продолжить, нам нужно удалить пару элементов, добавленных шаблоном целевой сборки, которые мы собираемся заменить.
Нажмите правой клавишей мыши на InterfaceController.swift в Project navigator и выберите Delete. В диалговом окне выберите Move to Trash, чтобы файл на самом деле был удалён из проекта:
Далее откройте Interface.storyboard, выберите единственный имеющийся там контроллер интерфейса и нажмите кнопку delete. В результате у вас должен остаться пустой storyboard, или, как я предпочитаю воспринимать его, пустой холст.
Общие данные и код
В начальном проекте есть файл JSON, содержащий всю информацию о рейсах Air Aber, и представляющий эти данные класс моделей. Именно это должно быть общим между целевыми сборками, потому что высока вероятность того, что приложение iOS и приложение Watch будут использовать один класс моделей и данные — помните принцип DRY?
Разверните группу Shared в Project navigator и выберите Flights.json. Затем найдите раздел Target Membership в File inspector и поставьте флажок Watch Extension:
Теперь файл будет включён и в AirAber, и в Watch Extension.
Повторите процесс для другого файла в группе Shared — Flight.swift.
И закончив с этим, мы наконец можем приступить к созданию интерфейса сведений о рейсах!
Создание интерфейса
Откройте Watch\Interface.storyboard и перетащите interface controller (контроллер интерфейса) из Object Library на холст storyboard. Выбрав контроллер интерфейса, откройте Attributes inspector, задайте для Identifier значение Flight и поставьте флажок Is Initial Controller. Снимите флажок Activity Indicator On Load:
Так мы задали идентификатор, чтобы можно было обращаться к контроллеру интерфейса в коде. Поставив флажок Is Initial Controller, мы просто даём знать WatchKit, что этот контроллер интерфейса нужно отображать при первом запуске приложения Watch. Этот интерфейс не скачивает никаких данных, поэтому ему не нужно показывать индикатор активности.
Чтобы упростить этот туториал, мы будем создавать схему интерфейса только для часов размером 42 мм. В ваших собственных приложениях нужно будет проверять, правильно ли оно отображется на всех размерах часов. В нижнем левом углу панели storyboard должно быть выбрано View as: Apple Watch 42mm.
Макет приложения Watch полностью отличается от макета iOS. Первое, что вы заметите: можно перемещать или изменять объекты UI, перетаскивая их в контроллере интерфейса. При перетаскивании объекта на контроллер он располагается под предыдущими объектами и экран довольно быстро заполняется. Чтобы расставить объекты в ряд, нужно использовать группы, которые во многом похожи на Stack View в iOS и macOS.
Итак, для начала перетащим group (группу) из Object Library на контроллер интерфейса:
Хотя пока это выглядит не очень впечатляюще, эта группа в будущем будет содержать логотип Air Aber, номер рейса и маршрут полёта.
Выбрав новую группу, перейдите к Attributes inspector и поменяйте Insets на Custom. Появятся четыре текстовых поля, в которых можно вручную задать отступы группы.
Поменяйте значение Top на 6:
Таким образом мы просто добавим немного больший отступ сверху.
Далее перетащите в группу image. Если группа отреагирует на изменение верхнего отступа сворачиванием (спасибо, Xcode!), тогда перетащите изображение непосредственно в Document Outline и убедитесь, что он стал дочерним элементом группы, а не соседним:
Теперь нам нужно отображаемое изображение. Скачайте этот логотип и перетащите в Watch\Assets.xcassets. При этом будет создан новый набор изображений, в котором наша картинка будет занимать слот 2x:
Я хочу придать этому изображению оттенок корпоративного цвета Air Aber, поэтому выберем изображение, затем изменим значение Render As на Template Image.
Снова откройте Watch\Interface.storyboard и выберите image. С помощью Attributes inspector внесите следующие изменения:
- Измените значение Image на Logo — если оно не отображается в раскрывающемся меню, просто введите его с клавиатуры.
- Измените значение Tint на #FA114F (можно ввести его в панели Color RGB Sliders).
- Измените параметр Width на Fixed со значением 40.
- Измените параметр Height на Fixed со значением 40.
Attributes inspector должен выглядеть следующим образом:
Не волнуйтесь, если не видите логотип: похоже, Xcode не меняет оттенок шаблонов изображений во время разработки! Поверьте, он будет ярко-розовым после сборки и запуска приложения.
Далее перетащите в созданную группу ещё одну group и проверьте, что она появилась справа от изображения, а затем воспользуйтесь Attributes inspector, чтобы изменить её Layout на Vertical. Также измените Spacing на Custom\0, а Width — на Size to Fit Content.
Затем перетащите на новую группу две label. Мы задали вертикальное расположение, поэтому метки появятся одна над другой:
Выберите верхнюю метку и воспользуйтесь Attributes inspector для ввода в поле Text значения Flight 123 и изменения Text Color на #FA114F (вместо повторного ввода в RGB panel можно выбрать розовый цвет из списка Recently Used Colors меню Color).
Затем выберите нижнюю метку и введите в поле Text текст MEL to SFO. Теперь контроллер интерфейса должен выглядеть вот так:
Этот текст временный, он будет заменён, когда мы подключим интерфейс к классу контроллера.
Далее перетащите ещё одну group на контроллер интерфейса, но на этот раз убедитесь, что добавляете её как соседний элемент самой первой группы. Если вам не удаётся правильно расположить группу в иерархии, то воспользуйтесь Document outline.
Выбрав эту новую группу, присвойте Layout значение Vertical, а Spacing — значение Custom\0.
Затем перетащите в эту новую группу три label:
Проверьте в Document outline, что все три метки находятся внутри группы, а не рядом с ней!
Выберите верхнюю метку и измените в Attributes inspector значение Text на AA123 Boards.
Затем выберите среднюю метку и замените её Text на 15:06. Далее измените Text Color на #FA114F, а Font — на System, присвоив Style значение Regular, и выбрав Size, равный 54. Наконец, измените Height на Fixed со значением 44.
Выберите нижнюю метку и замените её Text на On time, а Text Color — на #04DE71.
Теперь контроллер интерфейса должен выглядеть следующим образом:
Теперь нам осталось только добавить ещё одну группу, а потом приступать к созданию полей классов Outlet, чтобы этот интерфейс отображал реальные данные.
Перетащите новую group из Object Library в нижнюю группу, на этот раз она должна быть дочерней и находиться в самом низу содержащей её группы. Далее добавим в ней две label. Теперь полная иерархия объектов интерфейса должна выглядеть вот так:
Задайте в Attributes inspector для Text левой метки значение Gate 1A. Для правой метки задайте Text значение Seat 64A и выберите для Horizontal Alignment опцию Right.
Теперь законченный интерфейс должен выглядеть подобным образом:
Поздравляю, вы закончили создавать макет интерфейса своего первого приложения Watch! Теперь нужно заполнить его настоящими данными и запустить приложение в симуляторе.
Создание контроллера
Нажмите правой кнопкой на группу Watch Extension в Project navigator и выберите New File…. В открывшемся диалоговом окне выберите watchOS\Source\WatchKit Class и нажмите Next. Назовите новый класс FlightInterfaceController и проверьте, что он является подклассом
WKInterfaceController
, а для Language задано значение Swift:Нажмите Next, а затем Create.
Когда новый файл откроется в редакторе кода, удалите три пустых заготовки метода, и у вас должны остаться только оператор import и определение класса.
Добавьте сверху
FlightInterfaceController
следующие поля Outlet:@IBOutlet var flightLabel: WKInterfaceLabel!
@IBOutlet var routeLabel: WKInterfaceLabel!
@IBOutlet var boardingLabel: WKInterfaceLabel!
@IBOutlet var boardTimeLabel: WKInterfaceLabel!
@IBOutlet var statusLabel: WKInterfaceLabel!
@IBOutlet var gateLabel: WKInterfaceLabel!
@IBOutlet var seatLabel: WKInterfaceLabel!
Здесь мы просто добавили outlet для каждой метки, которую мы ранее поместили в интерфейс. Скоро мы их подключим.
Затем добавим под полями outlet следующее свойство и Property observer:
// 1
var flight: Flight? {
// 2
didSet {
// 3
guard let flight = flight else { return }
// 4
flightLabel.setText("Flight \(flight.shortNumber)")
routeLabel.setText(flight.route)
boardingLabel.setText("\(flight.number) Boards")
boardTimeLabel.setText(flight.boardsAt)
// 5
if flight.onSchedule {
statusLabel.setText("On Time")
} else {
statusLabel.setText("Delayed")
statusLabel.setTextColor(.red)
}
gateLabel.setText("Gate \(flight.gate)")
seatLabel.setText("Seat \(flight.seat)")
}
}
Вот что здесь происходит на каждом этапе:
- Мы объявили вспомогательное свойство типа
Flight
. Этот класс объявляется в Flight.swift, который является частью общего кода, ранее добавленного в целевую конфигурацию Watch Extension. - Мы добавили Property observer, выполняемый при задании свойства.
- Мы проверяем, что во вспомогательном свойстве содержится действительно рейс, а не
nil
. Мы можем продолжать конфигурировать метки только если знаем, что у нас есть верный экземплярFlight
. - Мы конфигурируем метки с помощью соответствующих свойств
flight
. - Если рейс откладывается, то мы меняем цвет текста в метке на красный.
Теперь нам нужно задать
flight
при первом отображении контроллера. Добавим следующие строки под объявлением flight
:
override func awake(withContext context: Any?) {
super.awake(withContext: context)
flight = Flight.allFlights().first
}
В следующей части мы изменим эту реализацию так, чтобы использовать передаваемый ей контекст, но пока мы просто загружаем все рейсы из общего файла JSON, а затем берём первый из массива.
Примечание:
awake(withContext:)
вызывается после того, как контроллер загружен из storyboard и все его поля Outlet настроены, так что это отличное место для задания flight
.Остался всего один последний шаг, прежде чем мы сможем собрать и запустить приложение, и этот шаг — подключение полей Outlet.
Подключение полей Outlet
Откройте Watch\Interface.storyboard и выберите контроллер интерфейса. С помощью Identity inspector задайте Custom Class\Class значение FlightInterfaceController.
Затем соедините поля Outlet согласно приведённому ниже списку:
flightLabel
: Flight 123routeLabel
: MEL to SFOboardingLabel
: AA123 BoardsboardTimeLabel
: 15:06statusLabel
: On timegateLabel
: Gate 1AseatLabel
: Seat 64A
Прежде чем запустить приложение, нам осталось сделать только одно. Пример приложения, которое мы разрабатывали в этом туториале, создавался для 42-миллиметровых Apple Watch, так что нужно убедиться, что настроен правильный симулятор, в противном случае интерфейс может выглядеть немного искажённым. В настоящих приложениях разработчики проверяют работу интерфейсов на всех размерах часов, но это не относится к теме этого туториала.
Откройте меню схемы Watch и выберите один из симуляторов 42-миллиметровых часов:
Выполните сборку и запустите приложение. После завершения загрузки симулятора вы должны увидеть правильное расположение интерфейса и розовый логотип Air Aber. Объект
Flight
генерирует случайные значения для времени посадки и номера места, поэтому здесь вы увидите другие значения:Примечание: если вы получите сообщение о сбое установки, то можно или попробовать повторить процесс в Xcode, или вручную установить приложение в симулятор часов. Для этого нужно открыть приложение Watch в симуляторе iOS, коснуться AirAber, а затем переключить Show App on Apple Watch на On. После этого можно вернуться в симулятор часов, коснуться Digital Crown для перехода к основному экрану, а затем коснуться значка AirAber для запуска приложения.
Поздравляю! Вы закончили реализацию вашего первого интерфейса WatchKit interface, и он работает в симуляторе часов с реальными данными — отличная работа!
Вот готовый пример того, что мы делали в этой части туториала.
Часть 2: таблицы
В первой части туториала мы узнали об основах разработки под watchOS 4, создав наш первый контроллер интерфейса.
Во второй части серии мы добавим таблицу, чтобы приложение могло отображать список рейсов.
В процессе работы вы узнаете:
- Как добавить новый контроллер интерфейса, добавить к нему таблицу и создать прототип из строк.
- Как создать подкласс класса
WKInterfaceController
, чтобы заполнить таблицу, настроить строки и обрабатывать выбор. - Как сделать контроллер интерфейса модальным и передавать ему данные для отображения.
Приступаем к работе
Откройте Watch\Interface.storyboard и перетащите ещё один interface controller из Object Library на the storyboard, слева от уже имеющегося контроллера Flight.
Выбрав новый контроллер интерфейса, откройте Attributes inspector и внесите следующие изменения:
- Задайте Identifier значение Schedule.
- В Title впишите Air Aber.
- Поставьте флажок Is Initial Controller.
- Убедитесь, что поставлен флажок Activity Indicator On Load.
Как и в контроллере Flight, мы задаём идентификатор, чтобы можно было обращаться к этому контроллеру интерфейса в коде. Это настоящий начальный контроллер для приложения Watch, поэтому нужно задать ему заголовок и поставить флажок. Этот контроллер загружает таблицу из какого-то источника данных, поэтому мы показываем индикатор активности.
Теперь перейдём к интерфейсу: перетащите table из Object Library на новый контроллер интерфейса:
Выберите Table Row Controller в Document outline:
Используйте Attributes inspector для задания Identifier значения FlightRow. Идентификатор также является типом строки, когда мы сообщаем таблице, экземпляры каких строк нужно создать, поэтому важно задать ему название.
Создание интерфейса строки
Строка таблицы — это на самом деле группа, поэтому можно задавать ей конфигурацию любой сложности.
Наша первая задача — внести два изменения в группу конфигурации по умолчанию. В Document outline выберите группу внутри строки таблицы, затем используйте Attributes inspector для присвоения Spacing значения 6, а Height — Size To Fit Content.
По умолчанию строки таблиц имеют стандартную фиксированную высоту. Однако чаще всего нам требуется, чтобы строки отображали все добавляемые в них объекты интерфейса, поэтому всегда стоит изменять атрибут Height таким образом.
Затем перетащите separator из Object Library в группу строки таблицы. Мы на самом деле не будем ничего разделять, а просто придадим строке таблицы небольшой визуальный штрих. Выбрав разделитель, используйте Attributes inspector для внесения следующих изменений:
- Присвойте Color значение #FA114F (ранее использованный розовый цвет Air Aber).
- Задайте Vertical Alignment значение Center.
- Задайте Height значение Relative to Container.
- Задайте Adjustment значение –4.
Инспектор теперь должен выглядеть вот так:
Строка таблицы внезапно выросла и заполнила весь экран! Но мы исправим это в процессе настройки конфигурации строки.
Перетащите group из Object Library на строку таблицы, справа от разделителя. При выбранной группе измените в Attributes inspector следующие атрибуты:
- Задайте Layout значение Vertical.
- Задайте Spacing значение 0.
- Задайте Width значение Size To Fit Content.
Вы уже наверно заметили, что мы часто манипулируем атрибутом Spacing; он просто сужает пространство между объектами интерфейса группы, и делает всё немного чётче на маленьком экране.
Перетащите ещё одну group в только что добавленную группу и внесите следующие изменения:
- Задайте Spacing значение 4.
- Выберите для Height опцию Fixed со значением 32.
Теперь строка таблицы вернулась к нормальной высоте!
Затем добавьте в эту новую группу label и image. Мы настроим метку, а потом скопируем и будем обновлять её для отображения начальной и конечной точки каждого рейса.
Теперь нам нужно что-нибудь поместить в изображение. Скачайте это изображение и добавьте его в Watch\Assets.xcassets. При этом будет создан новый набор изображений Plane, в котором настоящее изображение будет занимать слот 2x:
Мы хотим придать этому изображению розовый оттенок Air Aber, поэтому выберем изображение, затем используем Attributes inspector, чтобы задать Render As значение Template Image.
Повторно откроем Watch\Interface.storyboard и выберем изображение в Document outline. С помощью Attributes inspector внесём следующие изменения:
- Зададим Image значение Plane.
- Зададим Tint значение #FA114F.
- Зададим Horizontal и Vertical Alignment значение Center.
- Выберем для Width опцию Fixed со значеним 24.
- Выберем для Height опцию Fixed со значением 20.
Выберем метку и зададим её полю Text значение MEL. Затем изменим её Font на System со стилем Semibold и размером 20. Наконец, зададим Vertical Alignment значение Center.
Скопируем метку, а затем вставим её справа от изображения. Изменим её текст на SFO, а Horizontal Alignment на Right. Строка таблицы теперь должна выглядеть следующим образом:
Примечание: при вставке копии метки она может упорно прилипать к левой части изображения вне зависимости от её положения в Document outline. Но если задать выравнивание по горизонтали вправо, то она переместится на место.
Иерархия объектов интерфейса должна теперь быть такойна такую:
Мы почти закончили с интерфейсом строки таблицы, осталось добавить только номер и состояние рейса.
Перетащите ещё одну group из Object Library в строку таблицы, чтобы она оказалась по соседству с группой, содержащей метки отправления и прибытия:
Продолжив создавать этот интерфейс, вы увидите и другие примеры того, как можно использовать встроенные группы со смешанными конфигурациями для создания сложных конфигураций. Кому нужен этот Auto Layout?!
Перетащите две метки в эту новую горизонтальную группу. Используйте Attributes inspector для внесения изменений в левую метку:
- Задайте Text значение AA123.
- Задайте Text Color значение Light Gray Color.
- Задайте Font значение Caption 2.
- Задайте Vertical Alignment значение Bottom.
Далее внесём изменения в правую метку:
- Задайте Text значение On time.
- Задайте Text Color значение #04DE71.
- Задайте Font значение Caption 2.
- Задайте Horizontal Alignment значение Right.
- Задайте Vertical Alignment значение Bottom.
После внесения этих последних изменений законченная строка таблицы должна выглядеть так:
Настроив таблицу в Interface Builder, мы можем начать заполнять её какими-нибудь данными.
Заполнение таблицы
Первое, что нужно сделать — создать подкласс класса
WKInterfaceController
для управления таблицей.Нажмите правой кнопкой на группу Watch Extension в Project navigator и выберите New File…. В появившемся диалоговом окне выберите watchOS\Source\WatchKit Class и нажмите Next. Назовите новый класс ScheduleInterfaceController. Убедитесь, что он является подклассом WKInterfaceController, и что для Language задано значение Swift:
Нажмите Next, затем Create.
Когда новый файл откроется в редакторе кода, удалите три пустых заготовки методов, чтобы остались только операторы import и определение класса.
Откройте заново Watch\Interface.storyboard и выберите новый контроллер интерфейса. В Identity inspector замените Custom Class\Class на ScheduleInterfaceController:
Оставив выбранным контроллер интерфейса, откройте Assistant editor и убедитесь, что там отображается
ScheduleInterfaceController
. Далее зажмите Control и перетащите из Table в Document outline внутрь определения класса ScheduleInterfaceController
для создания поля Outlet:Назовите Outlet flightsTable, убедитесь, что ему задан тип WKInterfaceTable и нажмите Connect.
Теперь, когда мы задали собственный класс и создали Outlet в таблицу, настало время её заполнить!
Закройте Assistant editor, откройте ScheduleInterfaceController.swift и добавьте следующую строку прямо после Outlet:
var flights = Flight.allFlights()
Здесь мы просто добавляем свойство, содержащее всю информацию о рейсах как массив экземпляров
Flight
.Затем добавим следующую реализацию
awake(withContext:)
:override func awake(withContext context: Any?) {
super.awake(withContext: context)
flightsTable.setNumberOfRows(flights.count, withRowType: "FlightRow")
}
Здесь мы сообщаем таблице, что нужно создать экземпляр строки, которую мы только что создали в Interface Builder для каждого рейса в
flights
. Количество строк равно размеру массива, а тип строки — это идентификатор, который мы задали в storyboard.Выполните сборку приложения и запустите его. Вы увидите таблицу, заполненную несколькими строками, рядом с каждой из которых расположено розовое изображение самолёта Air Aber:
Но постойте! Заголовок имеет тёмно-серый цвет вместо ярко-розового корпоративного цвета Air Aber. Сейчас мы это исправим.
Откроем Watch\Interface.storyboard, выберем контроллер интерфейса Air Aber. В File inspector изменим Global Tint на #FA114F.
Выполните сборку и запустите приложение. Вот так-то лучше!
Но теперь вы заметите, что во всех строках отображается текст-заполнитель, заданный нами в Interface Builder. Сейчас мы это исправим, добавив контроллер строк, настраивающий метки для каждой строки.
Добавление контроллера строк
Таблицы WatchKit намного проще, чем таблицы iOS: здесь нет источников данных и delegate! Достаточно просто создать класс контроллера строк, который, несмотря на своё название, является подклассом класса
NSObject
.Нажмите правой кнопкой на группу Watch Extension в Project navigator и выберите New File…. В открывшемся диалоговом окре выберите watchOS\Source\WatchKit Class и нажмите Next. Назовите новый класс FlightRowController. Убедитесь, что он является подклассом NSObject, — а не WKInterfaceController! — и что Language имеет значение Swift:
Нажмите Next, а затем Create.
Когда в редакторе кода откроется новый файл, добавьте следующие строки сверху класса:
@IBOutlet var separator: WKInterfaceSeparator!
@IBOutlet var originLabel: WKInterfaceLabel!
@IBOutlet var destinationLabel: WKInterfaceLabel!
@IBOutlet var flightNumberLabel: WKInterfaceLabel!
@IBOutlet var statusLabel: WKInterfaceLabel!
@IBOutlet var planeImage: WKInterfaceImage!
Здесь мы просто добавляем Outlet для каждой из меток, добавленных в строку таблицы. Скоро мы их подключим.
Затем добавим следующие свойство и Property observer, прямо под полями Outlet:
// 1
var flight: Flight? {
// 2
didSet {
// 3
guard let flight = flight else { return }
// 4
originLabel.setText(flight.origin)
destinationLabel.setText(flight.destination)
flightNumberLabel.setText(flight.number)
// 5
if flight.onSchedule {
statusLabel.setText("On Time")
} else {
statusLabel.setText("Delayed")
statusLabel.setTextColor(.red)
}
}
}
Вот что происходит на каждом из этапов:
- Мы объявляем вспомогательное свойство типа
Flight
. Помните, что этот класс объявляется в Flight.swift, который является частью общего кода, добавляенного к Watch Extension в предыдущей части туториала. - Добавляем Property observer, выполняемый при задании свойства.
- Выполняем выход, если
flight
имеет значениеnil
: это необязательный шаг, но мы хотим продолжать настройку меток только если у нас есть правильный экземплярFlight
. - Настраиваем метки с помощью соответствующих свойств
flight
. - Если рейс откладывается, то мы меняем цвет текста метки на красный и соответствующим образом обновляем текст.
Завершив настройку контроллера строк, мы должны обновить строку таблицы, чтобы её использовать.
Откройте Watch\Interface.storyboard и выберите FlightRow в Document outline. С помощью Identity inspector задайте Custom Class\Class значение FlightRowController.
В Document outline откройте все группы в FlightRow, затем нажмите правой кнопкой на FlightRow, чтобы вызвать всплывающее окно полей Outlet и действий:
Можно перетащить это всплывающее окно вправо, чтобы видеть все объекты в FlightRow.
Сначала подключим
planeImage
к изображению в строке таблицы, а separator
к разделителю. Затем подключим оставшиеся поля Outlet в соответствии со следующим списком:destinationLabel
: SFOflightNumberLabel
: AA123originLabel
: MELstatusLabel
: On time
Последний шаг — это обновление
ScheduleInterfaceController
, чтобы он передавал экземпляр Flight
каждому контроллеру строк в таблице.Откройте ScheduleInterfaceController.swift и добавьте следующие строки под
awakeWithContext(_:)
:
for index in 0..<flightsTable.numberOfRows {
guard let controller = flightsTable.rowController(at: index) as? FlightRowController else { continue }
controller.flight = flights[index]
}
Здесь мы проходим в цикле
for
по каждой строке в таблице и запрашиваем у таблицы контроллер строк с соответствующим индексом. Если мы правильно запрашиваем контроллер, то получаем экземпляр FlightRowController
. Затем мы задаём controller.flight
соответствующий элемент flight
в массиве flights
. Это приводит к выполнению наблюдателя didSet
в FlightRowController
и настраивает все метки в строке таблицы.Настало время увидеть результаты наших трудов: выполним сборку и запустим приложение. Вы увидите, что строки таблиц теперь заполнены правильными сведениями о рейсах:
И теперь последняя задача в этой части туториала: когда пользователь касается строки таблицы,
ScheduleInterfaceController
должен передавать соответствующий рейс в качестве контекста в интерфейс сведений о рейсах, который мы создали в предыдущей части туториала, и отображать его.Соответствие выбранной строке
Первое, что нужно сделать — переопределить метод
WKInterfaceTable
, выполняющий обработку выбора строки таблицы.Добавим в
ScheduleInterfaceController
следующие строки:override func table(_ table: WKInterfaceTable, didSelectRowAt rowIndex: Int) {
let flight = flights[rowIndex]
presentController(withName: "Flight", context: flight)
}
Здесь мы получаем из
flights
соответствующий рейс, используя индекс строки, переданный в этот метод. Затем мы отображаем интерфейс сведений о рейсе, в качестве context
передавая flight
. Помните, что имя, передаваемое в presentController(withName:context:)
— это идентификатор, заданный нами в storyboard в предыдущей части туториала.Теперь, как и обещано в первой части, мы изменим
FlightInterfaceController
, чтобы он использовать context
для настройки своего интерфейса.Откройте FlightInterfaceController.swift и найдите
awake(withContext:)
. Найдите эту строку:flight = Flight.allFlights().first
Замените её на следующие строки:
if let flight = context as? Flight {
self.flight = flight
}
Здесь мы пытаемся преобразовать
context
как экземпляр Flight
. Если это удаётся, мы используем его для задания self.flight
, который в свою очередь приводит к выполнению Property observer и настраивает интерфейс.В последний раз в этой части выполним сборку и запустим приложение. Коснитесь строки таблицы и вы увидите, как модально отобразится интерфейс сведений о рейсе с информацией о выбранном рейсе:
Поздравляю! Вы закончили реализацию своей первой таблицы и заполнили её настоящими данными. Отличная работа!
Вот готовый пример проекта, который мы пока сделали в этом туториале.
Часть 3: анимации
В третьей части туториала мы узнаем, как пользоваться анимациями watchOS 4 на примере нового интерфейса регистрации на рейс в нашем приложении.
В процессе работы вы узнаете:
- Как создавать анимации на основе изображений.
- Как использовать API анимации watchOS 4.
Приступаем к работе
Откройте Watch\Interface.storyboard и перетащите interface controller из Object Library на холст storyboard. Выбрав контроллер интерфейса, откройте Attributes inspector и введите для Identifier значение CheckIn. Мы сделали это для того, чтобы можно было определить контроллер интерфейса из
ScheduleInterfaceController
.Затем перетащите из Object Library group на новый контроллер интерфейса. В Attributes inspector внесите следующие изменения:
- Задайте Layout значение Vertical.
- Задайте Mode значение Center.
- Задайте Horizontal Alignment значение Center.
- Задайте Height значение Relative to Container.
Теперь контроллер интерфейса должен выглядеть так:
Теперь мы создадим ту же группу из метки-изображения-метки, которую мы создавали для строки таблицы.
Перетащите ещё одну group на созданную группу и внесите Attributes inspector следующие изменения:
- Задайте Spacing значение 4.
- Задайте Horizontal Alignment значение Center.
- Задайте Width значение Size To Fit Content.
- Выберите Height опцию Fixed со значением 30 (немного короче строки таблицы).
Добавьте в эту новую группу label и image. Мы настроим метку, а затем копируем и будем обновлять её чтобы отображать начальную и конечную точку каждого рейса.
Выберите изображение, или в storyboard, или в Document outline. В Attributes inspector внесите следующие изменения:
- Задайте Image значение Plane.
- Задайте Tint значение #FA114F (снова наш розовый цвет!).
- Задайте Vertical Alignment значение Center.
- Выберите Width опцию Fixed со значением 24.
- Выберите Height опцию Fixed со значением 20.
Как и раньше, изображение не меняет оттенок, поэтому его не видно на чёрном фоне контроллера интерфейса. Но мы знаем, что оно там.
Выберите метку и задайте Text значение MEL. Затем измените её Font на System со стилем Semibold и размером 20. Наконец, задайте Vertical alignment значение Center, и проверьте, что Width и Height имеют значение Size To Fit Content.
Скопируйте метку и вставьте её рядом с изображением. Измените её текст на SFO, а её Horizontal Alignment на Right. Теперь контроллер интерфейса должен выглядеть вот так:
Теперь настало время добавить огромную кнопку регистрации!
Добавление кнопки регистрации
Перетащите button из Object Library на контроллер интерфейса и убедитесь, что она является соседним элементом с группой, содержащей метки начальной и конечной точек рейса:
Кнопки в WatchKit невероятно гибкие: можно использовать их с внешним видом по умолчанию (такими, как сейчас выглядит добавленная нами), или превратить их в группу компоновки и добавить другие объекты интерфейса для настройки их внешнего вида. Именно этим мы и займёмся.
Выберите кнопку и внесите в Attributes inspector следующие изменения:
- Задайте Content значение Group.
- Задайте Horizontal Alignment значение Center.
- Задайте Vertical Alignment значение Center.
Теперь контроллер интерфейса должен выглядеть так:
Вы наверно заметили, что когда мы изменили атрибут Content кнопки, в Document outline появилась новая group:
Именно её мы будем использовать в качестве фона нашей собственной кнопки регистрации. Выберите эту группу и внесите в Attributes inspector следующие измененияt:
- Задайте Color значение #FA114F.
- Задайте Radius значение 39.
- Выберите Width опцию Fixed со значением 78.
- Выберите Height значение Fixed со значением 78.
Контроллер интерфейса теперь должен выглядеть следующим образом:
Наша кнопка регистрации уже начинает принимать свой вид. Не хватает только метки, поэтому сейчас мы её добавим.
Перетащите label из Object Library в принадлежащую кнопке группу, а затем выберите её. Снова внесите Attributes inspector в следующие изменения:
- Задайте Text значение Check In.
- Задайте Font значение System со стилем Semibold и размером 16.
- Задайте Horizontal Alignment значение Center.
- Задайте Vertical Alignment значение Center.
Готовый контроллер интерфейса регистрации должен выглядеть вот так:
Закончив с интерфейсом, мы можем перейти к созданию подкласса
WKInterfaceController
, управляющего этим контроллером и изменить ScheduleInterfaceController
, чтобы его отображать.Создание контроллера
Нажмите правой кнопкой на группу Watch Extension в Project navigator и выберите New File…. В появившемся диалоговом окне выберите watchOS\Source\WatchKit Class и нажмите Next. Назовите новый класс CheckInInterfaceController и убедитесь, что он является подклассом WKInterfaceController, и что для Language выбрано Swift:
Нажмите Next, а затем Create.
Когда в редакторе кода откроется новый файл, удалите три пустых заготовки методов, чтобы у вас остались только операторы import и определение класса.
Затем добавьте сверху класса следующие строки:
@IBOutlet var backgroundGroup: WKInterfaceGroup!
@IBOutlet var originLabel: WKInterfaceLabel!
@IBOutlet var destinationLabel: WKInterfaceLabel!
Здесь мы просто добавляем поля Outlet для самой внешней группы и двух меток интерфейса, которые мы только что создали. Скоро мы всех их подключим.
Затем добавьте под полями Outlet следующее:
var flight: Flight? {
didSet {
guard let flight = flight else { return }
originLabel.setText(flight.origin)
destinationLabel.setText(flight.destination)
}
}
Здесь мы добавили вспомогательное свойство типа
Flight
, которое включает в себя Property observer. При выполнении наблюдателя он пытается снять обёртку с flight
, и если это удаётся, использует flight
для настройки двух меток. Всё это уже нам знакомо.Теперь нам просто нужно задать
flight
при наличии контроллера. Добавим в CheckInInterfaceController
следующие строки:override func awake(withContext context: Any?) {
super.awake(withContext: context)
if let flight = context as? Flight {
self.flight = flight
}
}
С этим вы тоже уже знакомы. Мы пытаемся снять обёртку и преобразовать
context
в экземпляр Flight
. Если это удаётся, то мы используем его для задания self.flight
, который в свою очередь выполняет Property observer, настраивающий интерфейс.Наконец, добавим прямо под
awake(withContext:)
следующее действие:@IBAction func checkInButtonTapped() {
// 1
let duration = 0.35
let delay = DispatchTime.now() + (duration + 0.15)
// 2
backgroundGroup.setBackgroundImageNamed("Progress")
// 3
backgroundGroup.startAnimatingWithImages(in: NSRange(location: 0, length: 10),
duration: duration,
repeatCount: 1)
// 4
DispatchQueue.main.asyncAfter(deadline: delay) { [weak self] in
// 5
self?.flight?.checkedIn = true
self?.dismiss()
}
}
Вот что здесь происходит на каждом этапе:
- Мы создаём две константы: одну для длительности анимации, другую — для задержки, после которой контроллер будет отключен.
delay
— это неDouble
, а экземплярDispatchTime
, потому что мы будем использовать её с Grand Central Dispatch. - Мы загружаем последовательность изображений под названием Progress и устанавливаем его в качестве фонового изображения
backgroundGroup
. Группы компоновки подчиняютсяWKImageAnimatable
, что позволяет нам использовать их для воспроизведения анимированных последовательностей изображений. - Мы начинаем воспроизведение последовательности изображений. Передаваемый интервал охватывает всю последовательность, а значение
repeatCount
, равное 1, означает, что анимация будет воспоизводиться только один раз. - В WatchKit нет обработчиков завершения, так что мы используем Grand Central Dispatch для выполнения закрытия после заданной задержки.
- После закрытия мы помечаем, что на рейс
flight
выполнена регистрация, а затем отключаем контроллер.
Теперь нам достаточно добавить к проекту изображения и подключить Outlet и одно действие.
Скачайте этот zip-файл, распакуйте его и перетащите папку в Watch\Assets.xcassets.
Перетащите именно папку, а не её содержимое. При этом будет создана группа в каталоге ресурсов под названием Progress, содержащая несколько наборов изображений:
Добавив изображения, мы можем назначить поля Outlet и действие кнопки.
Откройте Watch\Interface.storyboard и выберите наш новый контроллер интерфейса. В Identity inspector замените Custom Class\Class на CheckInInterfaceController:
Затем в Document outline нажмите правой кнопкой на CheckIn для вызова всплывающего окна полей Outlet и действий. Подключите
backgroundGroup
к самой внешней группе в контроллере интерфейса:На холсте storyboard подключите
destinationLabel
к метке, содержащей SFO и подключите originLabel
к метке, содержащей MEL.Затем подключите
checkInButtonTapped
к большой круглой розовой кнопке:Перед тем, как выполнить сборку и запустить приложение, нам нужно внести последнее изменение — отобразить контроллер интерфейса.
Отображение контроллера
Откройте ScheduleInterfaceController.swift, найдите
table(_:didSelectRowAt:)
и замените его содержимое на следующие строки:let flight = flights[rowIndex]
let controllers = ["Flight", "CheckIn"]
presentController(withNames: controllers, contexts: [flight, flight])
Здесь мы получаем соответствующий рейс из
flights
с индексом rowIndex
, создаём массив, содержащий идентификаторы двух контроллеров интерфейса, которые мы хотим отобразить, а затем отобразить их, передав обоим flight
в качестве context
.Выполните сборку и запустите приложение. Коснитесь рейса, и вы увидите пару отображённых контроллеров интерфейса. Проведите влево, чтобы открыть контроллер регистрации, а затем коснитесь кнопки для запуска анимации и прохождения регистрации:
Это выглядит отлично само по себе, но лучше, чтобы рейсы, на которые выполнена регистрация, подсвечивались в контроллере интерфейса расписания. Этим мы займёмся в следующем, последнем, разделе.
Подсвечивание рейса
Откройте FlightRowController.swift и добавьте в него следующий метод:
func updateForCheckIn() {
let color = UIColor(red: 90/255, green: 200/255, blue: 250/255, alpha: 1)
planeImage.setTintColor(color)
separator.setColor(color)
}
Здесь мы создаём экземпляр
UIColor
, затем используем его для задания цвета оттенка и цвета соответственно planeImage
и separator
. Этот метод будет вызываться при закрытии анимации, поэтому смена цвета будет красиво анимирована.Затем откроем ScheduleInterfaceController.swift и добавим под
flights
следующее свойство:var selectedIndex = 0
Мы используем его для того, чтобы запомнить, какая строка таблицы выбрана при отображении двух контроллеров интерфейса. Теперь достаточно задать его при выборе строки таблицы. Добавьте следующую строку прямо над вызовом
presentController(withNames:contexts:)
в table(_:didSelectRowAt:)
:selectedIndex = rowIndex
Эта строка задаёт
selectedIndex
значение индекса выбранной строки таблицы.Наконец, добавим следующие строки в
ScheduleInterfaceController
, прямо под awake(withContext:)
:override func didAppear() {
super.didAppear()
// 1
guard flights[selectedIndex].checkedIn,
let controller = flightsTable.rowController(at: selectedIndex) as? FlightRowController else {
return
}
// 2
animate(withDuration: 0.35) {
// 3
controller.updateForCheckIn()
}
}
Вот что происходит на каждом этапе:
- Проверяем, выполнена ли регистрация на выбранный рейс. Если это так, то мы пытаемся преобразовать контроллер строки с соответствующим индексом в таблице в экземпляр
FlightRowController
. - Если это удаётся, то используем API анимации на
WKInterfaceController
для выполнения закрытия через 0,35 секунды. - При закрытии мы вызываем метод, только что добавленный в
FlightRowController
, который изменяет цвет изображения и разделителя в этой строке таблицы, и обеспечивает пользователям наглядную обратную связь, сообщающую о том, что регистрация выполнена.
Выполните сборку и запустите приложение. Выполните те же самые действия, что и раньше, чтобы пройти регистрацию на рейс, и вы увидите, что вы вернулись к контроллеру интерфейса расписания, а цвета изображения и разделителя в соответствующей строке таблицы постепенно сменились на новый цвет:
Поздравляю! Вы закончили реализацию своего самого первого набора анимаций WatchKit.
Вот готовый пример проекта, который мы сделали в этом туториале.
В этой части мы узнали, как создавать два разных вида анимаций WatchKit. В первом использовалась анимированная последовательность изображений, а во втором для
WKInterfaceController
применялся API анимации.
LeqWiser
Воу-воу, спасибо за гайд
Как раз думал на днях попробовать в watchOS.