Técnico

Desenvolvendo o 1º App para Apple Watch (Parte II)

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)

Figura 1

Figura 1

Agora adicionamos dois novos arquivos  ToDoTableViewController que vai herdar da classe UITableViewController (Figura 03) e  ToDoNovoViewController que vai herdar de UIViewController (Figura 04).

New File

Figura 02

New File

Figura 03

New File

Figura 04

Hierarchy

Figura 05

Agora vamos abrir o arquivo Main.storyboard, nele vamos adicionar um Navigation Controller.

Figura 06

Figura 06

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.

Figura 07

Figura 07

No Root View Controller vamos adicionar um Navigation Item e um Item, conforme Figuras 08 e 09.

Figura 08

Figura 08

Figura 09

Figura 09

Vamos mudar o titulo (Title) do Navigation Item para To Do Watch, e o estilo do Bar Button para Add.

Figura 10

Figura 10

Para a Prototype Cells vamos mudar o estilo para Basic e colocar no Identifier todocell. (Figura 11)

Figura 11

Figura 11

Para essa View Controller vamos associar o arquivo ToDoTableViewController conforme Figura 12.

Figura 12

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).

Figura 13

Figura 13

 

Figura 14

Figura 14

Nessa View Controller vamos adicionar um Navigation Item, dois Bar Buttons (Save e cancel) um Label (Descrição) e uma Text (Figura 15)

Figura 15

Figura 15

Os Bar Buttons podem ter as seguintes configurações (Figuras 16 e 17).

Figura 16

Figura 16

 

Figura 17

Figura 17

Essa View Controller vai ser associada com o ToDoNovoViewController (Figura 18).

Figura 18

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)

Figura 19

Figura 19

Vamos ao código.

  • Objective-C (ToDoController.h)
[markdown]
“`#import <Foundation/Foundation.h>
#define TODO_LIST_KEY @”toDoList”
#define TODO_ID_KEY @”toDoId”
#define TODO_TITLE_KEY @”title”
#define TODO_DONE_KEY @”done”

@interface ToDoController : NSObject+(NSMutableArray *) toDoList;
+(void) createNewToDo:(NSString *)title;
+(void) deleteTodo:(NSDictionary *)toDoItem;
+(void) toggleToDo:(NSNumber *)toDoId;

@end

“`
[/markdown]

  • Objective-C (ToDoTableViewController.m)
[markdown]
“`
#import “ToDoController.h”
@implementation ToDoControllerstatic NSMutableArray *_toDoList;

+(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]

  • Objective-C (ToDoTableViewController.m)
[markdown]
“`
#import “ToDoTableViewController.h”
#import “ToDoController.h”@interface ToDoTableViewController ()
@end

@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]

  • Swift (ToDoController.swift)
[markdown]
“`
import UIKitclass ToDoController {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 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]

  • Swift (ToDoTableViewController.swift)
[markdown]
“`
import UIKitclass ToDoTableViewController: UITableViewController {let CELL_ID = “todocell”

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]

  • Swift (ToDoNovoViewController.swift)
[markdown]
“`import UIKitclass
ToDoNovoViewController: UIViewController {

@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.

Figura 20

Figura 20

Figura 21

Figura 21

Figura 22

Figura 22

Com isso já podemos testar o lado do iPhone, basta selecionar ToDoWatch

ToDoWatchSchema

e clicar em Run (redspark-blog-digital).

 

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.

  • Objectve-C (ToDoController.h)
[markdown]
“`#import <Foundation/Foundation.h&gt:

#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]

  • Objectve-C (ToDoController.m)
