Статьи

3 варианта обмена данными между приложениями iOS и WatchKit

В последней части этого короткого цикла мы рассмотрели варианты взаимодействия между расширениями WatchKit и их приложениями для iOS. В этой заключительной части мы рассмотрим кодирование этих опций.

Это будет длинный урок, поэтому вам может быть полезно обратиться к полному проекту на GitHub .

Откройте Xcode и создайте новый проект приложения Single View . Дайте ему соответствующее имя и установите язык Objective-C .

Новый проект

Выберите пункт меню Файл -> Новый -> Цель . Выберите шаблон приложения WatchKit .

Новая цель

Нажмите « Далее» и отмените выбор параметров для создания сцены уведомлений или сцены Glance .

Нажмите « Готово» и « Активировать» в следующем диалоговом окне, чтобы добавить новую цель в проект XCode с новыми группами, созданными для приложения и расширения WatchKit.

Мы начнем с разработки нашего родительского приложения для iOS. Разверните группу классов приложения iOS:

группа классов приложений iOS

Сначала я переименую свой родительский ViewController из «ViewController» во что-то описательное, например «ToDoListViewController»:

рефакторинг

рефакторинг

рефакторинг

В этом приложении мы будем использовать TableView для отображения списка дел. Чтобы упростить настройку TableView , мы TableView новый подкласс из класса UITableViewController . Имея этот подкласс, Xcode вставляет сигнатуры методов для большинства необходимых нам методов, для источника данных TableView и делегата TableView .

Сначала удалите текущие ToDoListViewController.h и ToDoListViewController.m , затем выберите пункт меню File -> New -> File . Выберите шаблон Какао Touch Class :

Нажмите Next , вызовите класс ToDoListTableViewController и убедитесь, что это подкласс UITableViewController :

Откройте новый ToDoListTableViewController.m , вы увидите сигнатуры методов, автоматически созданные XCode. Эти методы должны быть реализованы, потому что интерфейс класса ToDoListTableViewController соответствует протоколам UITableViewDataSource и UITableViewDelegate .

 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { #warning Incomplete implementation, return the number of sections return 0; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { #warning Incomplete implementation, return the number of rows return 0; } 

Для простоты я не буду использовать постоянное хранилище данных для наших задач. Я буду использовать NSMutableArray для работы с элементами в нашем списке дел. Определите этот NSMutableArray , добавив новое свойство в интерфейсе ToDoListTableViewController() внутри ToDoListTableViewController.m :

 @interface ToDoListTableViewController () @property (nonatomic, strong) NSMutableArray *toDoListItems; @end 

Создайте метод для инициализации и вызовите его внутри viewDidLoad . Этот метод называется initializeValues , и он инициализирует toDoListItems NSMutableArray :

 - (void)viewDidLoad { [super viewDidLoad]; [self initializeValues]; } - (void)initializeValues { self.toDoListItems = [NSMutableArray array]; } 

Следующий шаг — начать работу над раскадровкой. В Main.storyboard вы найдете обычный ViewController созданный по умолчанию. Удалите его и перетащите «Table View Controller» с правой панели (Utility Area) на сцену раскадровки.

Хорошей практикой является встраивание контроллера навигации в контроллер табличного представления. Это позволит использовать панель навигации для взаимодействия с новыми контроллерами представления с использованием сегментов. Чтобы встроить контроллер навигации, выберите только что добавленный «контроллер табличного представления» и пункт меню « Редактор» -> «Встроить» -> «контроллер навигации» .

Внутри раскадровки выберите контроллер навигации и в правой панели Xcode «Utilities Panel» откройте вкладку «Attributes». В разделе «View Controller» вы найдете опцию «Is Initial View Controller». Отметьте это как правильный, чтобы сделать TableViewController первым экраном, который появится при запуске приложения.

Начальный просмотр протокола

В Main.storyboard назначьте класс ToDoListTableViewController контроллеру табличного представления с помощью текстового поля « Класс» в Identity Inspector . Это можно сделать, выбрав «TableViewController» в раскадровке и открыв правую панель «Утилиты».

Назначить пользовательский класс

Чтобы добавить новую задачу, мы будем использовать UITextField для ввода текста, а когда нажата UITextField Готово» , добавим новую задачу в табличное представление.

UITextField находится внутри пользовательского представления и будет действовать как заголовок для представления таблицы. Это гарантирует, что поле ввода текста всегда находится над табличным представлением, и упрощает добавление новых задач.

Создайте метод, который возвращает представление заголовка для представления таблицы задач, а затем определите константу для хранения значения высоты ячейки, необходимого для представления таблицы. Мы будем использовать ту же высоту для представления заголовка, чтобы весь интерфейс был согласованным. Добавьте этот метод в ToDoListTableViewController.m :

 - (UIView*)toDoListTableViewHeader { UIView *tableViewHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, TABLE_VIEW_CELL_HEIGHT)]; tableViewHeader.backgroundColor = [UIColor lightGrayColor]; return tableViewHeader; } 

