[Перевод] Туториал: создаём простое приложение для watchOS 4

image


В этом туториале мы создадим простое, но полнофункциональное приложение под watchOS 4. А конкретнее, мы будем работать над приложением придуманной авиакомпании Air Aber для Apple Watch.

Из этого туториала вы узнаете:

  • Как добавлять целевую сборку watchOS 4 в приложение iOS.
  • Как обмениваться данными между двумя целевыми сборками.
  • Как добавить в Storyboard контроллер интерфейса watchOS и расположить объекты интерфейса.
  • Как создать подкласс WKInterfaceController и соединить всё вместе.
  • Как добавить новый контроллер интерфейса, добавить к нему таблицу и создать прототип из строк.
  • Как создать подкласс класса WKInterfaceController, чтобы заполнить таблицу, настроить строки и обрабатывать выбор.
  • Как сделать контроллер интерфейса модальным и передавать ему данные для отображения.
  • Как создавать анимации на основе изображений.
  • Как использовать API анимации watchOS 4.


Часть 1: начало работы


Из этой части вы узнаете:

  • Как добавлять целевую сборку watchOS 4 в приложение iOS.
  • Как обмениваться данными между двумя целевыми сборками.
  • Как добавить в Storyboard контроллер интерфейса watchOS и расположить объекты интерфейса.
  • Как создать подкласс WKInterfaceController и соединить всё вместе.


Приступаем


Начнём со скачивания начального проекта для этого туториала.

Откройте его в Xcode, выполните сборку и запустите. Вы должны увидеть пустой белый экран:

40760f8b0f86240c7317b19c63225e16.png

Как видно, пока в этом проекте довольно мало всего: в нём есть несколько нужных вспомогательных файлов, и на этом всё. Мы скоро решим эту проблему!

Добавляем приложение WatchKit


Выберите File\New\Target…. В открывшемся диалоговом окне выберите watchOS\Application\WatchKit App, затем нажмите Next:

4575ec4369a810089af2774bec9c715c.png


На следующем экране введите в качестве Product Name слово Watch, убедитесь, что в поле Language выбрано Swift и снимите все установленные флажки. Нажмите на Finish:

c145d517c3a3ff919dda265aa98295ca.png


Система спросит, нужно ли активировать схему «watch» — она нам понадобится, так что выберите Activate:

b2a926c34c668e2950626ce78d646535.png


Поздравляю, вы только что создали ваше первое приложение для Watch! Всё на самом деле так просто.

Можно заметить, что это действие на самом деле создало две целевых сборки, а не одну, и две соответствующие группы в Project navigator. Так получилось, потому что код приложения Watch на самом деле выполняется как Extension (расширение), заключённое внутри приложения Watch, почти так же, как работают Today Extension в iOS.

Разверните группы Watch и Watch Extension в Project navigator, и вы увидите, что storyboard находится в группе Watch, а созданные шаблоном целевой сборки классы находятся в группе Watch Extension:

7acec260618ba3061b9f1354255959e8.png


Вот какого порядка мы будем придерживаться в работе: любой добавляемый код должен храниться внутри группы Watch Extension, и добавляться в целевую сборку Watch Extension, в то время как любые ресурсы или storyboard должны добавляться в группу Watch.

Немного приберёмся


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

Нажмите правой клавишей мыши на InterfaceController.swift в Project navigator и выберите Delete. В диалговом окне выберите Move to Trash, чтобы файл на самом деле был удалён из проекта:

f17116c65b3b58ee0503e8b5352088e5.png


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

Общие данные и код


В начальном проекте есть файл JSON, содержащий всю информацию о рейсах Air Aber, и представляющий эти данные класс моделей. Именно это должно быть общим между целевыми сборками, потому что высока вероятность того, что приложение iOS и приложение Watch будут использовать один класс моделей и данные — помните принцип DRY?

Разверните группу Shared в Project navigator и выберите Flights.json. Затем найдите раздел Target Membership в File inspector и поставьте флажок Watch Extension:

3154f22430019f97f21f0c5cb11f258f.png


Теперь файл будет включён и в 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:

14ad3b55e8232e81ad72c418a5f1b386.png

Так мы задали идентификатор, чтобы можно было обращаться к контроллеру интерфейса в коде. Поставив флажок Is Initial Controller, мы просто даём знать WatchKit, что этот контроллер интерфейса нужно отображать при первом запуске приложения Watch. Этот интерфейс не скачивает никаких данных, поэтому ему не нужно показывать индикатор активности.

Чтобы упростить этот туториал, мы будем создавать схему интерфейса только для часов размером 42 мм. В ваших собственных приложениях нужно будет проверять, правильно ли оно отображется на всех размерах часов. В нижнем левом углу панели storyboard должно быть выбрано View as: Apple Watch 42mm.