[markdown]
“`#import “ToDoController.h”
#import “ToDoItem.h”

@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]

  • Swift (ToDoController.swift)
[markdown]
“`
import UIKitclass ToDoController: NSObject {

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.

Figura 23

Figura 23

Depois de habilitado o App Groups, precisamos criar o identificador, clicando em “+” e adicionando o nome “group.io.redspark.ToDoWatch“.

Figura 24

Figura 24

 

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.

  • Objective-C (ToDoItem.h)
[markdown]
“`
#import <Foundation/Foundation.h>@interface ToDoItem : NSObject@property (strong, nonatomic) NSNumber *toDoId;
@property (nonatomic) BOOL done;
@property (strong, nonatomic) NSString *title;-(instancetype)initWithDictionary:(NSDictionary *)toDoDictionary;@end
“`
[/markdown]
  • Objective-C (ToDoItem.m)
[markdown]
“`
#import “ToDoItem.h”
#import “ToDoController.h”@implementation ToDoItem-(instancetype)initWithDictionary:(NSDictionary *)toDoDictionary
{
self = [super init];
if (self) {
[self setToDoId:[toDoDictionary objectForKey:TODO_ID_KEY]];
[self setTitle:[toDoDictionary objectForKey:TODO_TITLE_KEY]];
[self setDone:[[toDoDictionary objectForKey:TODO_DONE_KEY] boolValue]];
}return self;
}@end
“`
[/markdown]
  • Swift (ToDoItem.swift)
[markdown]
“`
import WatchKitclass ToDoItem: NSObject {var toDoId: Int = 0
var done: Bool = false
var title: String = “”init(fromDictionary toDoDictionary:[String:AnyObject]) {
self.toDoId = toDoDictionary[ToDoController.TODO_ID_KEY] as! Int
self.title = toDoDictionary[ToDoController.TODO_TITLE_KEY] as! String
self.done = toDoDictionary[ToDoController.TODO_DONE_KEY] as! Bool
}
}
“`
[/markdown]

Vamos alterar agora alguns métodos da classe InterfaceController, conforme códigos abaixo.

Primeiro vamos REMOVER os seguintes trechos de código:

  1. Propriedade toDoItems.
  2. Método createTodoList.
  • Objectve-C

[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]

  • Swift

[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.

  1. awakeWithContext
  2. didSelectRowAtIndex
  3. setupTable

Para Objective-C temos também que importar o ToDoController.

  • Objective-C

[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]

  • Swift

[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:

  • updateApplicationContext:error:

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.

  • transferUserInfo

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.

  • sendMessage:replyHandler:errorHandler:

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.)

  • Objective-C (AppDelegate.h)

[markdown]
“`

#import <WatchConnectivity/WatchConnectivity.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate, WCSessionDelegate>

“`
[/markdown]

  • Objective-C (ExtensionDelegate.h)

[markdown]
“`

#import <WatchConnectivity/WatchConnectivity.h>

@interface ExtensionDelegate : NSObject <WKExtensionDelegate, WCSessionDelegate>

“`
[/markdown]

  • Swift (AppDelegate.Swift)

[markdown]
“`

import WatchConnectivity

class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate

“`
[/markdown]

  • Swift (ExtensionDelegate.Swift)

[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.

  • Objective-C (AppDelegate.m)

[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]

  • Objective-C (ExtensionDelegate.m)

[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]

  • Swift (AppDelegate.swift)

[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]

  • Swift (ExtensionDelegate.swift)

[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.

  • Objective-C (ToDoController.m) – iPhone

[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]

  • Swift (ToDoController.swift) – iPhone

[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.

 

  • Objective-C (ToDoController.m) – Relógio

[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]

  • Swift (ToDoController.swift) – Relógio

[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 :

  • didReceiveUserInfo na classe AppDelegate no iPhone e
  • 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 (WatchCommProtocole uma propriedade (watchCommDelegate)

em cada um das classes (AppDelegate e ExtensionDelegate)

  • Objectve-C (AppDelegate.h)

[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]

  • Objectve-C (ExtensionDelegate.h)

[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]

  • Swift (AppDelegate.swift)

[markdown]
“`

import UIKit
import WatchConnectivity

protocol WatchCommProtocol {

func itemToogled(ToDoId: Int)
}

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {

var window: UIWindow?

var watchCommDelegate : WatchCommProtocol?

[…]

“`
[/markdown]

  • Swift (ExtensionDelegate.swift)

[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.

  • Objectve-C (AppDelegate.m)

[markdown]
“`

[…]

-(void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary *)userInfo
{
if([userInfo.allKeys containsObject:@”toogle”])
if(self.watchCommDelegate)
[self.watchCommDelegate itemToogled:[userInfo objectForKey:@”toogle”]];
}

“`
[/markdown]

  • Objectve-C (ExtensionDelegate.m)

[markdown]
“`

[…]

#pragma mark – WCSessionDelegate

-(void)session:(WCSession *)session didReceiveApplicationContext:(NSDictionary *)applicationContext
{
if(self.watchCommDelegate)
[self.watchCommDelegate contextReceived:applicationContext];
}

“`
[/markdown]

  • Swift (AppDelegate.swift)

[markdown]
“`

[…]

// Mark : WCSessionDelegate

func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {

if let toDoId = userInfo[“toogle”] {
watchCommDelegate?.itemToogled(toDoId as! Int)
}
}

“`
[/markdown]

  • Swift (ExtensionDelegate.swift)

[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)

  • Objectve-C (ToDoTableViewController.h)

[markdown]
“`

#import
#import “AppDelegate.h”

@interface ToDoTableViewController : UITableViewController

@end

“`
[/markdown]

  • Objectve-C (ToDoTableViewController.m)

[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]

  • Objectve-C (InterfaceController.h)

[markdown]
“`

#import
#import
#import “ExtensionDelegate.h”

@interface InterfaceController : WKInterfaceController

@end

“`
[/markdown]

  • Objectve-C (InterfaceController.m)

[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]

  • Swift (ToDoTableViewController.swift)

[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]

  • Swift (InterfaceController.swift)

[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.

 

Figura 25

Figura 25

 

Figura 26

Figura 26

 

Você pode também fazer o download do código fonte nas duas linguagens aqui.

 

CTA-ebook-transformação-digital

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Compartilhe isso: