В последней части этого короткого цикла мы рассмотрели варианты взаимодействия между расширениями WatchKit и их приложениями для iOS. В этой заключительной части мы рассмотрим кодирование этих опций.
Это будет длинный урок, поэтому вам может быть полезно обратиться к полному проекту на GitHub .
Откройте Xcode и создайте новый проект приложения Single View . Дайте ему соответствующее имя и установите язык Objective-C .
Выберите пункт меню Файл -> Новый -> Цель . Выберите шаблон приложения WatchKit .
Нажмите « Далее» и отмените выбор параметров для создания сцены уведомлений или сцены Glance .
Нажмите « Готово» и « Активировать» в следующем диалоговом окне, чтобы добавить новую цель в проект XCode с новыми группами, созданными для приложения и расширения WatchKit.
Мы начнем с разработки нашего родительского приложения для 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
в качестве подпредставления для заголовка табличного представления:
Вы можете начать вводить что-либо как задание, но когда вы нажимаете кнопку Готово , ничего не происходит. Клавиатура должна скрыться и добавлен новый список дел Чтобы добиться этого, мы реализуем один из 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
.
Создайте новый класс с именем ToDoListTableViewCell, который является подклассом UITableViewCell
и используйте его в качестве класса модели для нашей пользовательской ячейки, которую мы вернем в cellForRowAtIndexPath:
метод
Присвойте этот класс модели ячейке-прототипу таблицы 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, скомпилируйте и запустите. Вы должны увидеть что-то вроде следующего:
Метод Червоточины
Далее мы попробуем другой способ связи, библиотеку 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. Убедитесь, что обе группы приложений активны:
В том же месте, что и 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, вы должны увидеть это:
Попробуйте добавить новую задачу в приложение iOS, вы должны увидеть результат, мгновенно отраженный в приложении Apple Watch:
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 в комментариях ниже.