42a295be57bb46cc56da8408bfdaa86c.png

Макет приложения Watch полностью отличается от макета iOS. Первое, что вы заметите: можно перемещать или изменять объекты UI, перетаскивая их в контроллере интерфейса. При перетаскивании объекта на контроллер он располагается под предыдущими объектами и экран довольно быстро заполняется. Чтобы расставить объекты в ряд, нужно использовать группы, которые во многом похожи на Stack View в iOS и macOS.

Итак, для начала перетащим group (группу) из Object Library на контроллер интерфейса:

5c69f13aa7016018852b36c23548671c.png


Хотя пока это выглядит не очень впечатляюще, эта группа в будущем будет содержать логотип Air Aber, номер рейса и маршрут полёта.

Выбрав новую группу, перейдите к Attributes inspector и поменяйте Insets на Custom. Появятся четыре текстовых поля, в которых можно вручную задать отступы группы.

Поменяйте значение Top на 6:

9d5b4e6af628a746e0bbd70cdb20f5fd.png


Таким образом мы просто добавим немного больший отступ сверху.

Далее перетащите в группу image. Если группа отреагирует на изменение верхнего отступа сворачиванием (спасибо, Xcode! ), тогда перетащите изображение непосредственно в Document Outline и убедитесь, что он стал дочерним элементом группы, а не соседним:

d60ce604a4de03415416954649aa1e26.png


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

1c3e933ad9c66474b67466c9c896c7c6.png


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

2ff322e27065a928463611bce6809ac1.png


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

  • Измените значение Image на Logo — если оно не отображается в раскрывающемся меню, просто введите его с клавиатуры.
  • Измените значение Tint на #FA114F (можно ввести его в панели Color RGB Sliders).
  • Измените параметр Width на Fixed со значением 40.
  • Измените параметр Height на Fixed со значением 40.


Attributes inspector должен выглядеть следующим образом:

db1f0b6a765fd041fe0ad88eba5fe1b7.png

Не волнуйтесь, если не видите логотип: похоже, Xcode не меняет оттенок шаблонов изображений во время разработки! Поверьте, он будет ярко-розовым после сборки и запуска приложения.

Далее перетащите в созданную группу ещё одну group и проверьте, что она появилась справа от изображения, а затем воспользуйтесь Attributes inspector, чтобы изменить её Layout на Vertical. Также измените Spacing на Custom\0, а Width — на Size to Fit Content.

Затем перетащите на новую группу две label. Мы задали вертикальное расположение, поэтому метки появятся одна над другой:

407067c4a5f7c838f28d4c1bf4aeb3b5.png


Выберите верхнюю метку и воспользуйтесь Attributes inspector для ввода в поле Text значения Flight 123 и изменения Text Color на #FA114F (вместо повторного ввода в RGB panel можно выбрать розовый цвет из списка Recently Used Colors меню Color).

Затем выберите нижнюю метку и введите в поле Text текст MEL to SFO. Теперь контроллер интерфейса должен выглядеть вот так:

3ef6656917cebbb37e75833bb2803f2a.png

Этот текст временный, он будет заменён, когда мы подключим интерфейс к классу контроллера.

Далее перетащите ещё одну group на контроллер интерфейса, но на этот раз убедитесь, что добавляете её как соседний элемент самой первой группы. Если вам не удаётся правильно расположить группу в иерархии, то воспользуйтесь Document outline.

e144a3014ac234d8fde0fa0b74634a0d.png


Выбрав эту новую группу, присвойте Layout значение Vertical, а Spacing — значение Custom\0.

Затем перетащите в эту новую группу три label:

938bec4fe965956d69090e8c8295f8f2.png


Проверьте в 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.

Теперь контроллер интерфейса должен выглядеть следующим образом:

3512e2861d405a541e9fcbf898dd0435.png


Теперь нам осталось только добавить ещё одну группу, а потом приступать к созданию полей классов Outlet, чтобы этот интерфейс отображал реальные данные.

Перетащите новую group из Object Library в нижнюю группу, на этот раз она должна быть дочерней и находиться в самом низу содержащей её группы. Далее добавим в ней две label. Теперь полная иерархия объектов интерфейса должна выглядеть вот так:

823b56f012c348a3fb9db463bdd281e6.png


Задайте в Attributes inspector для Text левой метки значение Gate 1A. Для правой метки задайте Text значение Seat 64A и выберите для Horizontal Alignment опцию Right.

Теперь законченный интерфейс должен выглядеть подобным образом:

e0d482e592b51ca393c311548711f3dc.png


Поздравляю, вы закончили создавать макет интерфейса своего первого приложения Watch! Теперь нужно заполнить его настоящими данными и запустить приложение в симуляторе.

