[Из песочницы] Три наиболее значимых нововведения с приходом watchOS2

Итак, о чем действительно стоит упомянуть при переходе к разработке на watchOS2 — это следующие три пункта:

  • WKInterfacePicker
  • Complications
  • WCSession


В данной статье я коротко расскажу о каждом из них.

WKInterfacePicker


WKInterfacePicker — это нововведение от Apple, которое расширяет возможности разработки в разы. Что же это такое? Если проводить аналоги с разработкой под iOS, то это UIPickerView для часов, однако с более гибким функционалом. Управление реализуется при помощи Digital Crown.

Обо всех особенностях я упоминать не буду, а отмечу лишь то, что будет важно на начальных этапах разработки. Итак, WKInterfacePicker имеет 2 важных параметра: Style и Focus Style. Меняя стиль (Style), мы меняем способ отображения информации.

Начну сразу с примера. Вопреки хипстерским представлениям, я предоставлю код, написанный на старой доброй классике — Objective-C.

Добавляем в наш InterfaceController аутлет на пикер:

@property (unsafe_unretained, nonatomic) IBOutlet WKInterfacePicker *picker;


Устанавливаем стиль пикера в режим List.

7d8f9dc3dca44b2eba9b410ffd04a324.png

И копипастим код:

- (void)willActivate {
    // This method is called when watch view controller is about to be visible to user
    [super willActivate];
    
    NSArray* arrayOfTexts = @[
                              @"Раз",
                              @"Два",
                              @"Три",
                              @"Четыре",
                              @"Пять",
                              @"Шесть",
                              @"Семь",
                              @"Восемь",
                              @"Девять",
                              @"Десять",
                              @"Одиннадцать",
                              @"Двенадцать",
                              @"Тринадцать",
                              @"Четырнадцать",
                              @"Пятнадцать",
                              @"Шестнадцать",
                              @"Семнадцать",
                              @"Восемнадцать",
                              @"Девятнадцать",
                              @"Двадцать"];
    NSMutableArray* arrayOfItems = [NSMutableArray new];
    
    for (int i = 0; i < arrayOfTexts.count; i++) {
        WKPickerItem *item = [WKPickerItem new];
        [item setTitle: arrayOfTexts[i]];
        [arrayOfItems addObject: item];
    }

    [self.picker setItems:arrayOfItems];
}



В первом случае (List) мы можем отображать текст в привычной нам форме.

18413a02ceff458dbb0750bc002a2f0e.gif

Установив пункт Stack, мы получаем набор картинок c предустановленной анимацией.

1e427284897b41b79b7c85efa045c1b9.pngda82c22c7cef44f4810e00c479558344.gif

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

b672e80e388f44e08e1263a082e254a5.pngedd94838358a470698d4c2b14996845a.gif

Однако стоит упомянуть кое-что важное: перед началом отрисовки арта следует создать порядка сотни раскадровок с разрешением x2 (x3). Это обеспечит плавную прокрутку и расширит диапазон изменяемых значений.

Кстати говоря, мы можем отслеживать текущий индекс путем создания экшена (способ нестандартный, лично я ожидал метода делегата или что-то вроде метода интерфейс контроллера для таблицы table didSelectRowAtIndex.

Тянем пикер в хедер и прописываем название метода, вроде такого:

- (IBAction)pickerDidSelectValue:(NSInteger)value;


Далее контролируем полученное значение по своему желанию.

Контролируя параметр Focus Style, мы можем установить или отключить видимость границ пикера. Однако на данный момент нет возможности контролировать какие-либо свойства фокуса (в активном состоянии пикера фокус зеленого цвета с дефолтными закруглениями).

e17504c70581424d9945453be150ebde.png5bf54ac6c60f49c88f427228f20e3685.png

3018137dc5d24049812744e7a1f68aa5.png67c7c9b3769f4d658b78cd6495163e91.png

e5b593a6bf4a4813b1bed678c6072aad.pngd81fb0a1858e43bfbac7f4a978360644.png

В целом WKinterfacePicker — это мощный инструмент, который открывает огромные возможности для разработчика.

Complications


По своей сути Complications — это маленькие элементы, которые мы видим при открытии Modular watch face. Дословный перевод слова complications — осложнения. Случайность? Не думаю. Ведь оболочкой для такого, казалось бы, несложного функционала является целый фреймворк ClockKit. При первом просмотре содержимого можно ужаснуться, хотя все не так грустно. Давайте разберем, максимально кратко, так как данной тема стоит отдельной статьи.

При создании нового проекта нам предлагают возможность подключения «осложнений». При поставленной галочке нашему проекту добавляется класс ComplicationController.

174c8b8a549847f29d3fab717080a160.png5596311cf4054d2aae7a2277b17149af.png

К хедеру этого класса подключается протокол CLKComplicationDataSource. В имплементации предустановлено 9 методов. Первые 4 фактически устанавливают условия для поддержки Complications в режиме путешествия во времени (Time Travel). Нас интересует иной метод.

- (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimelineEntry * __nullable))handler


Он здесь ключевой. В нем мы устанавливаем «точку входа» нашего complication с указанными параметрами и датой. Выглядит это следующим образом: сначала мы создаем пустой объект класса CLKComplicationTimelineEntry.