Определите константу TABLE_VIEW_CELL_HEIGHT используемую ниже вводных операторов #import :

 #import "ToDoListTableViewController.h" #define TABLE_VIEW_CELL_HEIGHT 40 

Присвойте UIView возвращенный методом tableHeaderView свойству tableView . Мы сделаем это внутри метода initializeValues :

 - (void)initializeValues { self.toDoListItems = [NSMutableArray array]; self.tableView.tableHeaderView = [self toDoListTableViewHeader]; } 

Я раскомментирую строку self.navigationItem.rightBarButtonItem = self.editButtonItem; внутри метода viewDidLoad моего класса ToDoListTableViewController.m . Это создает кнопку « Правка» справа от панели навигации. Эту кнопку « Редактировать» можно использовать для изменения таблицы «Вид». Один из способов модификации состоит в том, чтобы удалить задачу. Я расскажу больше об удалении дел в этой статье.

 - (void)viewDidLoad { [super viewDidLoad]; // Uncomment the following line to preserve selection between presentations. // self.clearsSelectionOnViewWillAppear = NO; // Uncomment the following line to display an Edit button in the navigation bar for this view controller. self.navigationItem.rightBarButtonItem = self.editButtonItem; [self initializeValues]; } 

Это должно быть результатом после запуска проекта на этом этапе:

Первый запуск приложения

Далее мы установим количество строк в секции для хранения счетчика массива toDoListItems и установим количество секций равным 1. Заменим текущие методы следующими:

 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Return the number of sections. return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the number of rows in the section. return self.toDoListItems.count; } 

Затем определите свойство ToDoListTableViewController() интерфейсе ToDoListTableViewController() , инициализируйте его и настройте метод, который возвращает заголовок табличного представления задач. Мы сделаем ToDoListTableViewController класса ToDoListTableViewController соответствующим протоколу UITextFieldDelegate , чтобы он мог взаимодействовать с новым UITextField :

Внутри ToDoListTableViewController.h :

 @interface ToDoListTableViewController : UITableViewController <UITextFieldDelegate> @end 

Установите ToDoListTableViewController.m в качестве делегата для этого UITextField , изменив эти существующие функции:

 @interface ToDoListTableViewController () @property (nonatomic, strong) NSMutableArray *toDoListItems; @property (nonatomic, strong) UITextField *toDoInputTextField; @end … - (void)initializeValues { self.toDoListItems = [NSMutableArray array]; self.tableView.tableHeaderView = [self toDoListTableViewHeader]; self.toDoInputTextField.delegate = self; } … - (UIView*)toDoListTableViewHeader { UIView *tableViewHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.tableView.frame.size.width, TABLE_VIEW_CELL_HEIGHT)]; tableViewHeader.backgroundColor = [UIColor lightGrayColor]; self.toDoInputTextField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, tableViewHeader.frame.size.width * 0.95, TABLE_VIEW_CELL_HEIGHT * 0.8)]; self.toDoInputTextField.center = CGPointMake(tableViewHeader.center.x, tableViewHeader.center.y); self.toDoInputTextField.placeholder = @"Add an item.."; self.toDoInputTextField.backgroundColor = [UIColor whiteColor]; self.toDoInputTextField.borderStyle = UITextBorderStyleRoundedRect; self.toDoInputTextField.returnKeyType = UIReturnKeyDone; [tableViewHeader addSubview:self.toDoInputTextField]; return tableViewHeader; } 