Создание контроллера


Нажмите правой кнопкой на группу Watch Extension в Project navigator и выберите New File…. В открывшемся диалоговом окне выберите watchOS\Source\WatchKit Class и нажмите Next. Назовите новый класс FlightInterfaceController и проверьте, что он является подклассом WKInterfaceController, а для Language задано значение Swift:

fb55a3f44d5981f4b36d00d351ab95fb.png


Нажмите 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)")
  }
}


Вот что здесь происходит на каждом этапе:

  1. Мы объявили вспомогательное свойство типа Flight. Этот класс объявляется в Flight.swift, который является частью общего кода, ранее добавленного в целевую конфигурацию Watch Extension.
  2. Мы добавили Property observer, выполняемый при задании свойства.
  3. Мы проверяем, что во вспомогательном свойстве содержится действительно рейс, а не nil. Мы можем продолжать конфигурировать метки только если знаем, что у нас есть верный экземпляр Flight.
  4. Мы конфигурируем метки с помощью соответствующих свойств flight.
  5. Если рейс откладывается, то мы меняем цвет текста в метке на красный.


Теперь нам нужно задать 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 123
  • routeLabel: MEL to SFO
  • boardingLabel: AA123 Boards
  • boardTimeLabel: 15:06
  • statusLabel: On time
  • gateLabel: Gate 1A
  • seatLabel: Seat 64A


fe3373d2c6a79ee76cc21e0f1ea81efe.png

Прежде чем запустить приложение, нам осталось сделать только одно. Пример приложения, которое мы разрабатывали в этом туториале, создавался для 42-миллиметровых Apple Watch, так что нужно убедиться, что настроен правильный симулятор, в противном случае интерфейс может выглядеть немного искажённым. В настоящих приложениях разработчики проверяют работу интерфейсов на всех размерах часов, но это не относится к теме этого туториала.

Откройте меню схемы Watch и выберите один из симуляторов 42-миллиметровых часов:

97a8413937f733598eff208205ac78e7.png


Выполните сборку и запустите приложение. После завершения загрузки симулятора вы должны увидеть правильное расположение интерфейса и розовый логотип Air Aber. Объект Flight генерирует случайные значения для времени посадки и номера места, поэтому здесь вы увидите другие значения:

9b1a1f0cb1d291a5f133f0cf1d182bbb.png


Примечание: если вы получите сообщение о сбое установки, то можно или попробовать повторить процесс в 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 на новый контроллер интерфейса:

1cf0a81ecb5a6071ae71d6edbc7f831a.png


Выберите Table Row Controller в Document outline:

204286ae439aca3296cacef68a0ee158.png


Используйте 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.


Инспектор теперь должен выглядеть вот так:

7f72ba51d7f11d7cb28f0358334cacd2.png

Строка таблицы внезапно выросла и заполнила весь экран! Но мы исправим это в процессе настройки конфигурации строки.

Перетащите 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:

66007229756da3381bbd745cdf872a17.png


Мы хотим придать этому изображению розовый оттенок 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. Строка таблицы теперь должна выглядеть следующим образом:

bf1d27c75bf9dd6e070231d251203d7e.png


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

Иерархия объектов интерфейса должна теперь быть такойна такую:

a8780162ff6d62541d857b93114b7df3.png


Мы почти закончили с интерфейсом строки таблицы, осталось добавить только номер и состояние рейса.

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

8aa8d7c77e00f7c65ad2a03032a4592a.png


Продолжив создавать этот интерфейс, вы увидите и другие примеры того, как можно использовать встроенные группы со смешанными конфигурациями для создания сложных конфигураций. Кому нужен этот 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.


После внесения этих последних изменений законченная строка таблицы должна выглядеть так:

2a912f599f141780a04a2459c629c9e6.png


Настроив таблицу в Interface Builder, мы можем начать заполнять её какими-нибудь данными.

Заполнение таблицы


Первое, что нужно сделать — создать подкласс класса WKInterfaceController для управления таблицей.

Нажмите правой кнопкой на группу Watch Extension в Project navigator и выберите New File…. В появившемся диалоговом окне выберите watchOS\Source\WatchKit Class и нажмите Next. Назовите новый класс ScheduleInterfaceController. Убедитесь, что он является подклассом WKInterfaceController, и что для Language задано значение Swift:

fcd3beca0fbcdca030ce14d753b46946.png


Нажмите Next, затем Create.

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

Откройте заново Watch\Interface.storyboard и выберите новый контроллер интерфейса. В Identity inspector замените Custom Class\Class на ScheduleInterfaceController:

0b2469e2faa3089fd931655d1e473035.png


