- PVSM.RU - https://www.pvsm.ru -

В этом туториале мы создадим простое, но полнофункциональное приложение под watchOS 4. А конкретнее, мы будем работать над приложением придуманной авиакомпании Air Aber для Apple Watch.
Из этого туториала вы узнаете:
WKInterfaceController и соединить всё вместе.WKInterfaceController, чтобы заполнить таблицу, настроить строки и обрабатывать выбор.Из этой части вы узнаете:
WKInterfaceController и соединить всё вместе.Начнём со скачивания начального проекта [1] для этого туториала.
Откройте его в Xcode, выполните сборку и запустите. Вы должны увидеть пустой белый экран:

Как видно, пока в этом проекте довольно мало всего: в нём есть несколько нужных вспомогательных файлов, и на этом всё. Мы скоро решим эту проблему!
Выберите FileNewTarget…. В открывшемся диалоговом окне выберите watchOSApplicationWatchKit 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 [2]?
Разверните группу Shared в Project navigator и выберите Flights.json. Затем найдите раздел Target Membership в File inspector и поставьте флажок Watch Extension:

Теперь файл будет включён и в AirAber, и в Watch Extension.
Повторите процесс для другого файла в группе Shared — Flight.swift.
И закончив с этим, мы наконец можем приступить к созданию интерфейса сведений о рейсах!
Откройте WatchInterface.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 и убедитесь, что он стал дочерним элементом группы, а не соседним:

Теперь нам нужно отображаемое изображение. Скачайте этот логотип [3] и перетащите в WatchAssets.xcassets. При этом будет создан новый набор изображений, в котором наша картинка будет занимать слот 2x:

Я хочу придать этому изображению оттенок корпоративного цвета Air Aber, поэтому выберем изображение, затем изменим значение Render As на Template Image.

Снова откройте WatchInterface.storyboard и выберите image. С помощью Attributes inspector внесите следующие изменения:
Attributes inspector должен выглядеть следующим образом:

Не волнуйтесь, если не видите логотип: похоже, Xcode не меняет оттенок шаблонов изображений во время разработки! Поверьте, он будет ярко-розовым после сборки и запуска приложения.
Далее перетащите в созданную группу ещё одну group и проверьте, что она появилась справа от изображения, а затем воспользуйтесь Attributes inspector, чтобы изменить её Layout на Vertical. Также измените Spacing на Custom, а 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.
Затем перетащите в эту новую группу три 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…. В открывшемся диалоговом окне выберите watchOSSourceWatchKit 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.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.
Откройте WatchInterface.storyboard и выберите контроллер интерфейса. С помощью Identity inspector задайте Custom ClassClass значение 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, и он работает в симуляторе часов с реальными данными — отличная работа!
Вот готовый пример [4] того, что мы делали в этой части туториала.
В первой части туториала мы узнали об основах разработки под watchOS 4, создав наш первый контроллер интерфейса.
Во второй части серии мы добавим таблицу, чтобы приложение могло отображать список рейсов.
В процессе работы вы узнаете:
WKInterfaceController, чтобы заполнить таблицу, настроить строки и обрабатывать выбор.Откройте WatchInterface.storyboard и перетащите ещё один interface controller из Object Library на the storyboard, слева от уже имеющегося контроллера Flight.
Выбрав новый контроллер интерфейса, откройте Attributes inspector и внесите следующие изменения:
Как и в контроллере 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 для внесения следующих изменений:
Инспектор теперь должен выглядеть вот так:

Строка таблицы внезапно выросла и заполнила весь экран! Но мы исправим это в процессе настройки конфигурации строки.
Перетащите group из Object Library на строку таблицы, справа от разделителя. При выбранной группе измените в Attributes inspector следующие атрибуты:
Вы уже наверно заметили, что мы часто манипулируем атрибутом Spacing; он просто сужает пространство между объектами интерфейса группы, и делает всё немного чётче на маленьком экране.
Перетащите ещё одну group в только что добавленную группу и внесите следующие изменения:
Теперь строка таблицы вернулась к нормальной высоте!
Затем добавьте в эту новую группу label и image. Мы настроим метку, а потом скопируем и будем обновлять её для отображения начальной и конечной точки каждого рейса.
Теперь нам нужно что-нибудь поместить в изображение. Скачайте это изображение [5] и добавьте его в WatchAssets.xcassets. При этом будет создан новый набор изображений Plane, в котором настоящее изображение будет занимать слот 2x:

