Nessa segunda parte, vou mostrar como fazer a comunicação entre o App do iPhone e o App do Watch. Vou continuar a partir do projeto do post anterior (Desenvolvendo o 1º App para Apple Watch) .
1. iPhone App
Para começar vamos fazer a o App do iPhone, como o foco desse tutorial é a comunicação com o Apple Watch, na primeira etapa do app, vamos programar as funcionalidade de adicionar / remover e marcar / desmarcar o To Do como concluído no iPhone.
Temos então que abrir o projeto feito na Parte I desse tutorial (Swift ou Objectve-C). Primeiro vamos excluir o arquivo ViewController (.swift ou .h e .m)
Agora adicionamos dois novos arquivos ToDoTableViewController que vai herdar da classe UITableViewController (Figura 03) e ToDoNovoViewController que vai herdar de UIViewController (Figura 04).
Agora vamos abrir o arquivo Main.storyboard, nele vamos adicionar um Navigation Controller.
Com o Navigation Controller selecionado, clique na opção Is Initial View Controller no inspector conforme Figura 07, fazendo assim que essa seja a primeira View a ser carregada pelo aplicativo.
No Root View Controller vamos adicionar um Navigation Item e um Item, conforme Figuras 08 e 09.
Vamos mudar o titulo (Title) do Navigation Item para To Do Watch, e o estilo do Bar Button para Add.
Para a Prototype Cells vamos mudar o estilo para Basic e colocar no Identifier todocell. (Figura 11)
Para essa View Controller vamos associar o arquivo ToDoTableViewController conforme Figura 12.
Temos agora que “conectar” o Bar Button com a ViewController que já estava no Storyboard, no menu que aparecerá selecione a opção show. (Figuras 13 e 14).
Nessa View Controller vamos adicionar um Navigation Item, dois Bar Buttons (Save e cancel) um Label (Descrição) e uma Text (Figura 15)
Os Bar Buttons podem ter as seguintes configurações (Figuras 16 e 17).
Essa View Controller vai ser associada com o ToDoNovoViewController (Figura 18).
Vamos aos códigos. Primeiramente vamos precisar de uma nova classe, essa classe vai controlar o armazenamento e o status de cada um dos To Do’s, vamos adicionar um novo arquivo chamado ToDoController. (Figura 19)
Vamos ao código.
@interface ToDoController : NSObject+(NSMutableArray *) toDoList;
+(void) createNewToDo:(NSString *)title;
+(void) deleteTodo:(NSDictionary *)toDoItem;
+(void) toggleToDo:(NSNumber *)toDoId;
@end
“`
[/markdown]
+(NSMutableArray *) toDoList
{
//Inicializa a lista de ToDos com os item já gravados ou uma nova lista vazia.
if(!_toDoList)
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
_toDoList = [[defaults arrayForKey:TODO_LIST_KEY] mutableCopy];if(!_toDoList)
_toDoList = [NSMutableArray new];
}return _toDoList;
}+(void)updateToDoList
{
//Grava a lista atual de ToDos
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[self toDoList] forKey:TODO_LIST_KEY];
[defaults synchronize];
}+(void)createNewToDo:(NSString *)title
{
NSNumber *toDoId;
NSMutableArray *allToDos = [self toDoList];//Verifica se existe algum ToDo na lista e define o Id do proximo a ser inserido
if(allToDos.count == 0)
toDoId = @1;
else
toDoId = @([[allToDos valueForKeyPath:@”@max.toDoId”] integerValue] + 1);[allToDos addObject:@{TODO_ID_KEY : toDoId,
TODO_TITLE_KEY : title,
TODO_DONE_KEY : @(NO)}];[self updateToDoList];
}+(void)deleteTodo:(NSDictionary *)toDoItem
{
[_toDoList removeObject:toDoItem];
[self updateToDoList];
}+(void)toggleToDo:(NSNumber *)toDoId
{
NSMutableArray *list= [self toDoList];NSDictionary *toDoItem = [[list filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@”toDoId = %@”, toDoId]] firstObject];if(toDoItem)
{
NSUInteger idx = [list indexOfObject:toDoItem];[list removeObjectAtIndex:idx];
[list insertObject:@{TODO_ID_KEY : toDoId,
TODO_TITLE_KEY : [toDoItem objectForKey:TODO_TITLE_KEY],
TODO_DONE_KEY : @(![[toDoItem objectForKey:TODO_DONE_KEY] boolValue])}
atIndex:idx];[self updateToDoList];
}
}@end“`
[/markdown]
@implementation ToDoTableViewController
#define CELL_ID @”todocell”
– (void)viewDidLoad {
[super viewDidLoad];
}
– (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.tableView reloadData];
}
#pragma mark – Table view data source
– (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
– (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [ToDoController toDoList].count;
}
– (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CELL_ID forIndexPath:indexPath];
NSDictionary *toDoItem = [[ToDoController toDoList] objectAtIndex:indexPath.row];
[cell.textLabel setText:[toDoItem objectForKey:TODO_TITLE_KEY]];
[cell setAccessoryType: [[toDoItem objectForKey:TODO_DONE_KEY] boolValue] ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone];
return cell;
}
// Override to support editing the table view.
– (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data sourceNSDictionary *toDoItem = [[ToDoController toDoList] objectAtIndex:indexPath.row];
[ToDoController deleteTodo:toDoItem];
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSDictionary *toDoItem = [[ToDoController toDoList] objectAtIndex:indexPath.row];
[ToDoController toggleToDo:[toDoItem objectForKey:TODO_ID_KEY]];[tableView reloadData];
}
@end
“`
[/markdown]
static var toDoList = ToDoController.loadToDoList()
static func loadToDoList() -> [[String:AnyObject]] {
//Inicializa a lista de ToDos com os item já gravados ou uma nova lista vazia.
let defaults = NSUserDefaults.standardUserDefaults()
if let toDos = defaults.arrayForKey(TODO_LIST_KEY) {
return toDos as! [[String:AnyObject]]
}
return []
}
static func updateToDoList() {
//Grava a lista atual de ToDos
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(toDoList, forKey: TODO_LIST_KEY)
defaults.synchronize()
}
static func crateNewToDo(title: String) {
var toDoId:Int
//Verifica se existe algum ToDo na lista e define o Id do proximo a ser inserido
if(toDoList.count == 0) {
toDoId = 1
}
else {
toDoId = toDoList.reduce(Int.min) { max($0, $1[TODO_ID_KEY] as! Int) } + 1
}
toDoList.append([TODO_ID_KEY : toDoId,
TODO_TITLE_KEY : title,
TODO_DONE_KEY : false])
ToDoController.updateToDoList()
}
static func deleteTodo(toDoItem: [String:AnyObject]) {
if let idx = toDoList.indexOf( { $0[TODO_ID_KEY] as! Int == toDoItem[TODO_ID_KEY] as! Int } ) {
toDoList.removeAtIndex(idx)
}
ToDoController.updateToDoList()
}
static func toggleToDo(toDoId: Int) {if let idx = toDoList.indexOf( { $0[TODO_ID_KEY] as! Int == toDoId } ) {
toDoList[idx][TODO_DONE_KEY] = !(toDoList[idx][TODO_DONE_KEY] as! Bool)
ToDoController.updateToDoList()
}
}
}
“`
[/markdown]
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// MARK: – Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ToDoController.toDoList.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(CELL_ID, forIndexPath: indexPath)
// Configure the cell…
let toDoItem = ToDoController.toDoList[indexPath.row]cell.textLabel!.text = (toDoItem[ToDoController.TODO_TITLE_KEY] as! String)
cell.accessoryType = toDoItem[ToDoController.TODO_DONE_KEY] as! Bool ? UITableViewCellAccessoryType.Checkmark : UITableViewCellAccessoryType.None
return cell
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
// Delete the row from the data sourcelet toDoItem = ToDoController.toDoList[indexPath.row]
ToDoController.deleteTodo(toDoItem)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)let toDoItem = ToDoController.toDoList[indexPath.row]
ToDoController.toggleToDo(toDoItem[ToDoController.TODO_ID_KEY] as! Int)tableView.reloadData()
}
}
“`
[/markdown]
@IBOutlet weak var textToDoTitle: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// Mark : Actions
@IBAction func saveNovoTodo(sender: AnyObject) {
ToDoController.crateNewToDo(textToDoTitle.text!)
navigationController?.popToRootViewControllerAnimated(true)
}
@IBAction func cancelNovoToDo(sender: AnyObject) {
navigationController?.popToRootViewControllerAnimated(true)
}
}
“`
[/markdown]
Para finalizar essa parte, temos que fazer as associações dos botões e da caixa de texto do ToDoNovoViewController com 0 código que acabamos de escrever conforme figuras 20, 21 e 22.
Com isso já podemos testar o lado do iPhone, basta selecionar ToDoWatch
2. Apple Watch
Agora vamos mexer nos arquivos do ToDoWatch WatchKit Extension. começaremos fazendo duas alterações na classe ToDoItem. A Primeira delas é criar uma classe para controle dos ToDos no relógio, vamos chama-la também de ToDoController com o seguinte código.
#define TODO_LIST_KEY @”toDoList”
#define TODO_ID_KEY @”toDoId”
#define TODO_TITLE_KEY @”title”
#define TODO_DONE_KEY @”done”
#define TODO_SHARE_GROUP_NAME @”group.io.redspark.ToDoWatch”
@class ToDoItem;
@interface ToDoController : NSObject+(NSMutableArray *) toDoList;
+(void) addToDoItem:(NSDictionary *)todoItem;
+(void) toggleToDo:(NSNumber *)toDoId;
+(void) updateToDoList;
+(void) clearAll;
@end
“`
[/markdown]
@implementation ToDoControllerstatic NSMutableArray *_toDoList;
+(NSMutableArray *) toDoList
{
if(!_toDoList)
{
NSUserDefaults *defaults = [[NSUserDefaults standardUserDefaults]
initWithSuiteName:TODO_SHARE_GROUP_NAME];
_toDoList = [[defaults arrayForKey:TODO_LIST_KEY] mutableCopy];
if(!_toDoList)
_toDoList = [NSMutableArray new];
}
return _toDoList;
}
+(void)addToDoItem:(NSDictionary *)todoItem
{
[[self toDoList] addObject:todoItem];
[self updateToDoList];
}
+(void)toggleToDo:(NSNumber *)toDoId
{
NSMutableArray *list= [self toDoList];
NSDictionary *toDoItem = [[list filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@”toDoId = %@”, toDoId]] firstObject];
if(toDoItem)
{
NSUInteger idx = [list indexOfObject:toDoItem];
[list removeObjectAtIndex:idx];
[list insertObject:@{TODO_ID_KEY : toDoId,
TODO_TITLE_KEY : [toDoItem objectForKey:TODO_TITLE_KEY],
TODO_DONE_KEY : @(![[toDoItem objectForKey:TODO_DONE_KEY] boolValue])}
atIndex:idx];
[self updateToDoList];
}
}
+(void)updateToDoList
{
NSUserDefaults *defaults = [[NSUserDefaults standardUserDefaults]
initWithSuiteName:TODO_SHARE_GROUP_NAME];
[defaults setObject:[self toDoList] forKey:TODO_LIST_KEY];
[defaults synchronize];
}
+(void)clearAll
{
_toDoList = [NSMutableArray new];
}
@end
“`
[/markdown]
static let TODO_LIST_KEY = “toDoList”
static let TODO_ID_KEY = “toDoId”
static let TODO_TITLE_KEY = “title”
static let TODO_DONE_KEY = “done”
static let TODO_SHARE_GROUP_NAME = “group.io.redspark.ToDoWatch”
static var toDoList = ToDoController.loadToDoList()
static func loadToDoList() -> [[String:AnyObject]] {
//Inicializa a lista de ToDos com os item já gravados ou uma nova lista vazia.
let defaults = NSUserDefaults.init(suiteName: TODO_SHARE_GROUP_NAME)
if let toDos = defaults!.arrayForKey(TODO_LIST_KEY) {
return toDos as! [[String:AnyObject]]
}
return []
}
static func updateToDoList() {
//Grava a lista atual de ToDos
let defaults = NSUserDefaults.init(suiteName: TODO_SHARE_GROUP_NAME)
defaults!.setObject(toDoList, forKey: TODO_LIST_KEY)
defaults!.synchronize()
}
static func addToDoItem(toDoItem: [String:AnyObject]) {
ToDoController.toDoList.append(toDoItem)
ToDoController.updateToDoList()
}
static func toggleToDo(toDoId: Int) {
if let idx = toDoList.indexOf( { $0[TODO_ID_KEY] as! Int == toDoId } ) {
toDoList[idx][TODO_DONE_KEY] = !(toDoList[idx][TODO_DONE_KEY] as! Bool)
}
ToDoController.updateToDoList()
}
static func clearAll() {
ToDoController.toDoList = []
}
}
“`
[/markdown]
Podemos perceber que esse código tem algumas diferenças para o ToDoController do iPhone, a principal delas é na inicialização do NSDefaults (Classe responsável pela persistência dos ToDos, no qual temos um parâmetro suiteName.
Objectve-C
[markdown]
“`
NSUserDefaults *defaults = [[NSUserDefaults standardUserDefaults]
initWithSuiteName:TODO_SHARE_GROUP_NAME];
“`
[/markdown]
Swift
[markdown]
“`
let defaults = NSUserDefaults.init(suiteName: TODO_SHARE_GROUP_NAME)
“`
[/markdown]
Temos essa diferença, pois o Apple Watch faz a persistência dos dados no iPhone, e precisamos indicar pra ele como identificar essa área.
Precisamos configurar um container. (no nosso caso group.io.redspark.ToDoWatch) no projeto, em Capabilities.
Depois de habilitado o App Groups, precisamos criar o identificador, clicando em “+” e adicionando o nome “group.io.redspark.ToDoWatch“.
Agora precisamos fazer algumas alterações nas classes que fizemos na primeira parte desse tutorial. Vamos adicionar mais uma propriedade chamada toDoId e um construtor na classe ToDoItem. O código em objective-c e swift deve ficar conforme exemplo abaixo.
Vamos alterar agora alguns métodos da classe InterfaceController, conforme códigos abaixo.
Primeiro vamos REMOVER os seguintes trechos de código:
[markdown]
“`
@property (strong, nonatomic) NSMutableArray *toDoItems;
“`
[/markdown]
[markdown]
“`
-(void)createTodoList
{
//Cria 5 itens e inicializa com um titulo padrao e como nao finalizado
[self setToDoItems:[NSMutableArray new]];
for (NSInteger idx = 1; idx <= 5; idx++) {
ToDoItem *newItem = [[ToDoItem alloc] init];
[newItem setDone:NO];
[newItem setTitle:[NSString stringWithFormat:@”To Do Item %ld”, (long)idx]];
[self.toDoItems addObject:newItem];
}
}
“`
[/markdown]
[markdown]
“`
var toDoItems = [ToDoItem]()
“`
[/markdown]
[markdown]
“`
func createTodoList() {
//Cria 5 itens e inicializa com um titulo padrao e como nao finalizado
for idx in 1…5 {
let newItem = ToDoItem()
newItem.done = false
newItem.title = “To Do Item \(idx)”
toDoItems.append(newItem)
}
}
“`
[/markdown]
Feito isso, vamos modificar os seguintes métodos pra utilizar a classe ToDoController no lugar da propriedade que acabamos de remover.
Para Objective-C temos também que importar o ToDoController.
[markdown]
“`
#import “ToDoController.h”
“`
[/markdown]
[markdown]
“`
– (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
//Preenche a tabela
[self setupTable];
}
“`
[/markdown]
[markdown]
“`
-(void)table:(WKInterfaceTable *)table didSelectRowAtIndex:(NSInteger)rowIndex
{
//Marca como Feito ou não feito (inverte o valor a cada click)
NSDictionary *toDoItem = [[ToDoController toDoList] objectAtIndex:rowIndex];
[ToDoController toggleToDo:[toDoItem objectForKey:TODO_ID_KEY]];
//Preenche a tabela
[self setupTable];
}
“`
[/markdown]
[markdown]
“`
-(void)setupTable
{
[self.table setNumberOfRows:[ToDoController toDoList].count withRowType:@”ToDoRow”];
//Para cada item da lista configura a linha na tabela
for (NSInteger idx = 0; idx < self.table.numberOfRows; idx++) {
ToDoRow *toDoRow = [self.table rowControllerAtIndex:idx];
ToDoItem *toDoItem = [[ToDoItem alloc] initWithDictionary:[[ToDoController toDoList] objectAtIndex:idx]];
[toDoRow configRowWithItem:toDoItem];
}
}
“`
[/markdown]
[markdown]
“`
override func table(table: WKInterfaceTable, didSelectRowAtIndex rowIndex: Int) {
//Marca como Feito ou não feito (inverte o valor a cada click)
let toDoItem = ToDoController.toDoList[rowIndex]
ToDoController.toggleToDo(toDoItem[ToDoController.TODO_ID_KEY] as! Int)
//Preenche a tabela
setupTable()
}
“`
[/markdown]
[markdown]
“`
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
//Preenche a tabela
setupTable()
}
“`
[/markdown]
[markdown]
“`
func setupTable() {
//Seta o numero de item da lista
table.setNumberOfRows(ToDoController.toDoList.count, withRowType: “ToDoRow”)
//Para cada item da lista configura a linha na tabela
for idx in 0…table.numberOfRows {
if let toDoRow = table.rowControllerAtIndex(idx) as? ToDoRow {
let toDoItem = ToDoItem.init(fromDictionary: ToDoController.toDoList[idx])
toDoRow.configRowWithItem(toDoItem)
}
}
}
“`
[/markdown]
3. Comunicação
Para fazer a comunicação entre os dois aplicativos vamos usar a classe WCSession e o protocolo WCSessionDelegate, ambos contidos no Watch Connectivity Framework.
Essa classe é responsável pela troca de dados entre os dois dispositivos e contém, entre outros métodos, os seguintes:
Esse método é utilizado para enviar as informações mais recentes para o outro device, que pode usar essa informação para atualizar seu estado. Esse método substitui o os dados anteriores que não foram sincronizados, ou seja deve ser utilizado somente quando as últimas informações são relevantes.
Esse método transfere um dicionário de dados, e ao contrário do anterior, ele adiciona as dodos em uma fila, e o outro device pode tratar todos eles. Todos os dados são entregues, mesmo se o aplicativo estiver em backgroud ou terminado.
Esse método entrega a mensagem imediatamente, desde que os dois aplicativos estejam rodando simultaneamente, é necessário, antes de usa-lo, verificar se existe conectividade entre os dois dispositivos.
Maiores detalhes sobre esse framework podem serem vistos nesse vídeo gravado na WWDC 2015.
Agora vamos fazer as modificações necessárias para a comunicação entre os aplicativos.
Nós vamos utilizar no App do iPhone o método updateApplicationContext:error: para mandar todos os To Dos não finalizados para o relógio, e no App do relógio, vamos utilizar o transferUserInfo para mandar pro iPhone cada um dos item marcados como finalizado.
Precisamos inicializar e ativar a classe, é recomendável inicializa-la logo que o aplicativo for inicializado, sendo assim vamos editar as classes AppDelegate e ExtensionDelegate, respectivamente no App do iPhone e no App do relógio. As mudanças necessárias são:
1 – Importar o framework.
(Não podemos esquecer de “avisar” a classe que ele precisa implementar o WCSessionDelegate, adicionando o protocolo na definição da classe.)
[markdown]
“`
#import <WatchConnectivity/WatchConnectivity.h>
@interface AppDelegate : UIResponder <UIApplicationDelegate, WCSessionDelegate>
“`
[/markdown]
[markdown]
“`
#import <WatchConnectivity/WatchConnectivity.h>
@interface ExtensionDelegate : NSObject <WKExtensionDelegate, WCSessionDelegate>
“`
[/markdown]
[markdown]
“`
import WatchConnectivity
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate
“`
[/markdown]
[markdown]
“`
import WatchConnectivity
class ExtensionDelegate: NSObject, WKExtensionDelegate, WCSessionDelegate
“`
[/markdown]
2 – Inicializar a classe WCSession.
Essa inicialização é feira no método didFinishLaunchingWithOptions no iPhone e no applicationDidFinishLaunching no relógio.
[markdown]
“`
– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//configura a sessao de cominicacao
if([WCSession isSupported])
{
[[WCSession defaultSession] setDelegate:self];
[[WCSession defaultSession] activateSession];
}
return YES;
}
“`
[/markdown]
[markdown]
“`
– (void)applicationDidFinishLaunching {
// Perform any final initialization of your application.
//configura a sessao de cominicacao
if([WCSession isSupported])
{
[[WCSession defaultSession] setDelegate:self];
[[WCSession defaultSession] activateSession];
}
}
“`
[/markdown]
[markdown]
“`
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
//configura a sessao de cominicacao
if WCSession.isSupported() {
WCSession.defaultSession().delegate = self;
WCSession.defaultSession().activateSession()
}
return true
}
“`
[/markdown]
[markdown]
“`
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
//configura a sessao de cominicacao
if WCSession.isSupported() {
WCSession.defaultSession().delegate = self;
WCSession.defaultSession().activateSession()
}
}
“`
[/markdown]
3 – Enviar as informações de um app para o outro.
O envio das dados de um lado para o outro vai ser feito no ToDoController, tanto do iPhone como do relógio.
No app do iPhone vamos adicionar um método chamado sendToDosToWatch. (Lembrando que precisamos importar o Watch Connectivity Framework.) e vamos chama-lo no updateToDoList.
[markdown]
“`
+(void)sendToDosToWatch
{
//Filtra os todos nao fimalizados
NSArray *toDoUndone = [[self toDoList] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@”done = %@”, @(NO)]];
NSDictionary *context = @{TODO_LIST_KEY : toDoUndone};
//envia os todos para o relogio
[[WCSession defaultSession] updateApplicationContext:context
error:nil];
}
“`
[/markdown]
[markdown]
“`
+(void)updateToDoList
{
//Grava a lista atual de ToDos
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:[self toDoList] forKey:TODO_LIST_KEY];
[defaults synchronize];
[self sendToDosToWatch];
}
“`
[/markdown]
[markdown]
“`
static func sendToDosToWatch() {
//Filtra os todos nao fimalizados
let toDoUndone = toDoList.filter( {!($0[TODO_DONE_KEY] as! Bool)})
let context:[String:AnyObject] = [TODO_LIST_KEY : toDoUndone]
do {
//envia os todos para o relogio
try WCSession.defaultSession().updateApplicationContext(context)
}
catch {
}
}
“`
[/markdown]
[markdown]
“`
static func updateToDoList() {
//Grava a lista atual de ToDos
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(toDoList, forKey: TODO_LIST_KEY)
defaults.synchronize()
sendToDosToWatch()
}
“`
[/markdown]
No app do relógio vamos apenas adicionar a chamada para envio do item que foi marcado como finalizado direto no método toggleToDo.
[markdown]
“`
+(void)toggleToDo:(NSNumber *)toDoId
{
NSMutableArray *list= [self toDoList];
NSDictionary *toDoItem = [[list filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@”toDoId = %@”, toDoId]] firstObject];
if(toDoItem)
{
NSUInteger idx = [list indexOfObject:toDoItem];
[list removeObjectAtIndex:idx];
[list insertObject:@{TODO_ID_KEY : toDoId,
TODO_TITLE_KEY : [toDoItem objectForKey:TODO_TITLE_KEY],
TODO_DONE_KEY : @(![[toDoItem objectForKey:TODO_DONE_KEY] boolValue])}
atIndex:idx];
[self updateToDoList];
[[WCSession defaultSession] transferUserInfo:@{@”toogle” : toDoId}];
}
}
“`
[/markdown]
[markdown]
“`
static func toggleToDo(toDoId: Int) {
if let idx = toDoList.indexOf( { $0[TODO_ID_KEY] as! Int == toDoId } ) {
toDoList[idx][TODO_DONE_KEY] = !(toDoList[idx][TODO_DONE_KEY] as! Bool)
ToDoController.updateToDoList()
WCSession.defaultSession().transferUserInfo([“toogle”: toDoList[idx][TODO_DONE_KEY] as! Int ])
}
}
“`
[/markdown]
4 – Implementar os métodos que vamos usar o WCSessionDelegate.
Os métodos do delegate que devem ser implementados são :
didReceiveApplicationContext nja classe ExtensionDelegate no relógio
Esses métodos são responsáveis por receber os dados que chegam do outro device, o que vamos codificar neles é, receber os dados e atualizar a lista de ToDos, como esta lista fica em uma outra controller em ambos os projetos, precisamos criar um protocolo (WatchCommProtocol) e uma propriedade (watchCommDelegate)
em cada um das classes (AppDelegate e ExtensionDelegate)
[markdown]
“`
#import <UIKit/UIKit.h>
#import <WatchConnectivity/WatchConnectivity.h>
@protocol WatchCommProtocol
-(void)itemToogled:(NSNumber *)toDoId;
@end
@interface AppDelegate : UIResponder <UIApplicationDelegate, WCSessionDelegate>
@property (strong, nonatomic) UIWindow *window;
@property (strong, nonatomic) id watchCommDelegate;
@end
“`
[/markdown]
[markdown]
“`
#import <WatchKit/WatchKit.h>
#import <WatchConnectivity/WatchConnectivity.h>
@protocol WatchCommProtocol
-(void)contextReceived:(NSDictionary *)context;
@end
@interface ExtensionDelegate : NSObject <WKExtensionDelegate, WCSessionDelegate>
@property (strong, nonatomic) id watchCommDelegate;
@end
“`
[/markdown]
[markdown]
“`
import UIKit
import WatchConnectivity
protocol WatchCommProtocol {
func itemToogled(ToDoId: Int)
}
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
var window: UIWindow?
var watchCommDelegate : WatchCommProtocol?
[…]
“`
[/markdown]
[markdown]
“`
import WatchKit
import WatchConnectivity
protocol WatchCommProtocol {
func contextReceived(context : [String:AnyObject])
}
class ExtensionDelegate: NSObject, WKExtensionDelegate, WCSessionDelegate {
var watchCommDelegate : WatchCommProtocol?
“`
[/markdown]
Feito isso podemos agora implementar de fato os métodos do WCSessionDelegate e dentro de cada um deles vamos chamar a classe responsável pro atualizar as listas de ToDos.
[markdown]
“`
[…]
-(void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary *)userInfo
{
if([userInfo.allKeys containsObject:@”toogle”])
if(self.watchCommDelegate)
[self.watchCommDelegate itemToogled:[userInfo objectForKey:@”toogle”]];
}
“`
[/markdown]
[markdown]
“`
[…]
#pragma mark – WCSessionDelegate
-(void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary *)applicationContext
{
if(self.watchCommDelegate)
[self.watchCommDelegate contextReceived:applicationContext];
}
“`
[/markdown]
[markdown]
“`
[…]
// Mark : WCSessionDelegate
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let toDoId = userInfo[“toogle”] {
watchCommDelegate?.itemToogled(toDoId as! Int)
}
}
“`
[/markdown]
[markdown]
“`
[…]
// Mark : WCSessionDelegate
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
watchCommDelegate?.contextReceived(applicationContext)
}
“`
[/markdown]
5 – Associar a classe da lista de ToDos com o delegate que recebe as informações do outro app.
Mais uma vez, vamos mudar as classes ToDoTableViewController (iPhone) e InterfaceController (relógio)
[markdown]
“`
#import
#import “AppDelegate.h”
@interface ToDoTableViewController : UITableViewController
@end
“`
[/markdown]
[markdown]
“`
– (void)viewDidLoad {
[super viewDidLoad];
AppDelegate *appdelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appdelegate setWatchCommDelegate:self];
}
“`
[/markdown]
[markdown]
“`
#pragma mark – WatchCommProtocol
-(void)itemToogled:(NSNumber *)toDoId {
[ToDoController toggleToDo:toDoId];
[self.tableView reloadData];
}
“`
[/markdown]
[markdown]
“`
#import
#import
#import “ExtensionDelegate.h”
@interface InterfaceController : WKInterfaceController
@end
“`
[/markdown]
[markdown]
“`
– (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)[[WKExtension sharedExtension] delegate];
[extensionDelegate setWatchCommDelegate:self];
//Preenche a tabela
[self setupTable];
}
“`
[/markdown]
[markdown]
“`
#pragma mark – WatchCommProtocol
-(void)contextReceived:(NSDictionary *)context
{
NSArray *toDos = [context objectForKey:@”toDoList”];
[ToDoController clearAll];
for (NSInteger idx = 0; idx < toDos.count; idx++) {
NSDictionary *toDoItem = [toDos objectAtIndex:idx];
[ToDoController addToDoItem:toDoItem];
}
[self setupTable];
}
“`
[/markdown]
[markdown]
“`
class ToDoTableViewController: UITableViewController, WatchCommProtocol
“`
[/markdown]
[markdown]
“`
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.watchCommDelegate = self
}
“`
[/markdown]
[markdown]
“`
// Mark : WatchCommProtocol
func itemToogled(ToDoId: Int) {
ToDoController.toggleToDo(ToDoId)
tableView.reloadData()
}
“`
[/markdown]
[markdown]
“`
class InterfaceController: WKInterfaceController, WatchCommProtocol
“`
[/markdown]
[markdown]
“`
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let extensionDelegate = WKExtension.sharedExtension().delegate as! ExtensionDelegate
extensionDelegate.watchCommDelegate = self
//Preenche a tabela
setupTable()
}
“`
[/markdown]
[markdown]
“`
// Mark : WatchCommProtocol
func contextReceived(context: [String : AnyObject]) {
let toDos = context[“toDoList”] as! [[String:AnyObject]]
ToDoController.clearAll()
for toDo in toDos {
ToDoController.addToDoItem(toDo)
}
setupTable()
}
“`
[/markdown]
Com isso finalizamos a segunda parte desse tutorial. O resultado pode ser visto nas imagens a seguir.
Você pode também fazer o download do código fonte nas duas linguagens aqui.