CLKComplicationTimelineEntry* entry = nil;


Затем мы пляшем от того, какой тип complication хотим использовать. Для примера возьмем Modular Small и поставим туда картинку.

    if (complication.family == CLKComplicationFamilyModularSmall) {
        CLKComplicationTemplateModularSmallSimpleImage* image = [[CLKComplicationTemplateModularSmallSimpleImage alloc] init];
        [image setImageProvider: [CLKImageProvider imageProviderWithOnePieceImage:[UIImage imageNamed:@"cat"]]];
        entry = [CLKComplicationTimelineEntry entryWithDate:[NSDate date] complicationTemplate:image];
    }
    handler(entry);


Затем кастомизируем выбранный Modular watch face и получаем забавного котейку от нашего тестового приложения. Profit.

6537f35b0b784f04a9ccbb7e6c95cb16.png

06ab554a778d47b490b18cf7656b9172.png

c4363a04a7b5497aa5bf2d760cdc9214.png

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

WCSession


Пришла новая эра синхронизации между iPhone и Apple Watch. Если вы застали время дельного «костыля» от Apple под названием openParentApplication или пользовались AppGroup вместе с червоточиной, то пора о них забыть. Теперь есть 3 способа передачи информации между устройствами с использованием «сессии»:

  • Application Context
  • Transferring Files and Data
  • Sending Messages


Первое, что необходимо сделать — это подключить фреймфорк WatchConnectivity. Он-то и обеспечивает возможность коммуникации между устройствами.

  #import <WatchConnectivity/WatchConnectivity.h>


Предварительно подключаем протокол WCSessionDelegate в классах, между которыми будем производить трансфер. Далее проверяем поддержку сессий. Если все хорошо, то включаем. В дальнейшем все обращения к сессии производим через [WCSession defaultSession].

if ([WCSession isSupported]) {
      WCSession *session = [WCSession defaultSession];
      session.delegate = self;
      [session activateSession];
}


NSDictionary *dict = @{@"key": @"То, что мы так хотим передать"};
[[WCSession defaultSession] updateApplicationContext:dict error:nil];

для получения на обратной стороне используем метод 
-(void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary<NSString *,id> *)applicationContext {
  NSString* stringWeNeed =  [applicationContext objectForKey:@"key"];
    NSLog(@"%@", stringWeNeed);
}


Если необходима постоянная синхронизация между телефоном и часами, то это наилучший способ. Следующий вариант позволяет передать информацию при помощи 2-х методов:

transferUserInfo: (отправка NSDictionary)
transferFile: metadata: (отправка файла по URL + NSDictionary с метаданными)

По аналогии:

session didReceiveUserInfo: (получаем NSDictionary)
session didReceiveFile: (получаем WCSessionFile с сыллкой на файл и библиотекой с метаданными)

Ну и последний вариант рассмотрим на конкретном примере. Как уже было сказано, импортируем фреймворк WatchConnectivity в стандартных классах ViewController и InterfaceController. Подключаем протокол WatchConnectivity:

@interface ViewController : UIViewController <WCSessionDelegate>
@interface InterfaceController : WKInterfaceController <WCSessionDelegate>


В имплементации прописываем:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    if ([WCSession isSupported]) {
        WCSession *session = [WCSession defaultSession];
        session.delegate = self;
        [session activateSession];
    }
}

- (void)awakeWithContext:(id)context {
    [super awakeWithContext:context];

    if ([WCSession isSupported]) {
        WCSession *session = [WCSession defaultSession];
        session.delegate = self;
        [session activateSession];
    }
}


Будем отображать информацию на часах при клике кнопки на iPhone. Для этого добавляем кнопку в Main.storyboard и тянем IBAction: - (IBAction)buttonClicked:(id)sender;.

В Interface.storyboard добавляем лейбл и перетягиваем Outlet:

@property (unsafe_unretained, nonatomic) IBOutlet WKInterfaceLabel *infoHere;


Нажатием на кнопку мы создаем рандомное число и передаем его на часы, посредством NSDictionary:

- (IBAction)buttonClicked:(id)sender {
   NSString* randomValue = [NSString stringWithFormat:@"%u", (arc4random() % 10000) ]; 
    [[WCSession defaultSession] sendMessage:@{@"key":  randomValue]} replyHandler:^(NSDictionary<NSString *,id> * _Nonnull replyMessage) {
        
    } errorHandler:^(NSError * _Nonnull error) {
        
    }];
    


На обратной стороне прописываем:

-(void)session:(WCSession *)session didReceiveMessage:(NSDictionary<NSString *,id> *)message replyHandler:(void (^)(NSDictionary<NSString *,id> * _Nonnull))replyHandler {

     dispatch_async(dispatch_get_main_queue(), ^{
         
    NSString* string = [message objectForKey:@"key"];
         [self.label setText:string];
     });
}



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

Подытожим. Я перечислил несколько новшеств, которые лично считаю наиболее значимыми с приходом OS2. Несомненно, о каждом из них можно рассказать больше, причем достаточно объемно, но каждому из нас требуются базовые знания для освоения той или иной области. Так пусть эта статья станет той точкой опоры для программистов, решивших связать себя с разработкой под watchOS.

© Habrahabr.ru