Привет, Хабр! Недавно мы обновили приложение Почты Mail.Ru для iOS. Теперь оно поддерживает Apple Watch. Сегодня я хочу рассказать, с чем мы столкнулись при разработке приложения для еще не вышедших на тот момент часов, и поговорить о том, как справляться с минимализмом SDK и невозможностью протестировать приложение на «живом» девайсе.
Особенности разработки интерфейса для часов
Написание кода для часов мало отличается от написания кода под iOS — ведь вся логика выполняется на телефоне. Однако когда дело доходит до разработки пользовательского интерфейса, все усложняется: для часов появились новые классы, которые используются только в них. На данный момент этих классов всего 11 — но тут можно вспомнить iOS первой версии, в которой также было мало возможностей. Все мы знаем, как выросло их количество в последующих версиях, и можно надеяться, что SDK для часов ждет похожая судьба. Пока же реалии разработки UI для часов таковы:
Использование только компонентов для часов
Самое первое огорчение, с которым сталкиваешься при разработке интерфейса для часов — приходится забыть про все красивые кнопочки, анимации, экраны, которые вы до этого разработали для телефона. Их никак нельзя применить повторно, ведь в интерфейсе используются только визуальные компоненты для часов.
Новая модель layout
Относительно недавно Apple представила для своих телефонов новую модель layout. Ранее можно было задавать конкретные координаты различных элементов и правила, по которым элементы растягивались при растягивании экрана. Но начиная с iOS 6 появилась новая модель под названием Auto Layout, позволяющая задавать все эти правила более гибко.
Следовало ожидать, что Auto Layout можно будет использовать и на часах — однако в SDK для часов появилась третья, совершенно новая модель layout. Она напоминает ту, которая используется в Android SDK (весь интерфейс состоит из контейнеров, заполняющихся элементами вертикально или горизонтально).
Перестроение при скрытии
Интересная особенность, которой не было в iOS SDK: при скрытии элемента он не просто пропадает с экрана — все остальные элементы занимают его место, и пустого пространства на экране не остается. Благодаря этому можно на одном экране поместить сразу несколько, с возможностью динамического переключения между ними.
Вообще, в SDK для часов есть несколько возможностей для переключения между экранами: экран анимировано всплывает снизу или анимировано выезжает сбоку (многостраничный интерфейс). Эмуляция переключения при скрытии элементов — неофициальная, недокументированная, но довольно интересная альтернатива, позволяющая сменить экран без анимации.
Context вместо интерфейса контроллера
Если в iOS SDK мы могли сами инициализировать ViewController и назначать ему любые свойства, то в SDK для часов создание контроллера интерфейса (WKInterfaceController) делается системой, и получить к нему доступ из кода нельзя. Взамен разработчики получили новый параметр под названием context, который можно передать в контроллер при его отображении. Внутри контроллера он будет доступен в методе awakeWithContext. Через context можно передавать те же параметры, которые мы привыкли передавать при создании: например, блоки, которые дочерний контроллер должен будет вызвать, чтобы передать информацию родительскому, ID письма, которое надо открыть, и т.д.
Анимация
В часах сильно сокращены возможности создания анимаций. Здесь нет фреймворков CoreAnimation и UIKit, с помощью которых анимации можно создавать программно. Единственная возможность что-то анимировать на часах — это создать последовательность картинок, что-то вроде анимированной gif-ки.
С одной стороны, этот способ проигрывает в гибкости, ведь мы не можем динамически задавать параметры анимаций в коде. С другой стороны, это может быть удобнее для программистов, потому что анимацию полностью разрабатывают дизайнеры и предоставляют ее в уже готовом виде — в виде последовательности картинок, которые остается только воспроизводить (по кругу, однократно или даже не полностью, т.е. лишь некоторую последовательность кадров).
Glance
Glance (он же «Превью») — это небольшой экран, который может создать любое приложение, чтобы отображать на нем какую-то информацию. Например, мы на нем показываем количество непрочитанных писем и имя отправителя последнего непрочитанного письма. Создание интерфейса для Glance ограничено набором лэйаутов. Вариантов в этом наборе достаточно, но все же придется выбирать из предложенных.
Рекомендуемые стили шрифтов
В редакторе интерфейсов при задании шрифта текста появился набор рекомендуемых стилей. Раньше была возможность выбрать системный шрифт, его размер и стиль (Italic, Bold и т.д.). Сейчас же появились шаблоны, такие как Body, Title и т.д. Применение стилей необязательно, но сама возможность, надеемся, будет способствовать тому, что разработчики станут ей пользоваться, и интерфейсы будут выглядеть более единообразно.
Наложение элементов
В отличие от iOS, часы не позволяют накладывать элементы друг на друга. Единственный способ обойти это ограничение — задать фон группы в виде картинки и вставить элемент в содержимое группы. В приложении Почты Mail.Ru этот обходной путь используется для отображения экрана уведомления о новом письме (аватара отправителя, поверх которой выводится текст — тема письма и отправитель; чтобы текст было видно, под него подложен градиент).
Поддержка Attributed String
Из приятных вещей, которые мы с удивлением обнаружили в интерфейсе часов, стоит упомянуть поддержку Attributed String в лейблах. Благодаря этому у нас есть возможность нарисовать текст, в котором отдельные фрагменты будут по-разному отформатированы. В качестве примера можно привести тот же Glance: часть текста мы выделяем другим цветом, а другую часть — более крупным шрифтом
Force Touch и Digital Crown
В часах появился новый жест под названием Force Touch, то есть «сильное нажатие». Также в Apple Watch в наследство от обычных часов досталось колесико Digital Crown. Я объединил эти элементы в один пункт, потому что и Digital Crown, и Force Touch — элементы управления интерфейсом, с которыми пока нет полной ясности. Создается ощущение, что в Apple еще не уверены, как ими будут пользоваться. Так, Digital Crown где-то используется для скроллинга списка, где-то — для зуминга, где-то — для перемещения между элементами Storyboard.
Если поведение Digital Crown разработчикам неподвластно и остается довольствоваться тем, что «повесили» на него в Apple, то с Force Touch мы решили подождать и не стали включать его поддержку в первую версию приложения Почты.
Кэш изображений
Механизм кэширования изображений на часах довольно своеобразен, но вполне логичен, если вспомнить, что именно представляет собой приложение для часов. Если вкратце, оно состоит из нескольких частей: основное «хостовое» приложение, WatchKit Extension — расширение для часов, которое запускается на телефоне, — и приложение часов, которое запускается непосредственно на часах.
При добавлении картинки на экран часов происходит следующее. Запускается основная логика на телефоне, которая формирует картинку — например, скачивает ее из интернета. Картинка отдается по Bluetooth или по Wi-Fi на часы. Дальше начинаются особенности часов. При обычном кэшировании мы просто закачиваем изображение, показываем его пользователю и кладем в файл до следующего раза. С часами мы не можем просто положить изображение в файл на телефоне, ведь тогда нам придется каждый раз передавать его на часы. На этот случай у часов есть свой кэш объемом 20МБ.
Интересно, что положить изображение в кэш часов можно, а вот достать его оттуда может только стандартный контрол. Причем, насколько нам известно, есть только косвенные признаки, по которым мы можем понять, есть ли картинка в кэше. Например, используя методы API, по которым можно узнать размеры закэшированного изображения. Делается это следующим образом: мы спрашиваем, каковы размеры файла с именем предположительно закэшированного изображения. В зависимости от того, вернутся нам размеры или нет, можно сделать вывод, есть ли это изображение в кэше, или придется его сначала закачать.
Отсутствие applicationDidFinishLaunching и applicationDidResignActive
У iOS-приложения есть свой жизненный цикл. Оно запускается, уходит в фон, выгружается из памяти и так далее. При возникновении этих событий приложение получает соответствующие оповещения. На часах этих оповещений нет, что несколько осложняет создание объектов на этапе запуска приложения. Кроме того, могут возникнуть проблемы со сторонними библиотеками, работа которых завязана на эти оповещения. Например, мы столкнулись с тем, что библиотека для статистики Flurry не отправляет события в статистику на сервер именно потому, что не получает оповещения.
Проблема связана с тем, что в отличие от приложения для iOS с единой точкой входа, где можно один раз все проинициализировать, в приложении для часов есть три варианта входа: Glance, Notification и Main App. Однако во всех трех случаях запуск приложения для часов связан с показом первого контроллера. Это дает нам возможность использовать метод awakeWithContext.
@implementation MRWKContactsInterfaceController
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
[MRWKInitialization extensionDidFinishLaunching];
...
}
@implementation MRWKInitialization
+ (void)extensionDidFinishLaunching {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self internalExtensionDidFinishLaunching];
});
}
Кроме того, можно использовать метод initialize в одном из классов расширения, который будет выполнен при первом обращении к этому классу.
Также можно использовать «ленивую» инициализацию, когда объекты создаются только при обращении к ним. Но нужно учитывать, что в случае со статистикой это не поможет — к примеру, Flurry нужно знать, когда конкретно было запущено приложение.
Взаимодействие с основным приложением
Возможности приложения для часов ограничены, и чтобы выполнять действия, которые в нем не поддерживаются, мы можем вызвать основное приложение. Рассмотрим способы это сделать.
OpenParentApplication
Появился новый API —
+[WKInterfaceController openParentApplication:reply:]
, позволяющий из приложения для часов вызвать основное приложение, которое при этом запустится в фоне, выполнит запрошенные действия и вернет результат. Пример:
@implementation WKInterfaceController (MRWKParentApplicationActions)
+ (void)mrwk_markMessageIdAsRead:(NSString *)messageId
accountName:(NSString *)accountName
completionBlock:(MRWKParentApplicationActionsCompletionBlock)completionBlock {
MRWKParentApplicationAction *action = [MRWKParentApplicationAction markAsReadActionWithMessageId:messageId
accountName:accountName];
[self openParentApplication:action.dictionary reply:^(NSDictionary *replyInfo, NSError *error) {
if (completionBlock) {
MRWKParentApplicationReplyInfo *info = [[MRWKParentApplicationReplyInfo alloc] initWithDictionary:replyInfo];
completionBlock(info.success, error ?: info.error);
}
}];
}
@end
Application Groups
Второй способ взаимодействия — Application Groups. На iOS все приложения и расширения выполняются в отдельных песочницах — ограниченных участках файловой системы, доступ к которым есть только у них самих. Чтобы приложения могли использовать общие разделяемые ресурсы или обмениваться файлами, существуют Application Groups. Если приложению и расширению для часов назначить общую Application Group, в файловой системе появится папка, к которой оба будут иметь доступ. Мы эту возможность используем, в частности, для того, чтобы приложение Почты Mail.Ru для часов имело доступ к базе данных основного приложения (ко всем письмам, контактам и т.д.) и к объекту NSUserDefaults (различные пользовательские настройки).
Darwin Notifications
Darwin-нотификации — это способ обмениваться сообщениями между процессами в iOS. Эта возможность существует давно, но актуальной стала с появлением расширений — в частности, расширения для часов. С помощью Darwin-нотификаций можно отправить на часы оповещение, если в основном приложении что-то изменилось — например, если на часах открыт список контактов, и в это время в основном приложении добавлен новый контакт. Darwin-нотификации позволяют отправить событие из основного приложения, чтобы часы обновили список контактов из базы данных.
Handoff
В последней версии iOS появилась функция Handoff, которую Apple активно рекламирует. Handoff тоже может рассматриваться как способ взаимодействия с основным приложением. Здесь взаимодействием будет не непосредственный обмен данными, а например, создание активити из часов, чтобы при открытии основного приложения сразу загружался определенный экран. Допустим, если пользователь открыл Почту Mail.Ru и начал писать письмо на часах, а затем запустил приложение на iPhone — Handoff позволяет сразу же автоматически открывать на телефоне экран создания письма.
Разработка без устройства
Самый главный подводный камень разработки для Apple Watch — невозможность понять, как то, что мы пишем, будет работать на реальном устройстве. Мы видим только симулятор, который является лишь некоторым его приближением. Даже в официальной документации встречаются несоответствия с реальностью.
Получение нескольких уведомлений
Некоторые аспекты в симуляторе не отражены в принципе. Например, в документации описан следующий кейс: когда приходит несколько нотификаций, на экране появляется круглая иконка приложения и текст «N новых уведомлений». Однако симулятор не дает возможности увидеть, как это будет выглядеть. Казалось бы, мелочь, но на практике это может обернуться неожиданностями: название приложения будет не таким, как мы ожидаем, иконка будет неправильно отображаться — причем предугадать это практически невозможно. Единственное, что можно сделать для минимизации рисков — следовать документации абсолютно во всем.
Emoji
На презентации Apple рассказали, что на часах будут красивые анимированные смайлы, которые можно настраивать. Наше приложение их поддерживает — смайлы можно добавить через стандартный экран написания письма. При этом неизвестно, как они будут выглядеть в письме, в каком формате будут приходить в программу и как их надо преобразовать, чтобы вставить в письмо. Есть предположение, что это будут обычные символы emoji, но не исключено и то, что они будут приходить в виде анимированной gif-ки или чего-то еще, не известного ранее. В документации формат также не конкретизирован. Мы решили в первой версии приложения Почты исходить из предположения, что смайлы будут приходить в виде текста.
Голосовой набор
Еще одна неясность, связанная с написанием письма — работа голосового набора. В общих чертах все понятно: нажимаем на микрофон, говорим, получаем введенный текст. Но и здесь возможны нюансы — например, может возвращаться не одна строка текста, а несколько вариантов того, что продиктовал пользователь.
Неавторизованный пользователь
Следующий неоднозначный кейс: предположим, человек установил приложение Почты Mail.Ru на телефон, но ни разу его не запускал и, как следствие, не авторизовался. Будет ли в этом случае доступно приложение на часах, сможет ли пользователь его запустить с дисплея Apple Watch? Однозначный ответ мы узнаем только после выхода часов, и все же решили подстраховаться на случай, если запуск возможен: незалогиненному пользователю будет выводиться сообщение «Откройте приложение на телефоне, чтобы авторизоваться».
Handoff
Анонсировалось, что Handoff будет поддерживаться и на часах, и документация свидетельствует о том же. Однако проверить, как это работает, пока нельзя, так как симулятор не поддерживает Handoff. И снова мы вынуждены ждать реального устройства.
Static / dynamic notification
Когда приходят push-уведомления, часы сначала пытаются отобразить динамическую нотификацию, то есть более сложный интерфейс. Если загрузка затягивается, появляется более простой интерфейс — статическая нотификация. К сожалению, с помощью симулятора увидеть, как это происходит, невозможно.
Уведомления: «разделение труда» между телефоном и часами
С нотификациями связаны некоторые действия: мы можем ответить на письмо, удалить его, пометить прочитанным. Некоторые из этих действий обрабатываются расширением для часов, другие — основным приложением. Мы не можем протестировать на симуляторе действия, которые обрабатываются основным приложением. Можно вызвать эти действия напрямую с телефона, но если попробуем вызвать их с часов, симулятор попытается обработать их в расширении для часов, в то время как в реальной жизни они будут обрабатываться приложением. Такое поведение связано с тем, что симулятор не поддерживает push-уведомления в принципе — ни для телефона, ни для часов. Однако в SDK для часов появилась возможность при запуске приложения для часов из среды разработки «подсунуть» содержимое push-уведомления и проверить, как оно будет обрабатываться. Проблема в том, что нет возможности протестировать все возможные кейсы. Например, нельзя задать, чем будут обрабатываться нотификации — основным приложением или расширением для часов. Пришлось ограничиться проверкой тех действий, которые обрабатываются приложением, через вызов действий из уведомления на телефоне (в реальном устройстве), и надеяться, что на часах это будет работать так же.
Разработка в Xcode
Всем, кто работает с Xcode, известно, что в ней есть ошибки — и в каждой новой версии появляются свежие экземпляры. Выход WatchKit SDK не стал исключением.
Сертификаты для distribution
Недавно Xcode взяла курс на автоматические code signing и provisioning. В теории Xcode создает за вас все профили, необходимые сертификаты, Bundle ID. На практике она это делает в некоторых случаях, но не во всех. В частности, мы столкнулись с тем, что distribution-профили для часов автоматически не создаются, их приходится создавать самостоятельно на портале Apple для разработчиков.
Задание отдельных радиусов скругления для 38/42 мм
В интерфейс-билдере появилась возможность задавать отдельные свойства элементов для часов размером 38 и 42 мм, но в некоторых случаях это не работает. Например, если задать свой радиус скругления для группы для разных версий часов, не скруглится вообще ничего. Мы обходили этот баг заданием скругления через код, а не через графический редактор интерфейсов.
RemoteInterfacePrincipalClass
В plist приложения для часов появились новые свойства, в частности свойство RemoteInterfacePrincipalClass непонятного назначения. С одной стороны, вроде бы его надо задавать, с другой — судя по опыту, значение этого свойства ни на что не влияет. Мы вписали туда правильное имя класса на тот случай, если на реальных часах оно все же «всплывет».
SPErrorGizmoInstallNeverFinishedErrorMessage
Есть проблемы и в симуляторе. Иногда, если запустить одно приложение для часов, поработать с ним, остановить, а затем попробовать запустить другое, симулятор отказывается его устанавливать и выдает ошибку со странным названием из подзаголовка. Лечится это только удалением симулятора, сбросом всего содержимого и полным clean проекта.
Разделение кода между приложением и расширением
Напоследок пара слов об оптимизации процесса. Приложение и расширение Почты Mail.Ru для часов выполняют схожие функции: оба позволяют посмотреть список контактов, отправить письмо и т. д. Поэтому имеет смысл часть кода из основного приложения повторно использовать в часах. Ниже — список действий, позволяющих увеличить долю общего кода.
- Изначально писать код модульно, таким образом, чтобы отдельные классы можно было использовать в расширениях, не добавляя вместе с ними кучу зависимостей, которые, возможно, даже не понадобятся для работы, но необходимы для компиляции.
- Использовать CocoaPods — систему управления зависимостями для Objective-C. CocoaPods дает возможность добавлять определенные библиотеки к тем или иным таргетам. Благодаря этому мы можем к таргету основного приложения Почты Mail.Ru подключить большой список зависимостей, а к таргету для часов — только те библиотеки из этого большого списка, которые там нужны.
- Использовать Dynamic Frameworks, если вы уже отказались от поддержки iOS 7.
- Использовать скрипты для проверки файла проекта. Если код подключается не через CocoaPods, а вручную, надо следить за тем, чтобы в таргеты были включены только те файлы, которые там используются. В нашем случае в конфигурационном файле задаются все отличия, которые должны быть между основным таргетом и таргетом для часов, и скрипты все это проверяют.
- Вызвать основное приложение через API в тех случаях, когда это эффективнее, чем использовать классы из основного приложения в часах.
Заключение
Если обобщить наш опыт, можно сказать, что мы выделили для себя следующие (довольно очевидные) основные правила разработки без устройства:
- досконально изучить документацию, поскольку отловить проблему при тестировании будет почти невозможно;
- помимо основных вариантов, реализовывать страховочные — на тот случай, если устройство работает не так, как вы предполагали;
- использовать все возможные источники информации об устройстве — от официальных до гиковских подкастов.
Думаю, здесь, на Хабре, немало людей, которые уже сталкивались с необходимостью разработки под устройство, которое по тем или иным причинам нет возможности подержать в руках. В комментариях предлагаю обмениваться опытом на тему того, как можно облегчить себе жизнь в таких случаях.
Комментарии (7)
Palomnik
30.04.2015 09:38Это читали? habrahabr.ru/company/redmadrobot/blog/256501
А то, как-то, большая часть информации повторяется.k011a Автор
30.04.2015 10:33+1Да, действительно, получилось много пересечений со статьей коллег. Но это неудивительно, учитывая небольшие возможности WatchKit, их все легко уместить в одну статью. В нашей статье мы постарались сделать не просто пересказ документации, а поделиться различными особенностями, проблемами и решениями, с которыми мы столкнулись в ходе разработки.
RudkoDmitry
11.05.2015 17:33+1Спасибо за подробную и достоверную информацию. Полезная статья на мой взгляд. Я и мои коллеги тоже делали приложение для часов и почти все наши наблюдения в этой статье подробно описаны.
А вот чего не прочитать в блогах больших и важных компаний: так это об ошибках и разочарованиях. Напишу пост о том чего мы ждали от часов, как приходилось обламываться и переделывать, отказываться от решений и что в итоге получилось. И это не история успеха и покорения рынка. Это скорее слепая погоня за трендом на авось.
terre
Здорово, а как на часах написать сообщения? Я слышал русская диктовка пока не работает.
k011a Автор
Сообщение можно выбрать из нескольких заранее заданных фраз, либо вставить смайлик, либо с помощью диктовки. К сожалению, за час, пока у нас была возможность потестировать приложение на реальных часах, мы не догадались проверить русскую диктовку, но английская точно работает.
k011a Автор
Коллеги подсказывают, что работает.