Это будет конечный результат после добавления этого UITextField в качестве подпредставления для заголовка табличного представления:

Sub View

Вы можете начать вводить что-либо как задание, но когда вы нажимаете кнопку Готово , ничего не происходит. Клавиатура должна скрыться и добавлен новый список дел Чтобы добиться этого, мы реализуем один из UITextField делегата UITextField чтобы скрыть клавиатуру при нажатии кнопки Готово :

 - (BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; return YES; } 

Мы UITextField первым респондентом, и это заставляет клавиатуру прятаться.

Для простоты мы будем использовать существующий метод textFieldShouldReturn: и напишем логику для добавления нового элемента textFieldShouldReturn: . Мы проверим, что поле ввода текста не содержит пустой текст, и добавим этот текст как объект в массив toDoListItems . После этого перезагрузите данные табличного представления, и это должно отобразить все элементы, добавленные в массив toDoListItems :

 - (BOOL)textFieldShouldReturn:(UITextField *)textField { if(![self textIsEmpty:textField.text]) { [self.toDoListItems addObject:textField.text]; [self.tableView reloadData]; } [textField resignFirstResponder]; return YES; } - (BOOL)textIsEmpty:(NSString*)text { NSCharacterSet *whitespace = [NSCharacterSet whitespaceAndNewlineCharacterSet]; NSString *trimmed = [text stringByTrimmingCharactersInSet:whitespace]; if ([trimmed length] == 0) { return YES; } return NO; } 

На этом этапе мы проведем некоторую реорганизацию проекта, создав новую группу ModelClasses для создаваемых моделей и группу ControllerClasses для добавления наших файлов ViewController .

Выберите папку ModelClasses

Создайте новый класс с именем ToDoListTableViewCell, который является подклассом UITableViewCell и используйте его в качестве класса модели для нашей пользовательской ячейки, которую мы вернем в cellForRowAtIndexPath: метод

Создать подкласс

Перемещение файлов в папку ModelClasses

Присвойте этот класс модели ячейке-прототипу таблицы To-DOS View из Main.storyboard :

Назначить пользовательский класс

Затем добавьте метку к этой ячейке прототипа. Этот ярлык будет содержать заголовок каждого нового задания.

Настройте ограничения времени выполнения метки:

Настройте ограничение метки

Настройте ограничение метки

Настройте ограничение метки

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

Добавить идентификатор

Подключите эту метку к классу ToDoListTableViewCell через выход:

Подключить ярлык

 @property (weak, nonatomic) IBOutlet UILabel *toDoItemTitle; 

Чтобы использовать класс ToDoListTableViewCell внутри основного ToDoListTableViewController , импортируйте ToDoListTableViewCell класса ToDoListTableViewCell в ToDoListTableViewController.m :

 #import "ToDoListTableViewController.h" #import "ToDoListTableViewCell.h" 