Мы хотим придать этому изображению розовый оттенок Air Aber, поэтому выберем изображение, затем используем Attributes inspector, чтобы задать Render As значение Template Image.
Повторно откроем WatchInterface.storyboard и выберем изображение в Document outline. С помощью Attributes inspector внесём следующие изменения:
Выберем метку и зададим её полю Text значение MEL. Затем изменим её Font на System со стилем Semibold и размером 20. Наконец, зададим Vertical Alignment значение Center.
Скопируем метку, а затем вставим её справа от изображения. Изменим её текст на SFO, а Horizontal Alignment на Right. Строка таблицы теперь должна выглядеть следующим образом:

Примечание: при вставке копии метки она может упорно прилипать к левой части изображения вне зависимости от её положения в Document outline. Но если задать выравнивание по горизонтали вправо, то она переместится на место.
Иерархия объектов интерфейса должна теперь быть такойна такую:

Мы почти закончили с интерфейсом строки таблицы, осталось добавить только номер и состояние рейса.
Перетащите ещё одну group из Object Library в строку таблицы, чтобы она оказалась по соседству с группой, содержащей метки отправления и прибытия:

Продолжив создавать этот интерфейс, вы увидите и другие примеры того, как можно использовать встроенные группы со смешанными конфигурациями для создания сложных конфигураций. Кому нужен этот Auto Layout?!
Перетащите две метки в эту новую горизонтальную группу. Используйте Attributes inspector для внесения изменений в левую метку:
Далее внесём изменения в правую метку:
После внесения этих последних изменений законченная строка таблицы должна выглядеть так:

Настроив таблицу в Interface Builder, мы можем начать заполнять её какими-нибудь данными.
Первое, что нужно сделать — создать подкласс класса WKInterfaceController для управления таблицей.
Нажмите правой кнопкой на группу Watch Extension в Project navigator и выберите New File…. В появившемся диалоговом окне выберите watchOSSourceWatchKit Class и нажмите Next. Назовите новый класс ScheduleInterfaceController. Убедитесь, что он является подклассом WKInterfaceController, и что для Language задано значение Swift:

Нажмите Next, затем Create.
Когда новый файл откроется в редакторе кода, удалите три пустых заготовки методов, чтобы остались только операторы import и определение класса.
Откройте заново WatchInterface.storyboard и выберите новый контроллер интерфейса. В Identity inspector замените Custom ClassClass на 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. Сейчас мы это исправим.
Откроем WatchInterface.storyboard, выберем контроллер интерфейса Air Aber. В File inspector изменим Global Tint на #FA114F.

Выполните сборку и запустите приложение. Вот так-то лучше!

Но теперь вы заметите, что во всех строках отображается текст-заполнитель, заданный нами в Interface Builder. Сейчас мы это исправим, добавив контроллер строк, настраивающий метки для каждой строки.
Таблицы WatchKit намного проще, чем таблицы iOS: здесь нет источников данных и delegate! Достаточно просто создать класс контроллера строк, который, несмотря на своё название, является подклассом класса NSObject.
Нажмите правой кнопкой на группу Watch Extension в Project navigator и выберите New File…. В открывшемся диалоговом окре выберите watchOSSourceWatchKit 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 в предыдущей части туториала.flight имеет значение nil: это необязательный шаг, но мы хотим продолжать настройку меток только если у нас есть правильный экземпляр Flight.flight.Завершив настройку контроллера строк, мы должны обновить строку таблицы, чтобы её использовать.
Откройте WatchInterface.storyboard и выберите FlightRow в Document outline. С помощью Identity inspector задайте Custom ClassClass значение 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 и настраивает интерфейс.
В последний раз в этой части выполним сборку и запустим приложение. Коснитесь строки таблицы и вы увидите, как модально отобразится интерфейс сведений о рейсе с информацией о выбранном рейсе:

Поздравляю! Вы закончили реализацию своей первой таблицы и заполнили её настоящими данными. Отличная работа!
Вот готовый пример проекта [6], который мы пока сделали в этом туториале.
В третьей части туториала мы узнаем, как пользоваться анимациями watchOS 4 на примере нового интерфейса регистрации на рейс в нашем приложении.
В процессе работы вы узнаете:
Откройте WatchInterface.storyboard и перетащите interface controller из Object Library на холст storyboard. Выбрав контроллер интерфейса, откройте Attributes inspector и введите для Identifier значение CheckIn. Мы сделали это для того, чтобы можно было определить контроллер интерфейса из ScheduleInterfaceController.
Затем перетащите из Object Library group на новый контроллер интерфейса. В Attributes inspector внесите следующие изменения:
Теперь контроллер интерфейса должен выглядеть так:

Теперь мы создадим ту же группу из метки-изображения-метки, которую мы создавали для строки таблицы.
Перетащите ещё одну group на созданную группу и внесите Attributes inspector следующие изменения:
Добавьте в эту новую группу label и image. Мы настроим метку, а затем копируем и будем обновлять её чтобы отображать начальную и конечную точку каждого рейса.
Выберите изображение, или в storyboard, или в Document outline. В Attributes inspector внесите следующие изменения:
Как и раньше, изображение не меняет оттенок, поэтому его не видно на чёрном фоне контроллера интерфейса. Но мы знаем, что оно там.
Выберите метку и задайте 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 кнопки, в Document outline появилась новая group:

Именно её мы будем использовать в качестве фона нашей собственной кнопки регистрации. Выберите эту группу и внесите в Attributes inspector следующие измененияt:
Контроллер интерфейса теперь должен выглядеть следующим образом:

Наша кнопка регистрации уже начинает принимать свой вид. Не хватает только метки, поэтому сейчас мы её добавим.
Перетащите label из Object Library в принадлежащую кнопке группу, а затем выберите её. Снова внесите Attributes inspector в следующие изменения:
Готовый контроллер интерфейса регистрации должен выглядеть вот так:

Закончив с интерфейсом, мы можем перейти к созданию подкласса WKInterfaceController, управляющего этим контроллером и изменить ScheduleInterfaceController, чтобы его отображать.
Нажмите правой кнопкой на группу Watch Extension в Project navigator и выберите New File…. В появившемся диалоговом окне выберите watchOSSourceWatchKit 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.backgroundGroup. Группы компоновки подчиняются WKImageAnimatable, что позволяет нам использовать их для воспроизведения анимированных последовательностей изображений.repeatCount, равное 1, означает, что анимация будет воспоизводиться только один раз.flight выполнена регистрация, а затем отключаем контроллер.Теперь нам достаточно добавить к проекту изображения и подключить Outlet и одно действие.
Скачайте этот zip-файл [7], распакуйте его и перетащите папку в WatchAssets.xcassets.
Перетащите именно папку, а не её содержимое. При этом будет создана группа в каталоге ресурсов под названием Progress, содержащая несколько наборов изображений:

Добавив изображения, мы можем назначить поля Outlet и действие кнопки.
Откройте WatchInterface.storyboard и выберите наш новый контроллер интерфейса. В Identity inspector замените Custom ClassClass на 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.WKInterfaceController для выполнения закрытия через 0,35 секунды.FlightRowController, который изменяет цвет изображения и разделителя в этой строке таблицы, и обеспечивает пользователям наглядную обратную связь, сообщающую о том, что регистрация выполнена.Выполните сборку и запустите приложение. Выполните те же самые действия, что и раньше, чтобы пройти регистрацию на рейс, и вы увидите, что вы вернулись к контроллеру интерфейса расписания, а цвета изображения и разделителя в соответствующей строке таблицы постепенно сменились на новый цвет:

Поздравляю! Вы закончили реализацию своего самого первого набора анимаций WatchKit.
Вот готовый пример проекта [8], который мы сделали в этом туториале.
В этой части мы узнали, как создавать два разных вида анимаций WatchKit. В первом использовалась анимированная последовательность изображений, а во втором для WKInterfaceController применялся API анимации.
Автор: PatientZero
Источник [9]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/ios/269580
Ссылки в тексте:
[1] начального проекта: https://koenig-media.raywenderlich.com/uploads/2017/11/AirAber-Starter.zip
[2] DRY: https://ru.wikipedia.org/wiki/Don%E2%80%99t_repeat_yourself
[3] этот логотип: https://koenig-media.raywenderlich.com/uploads/2015/10/Logo@2x.png
[4] готовый пример: https://koenig-media.raywenderlich.com/uploads/2017/11/AirAber-Part1.zip
[5] это изображение: https://koenig-media.raywenderlich.com/uploads/2015/10/Plane@2x.png
[6] готовый пример проекта: https://koenig-media.raywenderlich.com/uploads/2017/11/AirAber-Part2.zip
[7] этот zip-файл: https://koenig-media.raywenderlich.com/uploads/2015/10/Progress.zip
[8] готовый пример проекта: https://koenig-media.raywenderlich.com/uploads/2017/11/AirAber-Part3.zip
[9] Источник: https://habrahabr.ru/post/343432/
Нажмите здесь для печати.