Ingressos no pulso
Levando festas e ingressos para o Apple Watch
Ingresse e o Apple Watch
A Ingresse é uma startup de tecnologia aplicada a eventos. Um dos nossos principais produtos é o app Ingresse, lançado para Android e iOS. Com ele, nossos usuários podem descobrir eventos por todo o Brasil, comprar ingressos e armazená-los no próprio celular. Os ingressos são armazenados de maneira offline para serem mostrados posteriormente na entrada do evento.
Desenvolvendo apps para um relógio
Um smartwatch é uma extensão do smartphone focada em ações simples e rápidas, as quais podem ser finalizadas no próprio relógio, com gestos ou comandos de voz, ou no smartphone caso demandem uma interação mais complexa. Por isso, a app para o smartwatch não é simplesmente a mesma app do smartphone com uma interface condensada, as funcionalidades e o fluxo da app também devem ser implementados pensando nessa nova forma de interação e em recursos mais restritos de processamento e energia.
Pensando nisso, implementamos versões do app Ingresse para Android Wear e Apple Watch, lançadas há algumas semanas. As principais funcionalidades do app Ingresse para smartwatches são a visualização dos QR-codes dos ingressos, para serem validados na entrada do evento, e mostrar informações de eventos na sua cidade, como datas e localização, podendo ver mais informações diretamente no smartphone. Além disso, através das notificações propagadas para o relógio, o usuário tem acesso rápido a seus ingressos e informações importantes do evento pouco antes de ele acontecer.
Nossa app para Apple Watch utiliza quase todos os recursos que o sistema tem a oferecer, como Glance, Notificações, Handoff, App Groups, etc. A ideia principal desse texto é divulgar a nossa experiência com o desenvolvimento para Apple Watch e dar dicas para desenvolvedores que estejam ou não começando a desenvolver suas apps para o relógio, de maneira que o texto seja útil também para entusiastas da computação vestível.
Estrutura do projeto
O aplicativo para Apple Watch é uma App Extension de um aplicativo para iOS. App Extension é um recurso que foi lançado no iOS 8 com o objetivo de fornecer um meio para o desenvolvedor extender as funcionalidades de suas apps.
Para adicionar uma extensão do Apple Watch em seu projeto no Xcode, selecione File > New > Target e Apple Watch no menu à esquerda. Após isso, os arquivos da extensão serão adicionados ao seu projeto, entre eles, o Storyboard, .plist, Images.xcassets, etc. No Storyboard, já é criado um InterfaceController da tela inicial e também das notificações estáticas e dinâmicas.
Os números Version e Build do seu projeto principal devem ser os mesmos no target da app para o Apple Watch.
Navegação entre telas
A navegação entre as telas é dividido em dois modos, Page based e Hierarchical.
No Page based, as telas não necessariamente possuem uma relações de dados entre si. Para quem já está por dentro do funcionamento do Apple Watch, esse é o modo que é usado nas telas de Glance dos aplicativos instalados. A navegação é feita com gestos de swipe para a direita e esquerda. Um conjunto de pontos na parte de baixo da tela indica em qual página o usuário está.
O modo Hierarchical, que é utilizado no app Ingresse, como o nome diz, estabelece uma relação hierárquica entre as telas do aplicativo. No nosso caso, temos uma hierarquia bem definida: da lista de eventos, o usuário pode selecionar um evento para ver mais detalhes e, caso tenha ingressos, continuar no fluxo para ver os ingressos comprados para o evento.
Implementando uma navegação hierárquica
A navegação hierárquica pode ser implementada utilizando Segues ou chamando o método pushControllerWithName:context: no InterfaceController de onde a navegação começará.
[self pushControllerWithName:@"eventDetails" context:eventObjectId];
Nesse método, é passado o identificador do InterfaceController para o qual a navegação seguirá e também um objeto de contexto. O identificador é definido no Storyboard, na propriedade "Identifier" do objeto. O contexto pode ser qualquer objeto utilizado para definir a informação que será mostrada na nova tela. No app Ingresse, o contexto é o id do evento que o usuário seleciona. Assim, ao chegar na tela de detalhes do evento, podemos utilizar o id para recuperar as demais informações.
No novo InterfaceController, é implementado o método awakeWithContext: que receberá o contexto enviado pelo último controller para que a interface seja criada corretamente.
- (void) awakeWithContext:(id)context { NSManagedObjectID *objectID = (NSManagedObjectID*) context; NSManagedObject *eventObject = [managedObjectContext objectWithID:objectID]; // ...}
Utilize awakeWithContext: para carregar os dados necessários e construir sua interface e o método willActivate para atualizá-la quando ela for mostrada novamente para o usuário (Por exemplo, quando o watch voltar do modo inativo para a sua app).
Notificações
No Apple Watch, a Apple soube trabalhar muito bem o conceito de "Computação Baseada em Contexto". Um dos exemplos disso é a lógica de exibição de notificações no smartphone e no relógio. Caso o usuário esteja com o iPhone no bolso, a notificação aparecerá no relógio, e somente lá. Caso ele esteja utilizando o iPhone, a notificação aparecerá na tela dele normalmente e ele não sentirá nenhuma vibração ou ouvirá algum som vindo do seu pulso.
O código de notificações antes desenvolvidas para o iPhone não precisam de modificações para serem mostradas também no relógio, a não ser que o desenvolvedor deseje implementar funcionamentos específicos no relógio para as ações presentes na notificação.
Você deve declarar dois tipos de notificações no seu Storyboard: Static Interface e Dynamic Interface. Como o nome diz, a Static Interface é uma notificação com informações estáticas, isto é, mostra a mensagem da notificação junto com algum outro texto ou imagem estática. Já com a Dynamic Interface, o desenvolvedor pode implementar novas mensagens, imagens e funcionalidades baseado nos dados obtidos através da notificação.
Sempre o Apple Watch tenta mostrar a notificação dinâmica primeiro. Caso ela ultrapasse um certo limiar de tempo para carregar, ele mostra a notificação estática por padrão. Uma ótima sacada da Apple para oferecer uma melhor experiência e não comprometer o tempo de espera do usuário caso ela não esteja disponível a tempo.
No InterfaceController criado para sua tela de notificação, você declara métodos de callback para tratar as notificações locais e remotas recebidas.
- (void) didReceiveLocalNotification:(UILocalNotification*) localNotification withCompletion:(void (^)(WKUserNotificationInterfaceType)) completionHandler
{
NSString *eventId = [localNotification.userInfo objectForKey:@"eventId"]; //...
}- (void) didReceiveRemoteNotification:(NSDictionary*) remoteNotification withCompletion:(void (^)(WKUserNotificationInterfaceType)) completionHandler
{
NSString *eventId = [remoteNotification objectForKey:@"eventId"]; //...
}
Glance
Glances são telas do sistema do Apple Watch que fornecem informações úteis para o usuário baseado no contexto em que ele está, como data, hora e localização.
Para criar a tela de Glance, basta arrastar uma Glance Interface Controller para o seu Storyboard e implementar uma classe que herda da WKInterfaceController para gerenciar o funcionamento da Glance.
Certifique-se que sua Glance mostre informações interessantes e atualizadas constantemente para o usuário. Caso você não tenha informações relevantes assim, talvez a Glance não seja uma boa ideia.
Glance e Notificações no simulador
Glance
Algo muito útil no desenvolvimento de Glance e notificações foi a possibilidade de simulá-las no Xcode. Para isso, abra o menu de schemes da sua aplicação, selecione o scheme da sua aplicação para o watch e selecione Edit Scheme.
Clique em Duplicate Scheme, para criar um novo esquema. Renomeie para “<Nome da sua app> — Glance” para identificar o scheme de Glance. Na opção Watch Interface, selecione Glance.
Pronto! Agora você pode implementar sua Glance e rodar no simulador do Apple Watch.
Notificações
O processo para simulação de notificações é basicamente o mesmo. Além disso, você pode definir um arquivo de payload, onde pode customizar os dados que serão enviados através da sua notificação, da mesma forma que você configura em seu serviço de push notifications, por exemplo.
Dentro da pasta Supporting Files, na pasta WatchKit Extension do seu projeto, você encontrará um arquivo JSON chamado "PushNotificationPayload.json". Nele, você pode configurar mensagens, títulos, categorias, botões de ação e dados adicionais que serão enviados pela notificação de teste. Abaixo está um example de payload que foi usado para testar as notificações do app Ingresse. Foram definidas as mesmas ações utilizadas nas notificações reais do app, Ver local e Meus ingressos. Os objetos eventId, date e time são dados adicionais que são enviados através da notificação.
{
"aps": {
"alert": "PRAIA TROPICAL",
"title": "Ingresse",
"category": "myCategory"
}, "WatchKit Simulator Actions": [
{
"title": "Ver local",
"identifier": "SEE_LOCATION"
},
{
"title": "Meus ingressos",
"identifier": "OPEN_TICKETS"
}], "eventId": "13074",
"date": "19/04/2015",
"time": "15:00:00"
}
Comunicação com o iPhone
Compartilhando dados com App Groups
Um dos desafios iniciais durante a implementação foi como compartilhar os mesmos dados utilizados na nossa aplicação para iPhone com o Apple Watch. Felizmente, com o uso de App Groups, podemos criar um conteiner onde os dados do app podem ser salvos para o compartilhamento com extensões (no nosso caso, a app para Apple Watch).
Para criar um app group, é necessário ativar essa funcionalidade na aba Capabilites do seu target no Xcode. Lá você cria uma string de identificação para o seu grupo e o Xcode trata automaticamente outras configurações. O mesmo processo deve ser feito na target de sua aplicação para o watch, utilizando o mesmo nome.
Para acessar e salvar NSUserDefaults no armazenamento compartilhado, utilize o método initWithSuiteName: passando como parâmetro o nome do seu app group ao inicializar a variável.
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.ingresse"];
Seus dados salvos utilizando CoreData também podem ser compartilhados. Para isso, na definição de seu NSPersistentStoreCoordinator, a URL deve ser definida a partir da localização de seu app group.
NSURL *appGroupsURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:@"group.ingresse"];NSURL *storeURL = [appGroupsURL URLByAppendingPathComponent:@"ingresse.sqlite"];//...[persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]
Importante: Não esqueça de criar um método para migrar todos os dados do seu app para o armazenamento compartilhado do App Groups e alterar referências de acesso ao NSUserDefaults.
Handoff
Quem já usou Android Wear, está acostumado com botões "Abrir no smartphone" nos apps do relógio. A Apple conseguiu remover completamente a necessidade de um botão como esse nas apps do Apple Watch. Caso o usuário queira continuar uma atividade iniciada no relógio no iPhone, basta ele ir diretamente ao iPhone. Se essa atividade estiver disponível no iPhone, o usuário verá um ícone do app no canto inferior esquerdo da tela de bloqueio. Fazendo um swipe daquele ícone para cima, o iPhone é desbloqueado na tela do aplicativo referente ao que o usuário acessava no relógio.
Para isso, os desenvolvedores devem utilizar o recurso de Handoff. Nos InterfaceControllers das telas da sua app para o watch, você declara para a sua app principal o "contexto" onde o usuário está navegando. Quando o usuário estiver em uma tela específica no relógio que suporta o Handoff, basta ele pegar o iPhone e com um gesto simples de swipe, desbloqueá-lo na tela em questão do app para iPhone.
Para usar o Handoff, declare uma "atividade" que o usuário executará na tela de um determinado InterfaceController:
- (void) willActivate
{
[self updateUserActivity:@"com.ingresse.event.details" userInfo:@{@"eventId": eventId} webpageURL:nil];}
Declare um array com o nome NSUserActivityTypes no .plist do seu app principal e nele adicione os identificadores das atividades declaradas nos InterfaceControllers do seu app para o relógio (Exemplo: "com.ingresse.event.details").
No AppDelegate da aplicação principal, implemente o método abaixo e mostre a tela da app de acordo com a userActivity passada como parâmetro:
- (BOOL) application:(UIApplication*)application continueUserActivity:(NSUserActivity*)userActivity restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler
{
NSDictionary *userInfo = userActivity.userInfo;
NSString *activityType = userActivity.activityType; NSString *eventId = [userInfo objectForKey:@"eventId"]; if ([activityType isEqualToString:@"com.ingresse.event.details"]) {
//...
} }
Um claro uso do Handoff que implementamos na app Ingresse é quando o usuário abre um evento no relógio. Há outras atividades mais complexas que podem ser realizadas pelo iPhone, como comprar ingressos, entrar na lista do evento ou ver uma descrição detalhada. Ao fazer swipe para abrir o app Ingresse no iPhone, o usuário é levado para a tela de detalhes do evento que estava vendo no relógio.
É uma boa prática mostrar um texto informando o usuário para abrir o app no iPhone caso deseje continuar uma ação mais complexa. Nada de botões "Abrir no iPhone".
Objetos da interface
Table
A famosa UITableView possui uma versão mais simplificada no Apple Watch, com o nome de WKInterfaceTable. A configuração e alimentação de uma Table é muito simples, bastando informar o número de linhas e criar a interface dos itens da lista no mesmo método.
- (void) configureTableWithData:(NSArray*)dataObjects {
[self.watchTable setNumberOfRows:[dataObjects count] withRowType:@"eventRowType"]; for (int i=0; i<self.watchTable.numberOfRows; i++) {
EventRowType* eventRow = [self.watchTable rowControllerAtIndex:i]; // Configure event list row }}
Ao criar uma Table no Storyboard, é possível configurar uma TableRowController que definirá a interface dos itens da lista. No nosso caso, criamos a classe EventRowType, herdada da NSObject, e a identificamos como TableRowController da nossa lista de eventos. Assim, foram criados os objetos da interface do item e seus IBOutlets para a classe EventRowType, para que fosse possível acessá-los do método configureTableWithData definido acima.
Cuidado com o número de linhas da sua Table! Muitas linhas podem comprometer a experiência do usuário no Apple Watch. No nosso caso, limitamos o número de eventos que aparecem na lista para 20. Caso o usuário deseje ver mais eventos, recomendamos que abra o app no iPhone (mais um exemplo onde utilizamos Handoff).
Map
O objeto WKInterfaceMap é um mapa onde pode ser mostrada a localização do usuário ou coordenadas específicas definidas na aplicação. Esse objeto não é interativo, ou seja, o usuário não pode navegar pelo mapa ou ver mais informações nele. Ele pode somente controlar o zoom no ponto em questão utilizando a coroa digital. No entanto, ao clicá-lo, o usuário é levado ao aplicativo de Mapas do relógio para realizar a navegação.
Para configurar o ponto e zoom mostrado no mapa:
CLLLocationCoordinate2D locationCoordinates = CLLocationCoordinate2DMake(latitude, longitude);MKCoordinateSpan span = MKCoordinateSpanMake(0.01, 0.01);MKCoordinateRegion = MKCoordinateRegionMake(locationCoordinates, span);[self.eventMap setRegion:region];[self.eventMap addAnnotation:locationCoordinates withPinColor:WKInterfaceMapPinColorRed];
Referências
WatchKit (Apple Developer): Seção sobre o Apple Watch do site de desenvolvedores da Apple. https://developer.apple.com/watchkit/
Watch Aware: Site que possui uma base de dados de apps para Apple Watch publicadas na App Store (e onde eu fiz a imagem legal do título ☺). http://watchaware.com/
Essas foram algumas dicas que acredito serem muito valiosas para desenvolvedores que estejam trabalhando ou tenham interesse em trabalhar com o Apple Watch.
Caso tenha gostado, não esqueça de compartilhar com outros devs! Já desenvolveu uma app pro Apple Watch? Compartilhe com a gente a sua experiência! ☺