Оставив выбранным контроллер интерфейса, откройте Assistant editor и убедитесь, что там отображается ScheduleInterfaceController. Далее зажмите Control и перетащите из Table в Document outline внутрь определения класса ScheduleInterfaceController для создания поля Outlet:

6560f110182f8dbfcc37c49e93b8b448.png


Назовите 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:

6f5f918cb0892d0c63a418dcb80362e9.png


Но постойте! Заголовок имеет тёмно-серый цвет вместо ярко-розового корпоративного цвета Air Aber. Сейчас мы это исправим.

Откроем Watch\Interface.storyboard, выберем контроллер интерфейса Air Aber. В File inspector изменим Global Tint на #FA114F.

9d152a687740e82d6c314455c5733e43.png


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

5c2320893259781f8cac9188258aeedf.png


Но теперь вы заметите, что во всех строках отображается текст-заполнитель, заданный нами в Interface Builder. Сейчас мы это исправим, добавив контроллер строк, настраивающий метки для каждой строки.

Добавление контроллера строк


Таблицы WatchKit намного проще, чем таблицы iOS: здесь нет источников данных и delegate! Достаточно просто создать класс контроллера строк, который, несмотря на своё название, является подклассом класса NSObject.

Нажмите правой кнопкой на группу Watch Extension в Project navigator и выберите New File…. В открывшемся диалоговом окре выберите watchOS\Source\WatchKit Class и нажмите Next. Назовите новый класс FlightRowController. Убедитесь, что он является подклассом NSObject, —, а не WKInterfaceController! — и что Language имеет значение Swift:

f64367d514d3581788f1c5a29a621c59.png


Нажмите 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)
    }
  }
}


Вот что происходит на каждом из этапов:

  1. Мы объявляем вспомогательное свойство типа Flight. Помните, что этот класс объявляется в Flight.swift, который является частью общего кода, добавляенного к Watch Extension в предыдущей части туториала.
  2. Добавляем Property observer, выполняемый при задании свойства.
  3. Выполняем выход, если flight имеет значение nil: это необязательный шаг, но мы хотим продолжать настройку меток только если у нас есть правильный экземпляр Flight.
  4. Настраиваем метки с помощью соответствующих свойств flight.
  5. Если рейс откладывается, то мы меняем цвет текста метки на красный и соответствующим образом обновляем текст.


Завершив настройку контроллера строк, мы должны обновить строку таблицы, чтобы её использовать.

Откройте Watch\Interface.storyboard и выберите FlightRow в Document outline. С помощью Identity inspector задайте Custom Class\Class значение FlightRowController.

В Document outline откройте все группы в FlightRow, затем нажмите правой кнопкой на FlightRow, чтобы вызвать всплывающее окно полей Outlet и действий:

3cf7a3aa68ed51e0fe420b2e69652e40.png


Можно перетащить это всплывающее окно вправо, чтобы видеть все объекты в FlightRow.

Сначала подключим planeImage к изображению в строке таблицы, а separator к разделителю. Затем подключим оставшиеся поля Outlet в соответствии со следующим списком:

  • destinationLabel: SFO
  • flightNumberLabel: AA123
  • originLabel: MEL
  • statusLabel: On time


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

Откройте ScheduleInterfaceController.swift и добавьте следующие строки под awakeWithContext(_:):


for index in 0..


Здесь мы проходим в цикле for по каждой строке в таблице и запрашиваем у таблицы контроллер строк с соответствующим индексом. Если мы правильно запрашиваем контроллер, то получаем экземпляр FlightRowController. Затем мы задаём controller.flight соответствующий элемент flight в массиве flights. Это приводит к выполнению наблюдателя didSet в FlightRowController и настраивает все метки в строке таблицы.

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

c2127b7014c9108c3de4cb2ebc7a9908.png


И теперь последняя задача в этой части туториала: когда пользователь касается строки таблицы, 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 и настраивает интерфейс.

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

f63cd0f4bb6b2fd83660b3eeef04d18d.gif


Поздравляю! Вы закончили реализацию своей первой таблицы и заполнили её настоящими данными. Отличная работа!

Вот готовый пример проекта, который мы пока сделали в этом туториале.

Часть 3: анимации


В третьей части туториала мы узнаем, как пользоваться анимациями watchOS 4 на примере нового интерфейса регистрации на рейс в нашем приложении.

В процессе работы вы узнаете:

  • Как создавать анимации на основе изображений.
  • Как использовать API анимации watchOS 4.


Приступаем к работе


Откройте Watch\Interface.storyboard и перетащите interface controller из Object Library на холст storyboard. Выбрав контроллер интерфейса, откройте Attributes inspector и введите для Identifier значение CheckIn. Мы сделали это для того, чтобы можно было определить контроллер интерфейса из ScheduleInterfaceController.

Затем перетащите из Object Library © Habrahabr.ru