Внутри cellForRowAtIndexPath: установите текст для метки элемента indexPath.row используя indexPath.row в качестве индекса для получения элементов toDoListItems массива toDoListItems :

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { ToDoListTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ToDoListTableViewCell" forIndexPath:indexPath]; cell.toDoItemTitle.text = self.toDoListItems[indexPath.row]; return cell; } 

Обязательно очистите текст, введенный внутри UITextField после добавления нового UITextField дел, сделайте это внутри textFieldShouldReturn: метод:

 - (BOOL)textFieldShouldReturn:(UITextField *)textField { if(![self textIsEmpty:textField.text]) { [self.toDoListItems addObject:textField.text]; [textField setText:@""]; [self.tableView reloadData]; } [textField resignFirstResponder]; return YES; } 

Скомпилируйте и запустите проект. Добавить новый список дел теперь просто, просто введите текст и нажмите Готово .

Теперь мы можем добавлять новые задачи в наш список, но как насчет удаления существующей?

Я включил кнопку « Редактировать» ранее, когда создавал свой To-Dos TableView. Два важных метода должны быть переопределены, чтобы разрешить удаление элементов. Первый — tableView:canEditRowAtIndexPath: он определяет, являются ли ячейки табличного представления редактируемыми или нет. Вы можете заблокировать действие редактирования для некоторых строк, возвращая NO после проверки их индексов, и при нажатии кнопки « Редактировать» они не будут затронуты.

 - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { // Return NO if you do not want the specified item to be editable. return YES; } 

Второй метод переопределения — tableView:commitEditingStyle:forRowAtIndexPath: где удаляется соответствующий элемент todo и набор анимации:

 - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { [self.toDoListItems removeObjectAtIndex:indexPath.row]; // Delete the row from the data source [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; } } 

После компиляции и запуска удаление строки таблицы теперь должно работать:

Удалить открытую таблицу

Удалить открытую таблицу

Проведите пальцем, чтобы удалить работает здесь. Проведите пальцем по любому списку дел, и появится красная кнопка « Удалить» .

Родительское приложение iOS теперь готово. Далее идет приложение для часов и различные методы передачи сообщений.

Начните с открытия файла Interface.storyboard в папке приложения WatchKit.

Затем перетащите таблицу в контроллер интерфейса. Когда вы перетаскиваете Table , первое, что вы увидите, это контроллер строк таблицы . Это действует как обычные строки, которые вы видите внутри UITableView :

Добавление таблицы

Перетащите метку на контроллер строк таблицы . Это будет содержать заголовок списка дел. Установите горизонтальное положение влево и вертикальное положение в центр . Установите ширину метки относительно контейнера .

Добавление ярлыка

Внутри папки расширения WatchKit создайте класс модели для контроллера ToDoListWatchKitTableRow таблицы с именем ToDoListWatchKitTableRow который является подклассом NSObject .

создать модель класса

Затем я ToDoListWatchKitTableRow класс ToDoListWatchKitTableRow контроллеру ToDoListWatchKitTableRow таблицы под контроллером интерфейса .

Назначить класс

Затем я установлю идентификатор этого контроллера строки таблицы в ToDoListWatchKitTableRow :

Добавить идентификатор

Этот идентификатор важен, когда вы устанавливаете количество строк для таблицы, которую мы только что перетащили в наш контроллер интерфейса . Мы рассмотрим это позже.

Следующим шагом является создание выходного соединения с только что созданной меткой и включение WatchKit.h в класс модели:

 #import <Foundation/Foundation.h> #import <WatchKit/WatchKit.h> @interface ToDoListWatchKitTableRow : NSObject @property (weak, nonatomic) IBOutlet WKInterfaceLabel *toDoListItemTitleLabel; @end 

Первый способ взаимодействия, который мы собираемся исследовать, — это openParentApplication:reply:

Перед этим создайте класс Singleton в базовом проекте, который будет содержать массив toDoListItemsList определенный ранее внутри ToDoListTableViewController.m . Это потому, что нам нужно иметь доступное toDoListItemsList массива toDoListItemsList и быть одинаковыми с любым классом внутри проекта. Шаблоны проектирования Singleton в этой ситуации, потому что нам нужно получить доступ к этому массиву из файла AppDelegate.m и ToDoListTableViewController.m . Этот класс будет создан под целевым приложением iOS, а не под WatchKit. Это означает, что этот класс будет добавлен в группу «ModelClasses» в группе «ToDoList».

Назовите этот класс модели ToDoListData и сделайте его подклассом NSObject .

Внутри ToDoListData.h добавьте:

 #import <Foundation/Foundation.h> @interface ToDoListData : NSObject + (NSMutableArray *)toDoListItems; @end 

И внутри ToDoListData.m :

 #import "ToDoListData.h" @interface ToDoListData() @property (nonatomic, strong) NSMutableArray* toDoListItems; @end @implementation ToDoListData + (ToDoListData *) sharedInstance { static dispatch_once_t onceToken; static ToDoListData *singelton = nil; dispatch_once(&onceToken, ^{ singelton = [[ToDoListData alloc] init]; }); return singelton; } - (id)init { self = [super init]; if(self){ self.toDoListItems = [NSMutableArray array]; } return self; } + (NSMutableArray*)toDoListItems { return [self sharedInstance].toDoListItems; } 

Используйте новый массив из этого класса Singleton внутри ToDoListTableViewController.m . Внутри метода InitializeValues сделайте это изменение для toDoListItemsProperty :

 - (void)initializeValues { self.toDoListItems = [ToDoListData toDoListItems]; self.tableView.tableHeaderView = [self toDoListTableViewHeader]; self.toDoInputTextField.delegate = self; } 

Убедитесь, что вы импортируете ToDoListData.h с помощью #import "ToDoListData.h" .

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

Внутри InterfaceController.m мы используем openParentApplication:reply: для openParentApplication:reply: родительского приложения iOS о openParentApplication:reply: . Добавьте это внутри метода willActivate . Это гарантирует, что приложение Apple Watch запрашивает данные у родительского приложения iOS каждый раз, когда приложение Apple Watch выводится на передний план. Я передаю словарь с ключом и значением, которое мы проверим в AppDelegate.m родительского приложения iOS:

 @interface InterfaceController() @property (nonatomic, strong) NSMutableArray* toDoListItems; @end @implementation InterfaceController - (void)willActivate { // This method is called when watch view controller is about to be visible to user [super willActivate]; // Using openParentapplication: method [WKInterfaceController openParentApplication:@{@"action":@"gettoDoListItems"} reply:^(NSDictionary *replyInfo, NSError *error) { if(error) { NSLog(@"An error happened while opening the parent application : %@", error.localizedDescription); } else { self.toDoListItems = [replyInfo valueForKey:@"toDoListItems"]; } }]; } @end 

Внутри AppDelegate.m родительского приложения iOS мы проверяем значение ключа, используемого с openParentApplication:reply: а затем используем блок reply чтобы отправить ответ родительскому приложению iOS, содержащий значение массива, содержащего -до предметов. Внутри openParentApplication:reply: проверьте, есть ли какие-либо ошибки, и установите локальный массив toDoListItems для хранения значения массива, возвращаемого из ответа. Используйте счетчик этого массива, чтобы установить количество строк таблицы:

 - (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void (^)(NSDictionary *))reply { if([[userInfo valueForKey:@"action"] isEqualToString:@"gettoDoListItems"]) { reply(@{@"toDoListItems":[ToDoListData toDoListItems]}); } } 

Обязательно #import "ToDoListData.h" .

Поскольку мы используем симулятор, сложно запустить приложение iOS до приложения Apple Watch, поэтому добавьте запись по умолчанию для массива toDoListItems чтобы доказать, что этот метод работает (измените существующий метод в ToDoListData.m ):

 - (id)init { self = [super init]; if(self){ self.toDoListItems = [NSMutableArray array]; [self.toDoListItems addObject:@"This is a default to-do."]; } return self; } 

Затем подключите розетку из таблицы под моим интерфейсным контроллером внутри класса Interface.storyboard к моему InterfaceController.h :

Создать аутлет

Класс InterfaceController.h должен выглядеть следующим образом:

 #import <WatchKit/WatchKit.h> #import <Foundation/Foundation.h> @interface InterfaceController : WKInterfaceController @property (weak, nonatomic) IBOutlet WKInterfaceTable *toDoListInterfaceTable; @end 

Чтобы заполнить таблицу задач для расширения WatchKit, создайте метод loadTable внутри InterfaceController.m :

 - (void)loadTable { [self.toDoListInterfaceTable setNumberOfRows:self.toDoListItems.count withRowType:@"ToDoListWatchKitTableRow"]; for(int i = 0; i < self.toDoListItems.count; i++) { ToDoListWatchKitTableRow* row = [self.toDoListInterfaceTable rowControllerAtIndex:i]; row.toDoListItemTitleLabel.text = self.toDoListItems[i]; } } 

Вызовите метод loadTable внутри блока ответа openParentApplication:reply: Обязательно #import "ToDoListWatchKitTableRow.h" .

Класс InterfaceController.m должен выглядеть следующим образом:

 #import "InterfaceController.h" #import "ToDoListWatchKitTableRow.h" @interface InterfaceController() @property (nonatomic, strong) NSMutableArray* toDoListItems; @end @implementation InterfaceController - (void)awakeWithContext:(id)context { [super awakeWithContext:context]; // Configure interface objects here. } - (void)willActivate { // This method is called when watch view controller is about to be visible to user [super willActivate]; [WKInterfaceController openParentApplication:@{@"action":@"gettoDoListItems"} reply:^(NSDictionary *replyInfo, NSError *error) { if(error) { NSLog(@"An error happened while opening the parent application : %@", error.localizedDescription); } else { self.toDoListItems = [replyInfo valueForKey:@"toDoListItems"]; [self loadTable]; } }]; } - (void)loadTable { [self.toDoListInterfaceTable setNumberOfRows:self.toDoListItems.count withRowType:@"ToDoListWatchKitTableRow"]; for(int i = 0; i < self.toDoListItems.count; i++) { ToDoListWatchKitTableRow* row = [self.toDoListInterfaceTable rowControllerAtIndex:i]; row.toDoListItemTitleLabel.text = self.toDoListItems[i]; } } - (void)didDeactivate { // This method is called when watch view controller is no longer visible [super didDeactivate]; } @end 

Одним из важных шагов перед компиляцией приложения на данный момент является открытие симулятора iOS, и в меню « Оборудование» выберите « Внешние дисплеи» , « Apple Watch», а затем 38 или 42 мм .

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

Приложение

Приложение WatchKit

Метод Червоточины

Далее мы попробуем другой способ связи, библиотеку MMWormhole . Сначала установите MMWormhole, используя CocoaPods . Создайте Podfile и добавьте:

 target 'ToDoList' do platform :ios, '9.0' pod 'MMWormhole', '~> 1.1.1' end target 'ToDoList WatchKit Extension' do platform :watchos, '2.0' pod 'MMWormhole', '~> 1.1.1' end 

В этом Podfile мы связываем MMWormhole с обоими целями, приложением iOS и расширением WatchKit.

Включите параметр « Группы приложений» на вкладке « Возможности » в проекте XCode. Создайте новую группу приложений как для приложения iOS, так и для расширения WatchKit. Убедитесь, что обе группы приложений активны:

App Group

App Group

В том же месте, что и Podfile , запустите pod install в командной строке. После завершения установки закройте проект и откройте созданный файл .xcworkspace .

Первым шагом является создание экземпляра класса MMWormhole внутри ToDoListTableViewController.m . Инициализируйте этот экземпляр с идентификатором группы приложений:

 #import "ToDoListTableViewController.h" #import "ToDoListTableViewCell.h" #import "ToDoListData.h" #import "MMWormhole.h" #define TABLE_VIEW_CELL_HEIGHT 40 @interface ToDoListTableViewController () @property (nonatomic, strong) UITextField *toDoInputTextField; @property (nonatomic, strong) NSMutableArray *toDoListItems; @property (nonatomic, strong) MMWormhole *wormhole; @end ... - (void)initializeValues { self.toDoListItems = [ToDoListData toDoListItems]; self.tableView.tableHeaderView = [self toDoListTableViewHeader]; self.toDoInputTextField.delegate = self; self.wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.safwat.development.ToDoList" optionalDirectory:@"wormhole"]; } 

Определите метод, который может отправить сообщение Wormhole. Мы будем использовать этот метод для отправки массива toDoListItems из родительского приложения iOS в расширение WatchKit:

 - (void)passWormholeMessage:(id)messageObject { [self.wormhole passMessageObject:messageObject identifier:@"toDoListItems"]; } 

Затем мы можем вызвать этот метод, где мы вносим изменения в toDoListItems , такие как добавление или удаление дел. Вызовите этот метод внутри textFieldShouldReturn: и tableView:commitEditingStyle:forRowAtIndexPath: методы:

 - (BOOL)textFieldShouldReturn:(UITextField *)textField { if(![self textIsEmpty:textField.text]) { [[ToDoListData toDoListItems] addObject:textField.text]; [textField setText:@""]; [self.tableView reloadData]; [self passWormholeMessage:[ToDoListData toDoListItems]]; } [textField resignFirstResponder]; return YES; } ... - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { [[ToDoListData toDoListItems] removeObjectAtIndex:indexPath.row]; // Delete the row from the data source [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; [self passWormholeMessage:[ToDoListData toDoListItems]]; } } 

Чтобы расширение WatchKit отображало содержимое toDoListItems при первом его открытии, отправьте сообщение Wormhole, содержащее объект массива внутри viewDidLoad объекта ToDoListTableViewController.m :

 - (void)viewDidLoad { [super viewDidLoad]; [self initializeValues]; self.navigationItem.rightBarButtonItem = self.editButtonItem; [self passWormholeMessage:[ToDoListData toDoListItems]]; } 

В группе Расширение WatchKit откройте InterfaceController.m . Определите экземпляр класса MMWormhole и инициализируйте его. Теперь класс должен выглядеть так:

 #import "InterfaceController.h" #import "ToDoListWatchKitTableRow.h" #import "MMWormhole.h" @interface InterfaceController() @property (nonatomic, strong) NSMutableArray* toDoListItems; @property (nonatomic, strong) MMWormhole *wormhole; @end @implementation InterfaceController - (void)awakeWithContext:(id)context { [super awakeWithContext:context]; // Configure interface objects here. self.wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.safwat.development.ToDoList" optionalDirectory:@"wormhole"]; } - (void)willActivate { // This method is called when watch view controller is about to be visible to user [super willActivate]; [self.wormhole listenForMessageWithIdentifier:@"toDoListItems" listener:^(id messageObject) { NSLog(@"messageObject %@", messageObject); self.toDoListItems = messageObject; [self loadTable]; }]; } - (void)loadTable { [self.toDoListInterfaceTable setNumberOfRows:self.toDoListItems.count withRowType:@"ToDoListWatchKitTableRow"]; for(int i = 0; i < self.toDoListItems.count; i++) { ToDoListWatchKitTableRow* row = [self.toDoListInterfaceTable rowControllerAtIndex:i]; row.toDoListItemTitleLabel.text = self.toDoListItems[i]; } } - (void)didDeactivate { // This method is called when watch view controller is no longer visible [super didDeactivate]; } @end 

Попробуйте скомпилировать и запустить проект, открыть родительское приложение iOS, а затем приложение Apple Watch, вы должны увидеть это:

Приложение WatchKit

Расширение WatchKit

Попробуйте добавить новую задачу в приложение iOS, вы должны увидеть результат, мгновенно отраженный в приложении Apple Watch:

Приложение WatchKit

Расширение WatchKit

NSUserDefaults.

Третий метод, который я упомянул, использует NSUserDefaults . Создайте свойство типа NSUserDefaults внутри ToDoListTableViewController.m и инициализируйте его внутри того же метода initializeValues :

 @interface ToDoListTableViewController () @property (nonatomic, strong) UITextField *toDoInputTextField; @property (nonatomic, strong) MMWormhole *wormhole; @property (nonatomic, strong) NSUserDefaults *userDefaults; @end 
 - (void)initializeValues { self.tableView.tableHeaderView = [self toDoListTableViewHeader]; self.toDoInputTextField.delegate = self; self.wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.safwat.development.ToDoList" optionalDirectory:@"wormhole"]; self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.safwat.development.ToDoList"]; } 

Закомментируйте вызовы метода passWormholeMessage: определенного ранее, и мы passWormholeMessage: новый метод, который обрабатывает сохранение объектов в NSUserDefaults :

 - (void)saveToUserDefaults:(id)object { [self.userDefaults setObject:object forKey:@"toDoListItems"]; } ... - (void)viewDidLoad { [super viewDidLoad]; [self initializeValues]; self.navigationItem.rightBarButtonItem = self.editButtonItem; //[self passWormholeMessage:[ToDoListData toDoListItems]]; [self saveToUserDefaults:[ToDoListData toDoListItems]]; } ... - (BOOL)textFieldShouldReturn:(UITextField *)textField { if(![self textIsEmpty:textField.text]) { [[ToDoListData toDoListItems] addObject:textField.text]; [textField setText:@""]; [self.tableView reloadData]; //[self passWormholeMessage:[ToDoListData toDoListItems]]; [self saveToUserDefaults:[ToDoListData toDoListItems]]; } [textField resignFirstResponder]; return YES; } ... - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { [[ToDoListData toDoListItems] removeObjectAtIndex:indexPath.row]; // Delete the row from the data source [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; //[self passWormholeMessage:[ToDoListData toDoListItems]]; [self saveToUserDefaults:[ToDoListData toDoListItems]]; } } 

Внутри расширения WatchKit InterfaceController.m прочитайте данные, сохраненные в NSUserDefaults и загрузите таблицу NSUserDefaults . Сначала определите экземпляр NSUserDefaults а затем инициализируйте его внутри метода awakeWithContext: Класс InterfaceController.m должен выглядеть следующим образом:

 #import "InterfaceController.h" #import "ToDoListWatchKitTableRow.h" #import "MMWormhole.h" @interface InterfaceController() @property (nonatomic, strong) NSMutableArray* toDoListItems; @property (nonatomic, strong) MMWormhole *wormhole; @property (nonatomic, strong) NSUserDefaults *userDefaults; @end @implementation InterfaceController - (void)awakeWithContext:(id)context { [super awakeWithContext:context]; // Configure interface objects here. self.wormhole = [[MMWormhole alloc] initWithApplicationGroupIdentifier:@"group.com.safwat.development.ToDoList" optionalDirectory:@"wormhole"]; self.userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.com.safwat.development.ToDoList"]; } - (void)loadTable { [self.toDoListInterfaceTable setNumberOfRows:self.toDoListItems.count withRowType:@"ToDoListWatchKitTableRow"]; for(int i = 0; i < self.toDoListItems.count; i++) { ToDoListWatchKitTableRow* row = [self.toDoListInterfaceTable rowControllerAtIndex:i]; row.toDoListItemTitleLabel.text = self.toDoListItems[i]; } } - (void)willActivate { // This method is called when watch view controller is about to be visible to user [super willActivate]; // Using openParentapplication: method /* [WKInterfaceController openParentApplication:@{@"action":@"gettoDoListItems"} reply:^(NSDictionary *replyInfo, NSError *error) { if(error) { NSLog(@"An error happened while opening the parent application : %@", error.localizedDescription); } else { self.toDoListItems = [replyInfo valueForKey:@"toDoListItems"]; [self loadTable]; } }]; */ // Using MMWormhole method /* [self.wormhole listenForMessageWithIdentifier:@"toDoListItems" listener:^(id messageObject) { NSLog(@"messageObject %@", messageObject); self.toDoListItems = messageObject; [self loadTable]; }]; */ // Using NSUserDefaults method self.toDoListItems = [self.userDefaults valueForKey:@"toDoListItems"]; [self loadTable]; } - (void)didDeactivate { // This method is called when watch view controller is no longer visible [super didDeactivate]; } @end 

Скомпилировав проект, запустив приложение iOS, а затем приложение Apple Watch, вы должны получить те же результаты, что и раньше, с openParentApplication:reply: и методом MMWormhole .

Вывод

Передача сообщений между приложением iOS и его расширением WatchKit представляет широкий спектр интересных задач программирования при разработке вашего приложения. Любой способ связи может достичь цели, но это будет зависеть от ситуации, приложения, которое его использует, и необходимости обновления данных в реальном времени. Попробуйте протестировать некоторые приложения Apple Watch и подумать о том, как достигается обмен данными.

Дайте мне знать, если у вас есть какие-либо вопросы об этом руководстве или разработке Apple Watch в комментариях ниже.