quinta-feira, 6 de janeiro de 2011

Criando apps para iPhone com Swift - Comunicação


Quase toda app móvel, hoje em dia, possui algum tipo de servidor, com o qual precisam se comunicar. Podem ser servidores privados, criados e mantidos por você, ou podem ser servidores públicos, que expõem algum tipo de API para consumo (por exemplo: Google, Yahoo etc).

Nesta lição, você vai aprender a se comunicar com servidores externos. Se você ainda não atualizou os arquivos do livro, baixe novamente AQUI, pois há modificações!

Comunicação pode ser algo simples, como enviar um email, ou complexo, como consumir uma API REST.



Ok, agora é hora de aprendermos como utilizar comunicação remota em nossas apps. O iOS permite várias formas de comunicação, e vamos ver algumas delas aqui, por exemplo:
  • Como compartilhar dados;
  • Como fazer “upload” de arquivos;
  • Como fazer “download” de arquivos;
  • Como consumir serviços;
  • Como enviar notificações “push”;
  • Como usar o iCloud.



Eu vou mostrar as maneiras mais simples de executar cada uma destas tarefas de comunicação. Existem maneiras mais sofisticadas e uma diversidade grande de classes e componentes que você pode utilizar, mas penso que isto não é assunto para este livro.



Compartilhar dados




Essa é bem fácil. Vamos supor que você queira que sua app envie uma mensagem, seja por e-mail, SMS, Facebook etc. Como fazer isso? No Android, nós usamos o famoso “ACTION-SEND” e ele mostra uma lista de apps que podem fazer isso. No iOS, não é diferente.



Abra o exemplo “capt9/ShareApp”, porém, execute usando o dispositivo real. Se você não tem um dispositivo real, ainda dá para prosseguir, porém, é mais limitado. Por exemplo, ao executar a app e tocar no botão “Compartilhar”, é isso que você verá, se estiver usando o Simulador do iOS:


Agora, veja a mesma app rodando no dispositivo real, depois de clicar no botão “Compartilhar˜:




Como vê, há uma grande diferença entre testar a app no iOS Simulator e no dispositivo real. Neste capítulo, eu vou mostrar os exemplos rodando em um dispositivo real. Você pode até rodar no iOS Simulator, porém, verá menos opções.



Se você abrir a ShareApp, verá, no arquivo “ViewController.swift”, o código que eu usei para compartilhar o conteúdo da Text View. O botão “Compartilhar” tem uma “Action” ligada a ele:



@IBAction func compartilhar(sender: AnyObject) {
  let objectsToShare = [texto.text]
  let activityVC = UIActivityViewController(activityItems: objectsToShare,               applicationActivities: nil)
  self.presentViewController(activityVC, animated: true, completion: nil)
}



Eu simplesmente criei um vetor com os elementos que queria compartilhar, neste caso, só o texto da Text View, e criei uma instância de UIActivityViewController, passando o vetor como parâmetro. Depois, usei o método “presentViewController()”, da classe UIViewController, para mostrar a tela de escolha de compartilhamento.



Simples e funcional! Veja o resultado de compartilhar por e-mail:



Fazer upload de arquivos




Em certas apps, talvez você queira fazer um “upload” de arquivos. Por exemplo, no HPP, nós vamos permitir que o usuário gere um XML de backup, e exporte para onde quiser.



Infelizmente, a solução do UIActivityViewController não funciona para isto. Se você quiser, pode criar “Activities” extras e invocar manualmente as aplicações, o que dá muito trabalho.



Vamos imaginar que eu queira compartilhar um arquivo com o Google Drive, como faria? É claro que é preciso ter a app do Google Drive instalada em seu dispositivo iOS! Podemos usar outra classe: UIDocumentInteractionController, que permite abrir um documento em qualquer outra app instalada. Apesar de ser simples, exige um pouco mais de trabalho do que a solução anterior:
  • Você tem que salvar o arquivo;
  • Pegar a URL e usar para compartilhá-lo com outra app.



A “Action” do botão “Compartilhar arquivo” faz exatamente isso. Para começar, ela grava um arquivo no diretório de documentos da app, depois, compartilha o arquivo usando um UIDocumentInteractionController:


  @IBAction func compartilharArquivo(sender: AnyObject) {
    
    //Primeiro, salvamos o arquivo:
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("mensagem.txt")
    let conteudo = self.texto.text as String
    var erro : NSError? = nil
    conteudo.writeToURL(url, atomically: true, encoding: NSUTF8StringEncoding, error: &erro)
    if erro != nil {
      println(erro?.description)
    }
    
    //Agora, chamamos o compartilhamento do arquivo com outras apps:
    docInteractionController = UIDocumentInteractionController(URL: url)
    docInteractionController!.presentOptionsMenuFromRect(self.view.bounds, inView: self.view, animated: true)
  }

Para começar, criamos uma propriedade “lazy” no nosso View Controller, para obter a URL do diretório de documentos da app:



lazy var applicationDocumentsDirectory: NSURL = {
let urls = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains:.UserDomainMask)
return urls[urls.count-1] as! NSURL
}()

Depois, construímos a URL final do arquivo, concatenando o nome: “mensagem.txt”.



Finalmente, instanciamos um UIDocumentInteractionController, passando a URL do arquivo, e usamos o método “presentOptionsMenuFromRect”, passando o retângulo da tela, e a view principal. Imediatamente, aparece um menu para escolhermos a app:




 Se você escolher Google Drive, a app será chamada e perguntará se você quer fazer o upload do arquivo:



Se você escolher “Fazer upload”, então “voilà”: Você terá compartilhado o conteúdo da Text View com o Google Drive.



Fazer download de arquivos




Em certos tipos de app, é necessário baixar arquivos, sejam de configuração ou de DLC (Downloadable Content), então é necessário acessar uma URL e baixar um arquivo. O exemplo “capt9/DownloadFile” ilustra isso.



Usamos uma instânca da classe NSURLSession para fazermos o download de um arquivo, o qual podemos gravar na “sandbox” da nossa app.



Uma nota importante sobre a classe NSURLSession, é sua execução é assíncrona, feita por um “Thread” diferente do Thread Principal, aquele que roda a app. Neste caso, quando a execução termina, você estará em um thread diferente, e não deverá “mexer” com variáveis que forem utilizadas pelo Thread Principal.



Eis a “Action” que faz o download:


  @IBAction func download(sender: AnyObject) {
    let url : NSURL? = NSURL(string: self.txtURL.text)
    let task = NSURLSession.sharedSession().dataTaskWithURL(url!, completionHandler: {(dados,resposta,erro) in
      // Este código é executado por outro thread, de forma assíncrona:
      println(NSString(data: dados, encoding: NSUTF8StringEncoding))
      dispatch_async(dispatch_get_main_queue()) {
        self.conteudo = NSString(data: dados, encoding: NSUTF8StringEncoding)
        self.terminou()
      }
    })
    task.resume()
  }

Eu criei uma instância de NSURLSession, passando uma URL que aponta para o local onde está o arquivo que desejo baixar. Eu estou baixando a página “index.html” do meu próprio site.



Após criar uma instância de NSURLSession, eu invoquei o método “dataTaskWithURL”, que, como os outros métodos da Classe, executa uma tarefa em modo assíncrono, usando um thread em background. Ao terminar, ele invoca um “callback” (um “handler”), especificado como uma expressão Lambda. Você até pode criar uma função externa, se assim o desejar, só deve se lembrar que ela será invocada por outro Thread, diferente do Thread principal, logo, não poderá alterar qualquer elemento de UI, como Text View, por exemplo.



Dentro do Handler, qualquer comando que não use elementos de UI, pode ser executado. Como eu quero mostrar o conteúdo do arquivo em uma Text View, preciso usar o comando “dispatch_async”, passando a fila principal de tarefas a serem executadas pelo Thread de UI. Desta forma, tudo o que estiver dentro do bloco do “dispatch_async”, será encaminhado para execução pelo Thread principal.


Esta é a maneira melhor indicada para fazer rapidamente um download de arquivo, sem intervenção do usuário.



E você pode usar o NSURLSession para fazer upload também, pois ela tem vários métodos assíncronos para fazer upload de arquivos. A grande vantagem é que o usuário não participa da ação.



Consumir serviços




Hoje em dia, é muito comum uma app ampliar seu conteúdo através de Serviços disponíveis na Web, como Web Services ou RESTful Services.



Por exemplo, você poderia obter cotações de ações, previsão do tempo, notícias e informações sobre localização diretamente de serviços disponíveis na Web.



Vou mostrar como fazer isto usando REST.



Existe um serviço chamado “freegeoip.net” que retorna informações sobre o seu enderço IP. Podemos invocá-lo e receberemos informações diversas, como: o país, a latitude e longitude do nosso endereço IP.



Eis o tipo de informação que recebemos:

{"ip":"xxx.yyy.nnn.ppp","country_code":"BR","country_name":"Brazil","region_code":"","region_name":"","city":"","zip_code":"","time_zone":"","latitude":-22.9217199,"longitude":-43.2409253,"metro_code":0}

O Endereço IP foi omitido por mim, de propósito.



Vamos usar a mesma NSURLSession para obter, usando um request GET, de forma assíncrona, as informações do nosso Endereço IP. Abra o projeto “capt9/GeoREST'. Eis o resultado da execução:

Eu “mascarei” o endereço IP.



Eis o código do “viewDidLoad()”, que faz o acesso ao serviço:



 override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    var url : String = "http://freegeoip.net/json"
    var request = NSMutableURLRequest(URL: NSURL(string: url)!)
    var session = NSURLSession.sharedSession()
    request.HTTPMethod = "GET"
    
    var err: NSError?
    
    var task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
      var conteudo = NSString(data: data, encoding: NSUTF8StringEncoding)
      println(conteudo)
      var erro: NSError?
      var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableLeaves, error: &erro) as? NSDictionary
      if(erro != nil) {
        println(erro!.localizedDescription)
        let jsonString = NSString(data: data, encoding: NSUTF8StringEncoding)
        println("Problema ao analisar o JSON recebido: '\(jsonString)'")
      }
      else {
        dispatch_async(dispatch_get_main_queue()) {

          self.lblPais.text = json!.valueForKey("country_name") as? String
          let latitude = json!.valueForKey("latitude") as? Double
          self.lblLatitude.text = String("\(latitude!)")
          let longitude = json!.valueForKey("longitude") as? Double
          self.lblLongitude.text = String("\(longitude!)")
          self.lblIP.text = json!.valueForKey("ip") as? String
        }
      }
    })
    
    task.resume()
  }

É uma NSURLSession comum, só que eu criei um request (uma instância de NSMutableURLRequest), e alterei o seu método. Eu poderia criar um request POST, DELETE, PUT etc.



A novidade aqui é que estou recebendo um JSON e de-serializando como NSDictionary, usando a classe NSJSONSerialization.



Para fazer um POST, é quase a mesma coisa, só que devemos acrescentar um “request body”, ou uma “entity” ao nosso request. Por exemplo, se eu quiser fazer um POST de um objeto JSON, poderia criar um request desta forma:



var url : String = "http://www.servicos.org/api/usuario"
var request = var request = NSMutableURLRequest(URL: NSURL(string: url)!)
var error NSError?
let usuarioJson = "{\"id\":\"usuario1\",\"nome\":\”Fulano de Tal\”}"
request.HTTPBody = usuarioJson.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true)
request.HTTPMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")



E também estou modificando o header HTTP “Content-type”. Para enviar, é só usar o método “dataTaskWithRequest”, do objeto NSURLSession, para criar a tarefa e enviar o request. Ao final, o método “resume()”, invocado a partir da “Task” envia o request.



Notificações “push”




Todos os usuários de Smartphone ou Tablet, seja ele Android, iOS ou Windows Phone, sabem o que são as notificações “push”. Geralmente, existe um serviço de “push”, que as aplicações (servidoras) podem usar para solicitar o envio de notificações aos dispositivos móveis.



Notificações push remotas só funcionam no Dispositivo Real. Não tente usar o simulador para isso!



No caso do iOS, existe o Apple Push Notification Service (APNS), e a API de notificações locais e remotas.



O funcionamento é simples, e dividido em dois momentos. Primeiramente, sua app precisa se registrar para poder receber notificações push remotas. Ela vai receber um “device id”, que precisa guardar, provavelmente em um servidor seu, para posterior uso:





Ao se registrar no Serviço APNS, sua app recebe um identificador, conhecido como “device token”. Geralmente, uma app que usa notificações é dividida entre Cliente (mobile) e Servidor (um Servidor na nuvem). O Servidor, de acordo com as regras de negócio, solicitará o envio de notificações para os dispositivos registrados, usando o “device token”. Então, sua app precisa “dar um jeito” de enviar o “device token” para o seu servidor de aplicação.



O segundo momento é quando sua aplicação, representada pelo Servidor de Aplicação, decide avisar aos usuários da sua app móvel, sobre alguma atualização. Vamos supor que sua app receba notícias, vindas de provedores Atom ou RSS, por exemplo. Quando o Servidor deteta que existem novas notícias, gera uma mensagem “push” para o dispositivo móvel, avisando-o que existem atualizações a serem baixadas.



Opcionalmente, ele pode incluir no aviso um “badge number”, ou seja, um número quantificador. Este número aparece sobre o ícone da app. Por exemplo, a app do Facebook mostra o número de novas notificações para você.




Eis o diagrama deste segundo momento:




Antes de mais nada, é preciso que sua app se registre para receber notificações “push”. Isto deve ser feito na sua classe “AppDelegate”, dentro de “AppDelegate.swift”.



Dentro dos arquivos do livro, abra o projeto: “capt9/PushNotificationSample.zip”.



No Projeto de exemplo, veja as principais alterações no AppDelegate.swift:



func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
  // Override point for customization after application launch.
  let notificationType : UIUserNotificationType = UIUserNotificationType.Badge |        UIUserNotificationType.Alert | UIUserNotificationType.Sound
  let notificationSettings : UIUserNotificationSettings =      UIUserNotificationSettings(forTypes: notificationType, categories: nil)
  application.registerUserNotificationSettings(notificationSettings)
  application.registerForRemoteNotifications()
  return true
}

No “callback” de inicialização da app, que fica na função “didFinishLaunchingWithOptions”, nós criamos duas instâncias de objetos:
  • UIUserNotificationType: Os tipos de notificações que nossa aplicação pode receber, por exemplo: Alertas, Sons e modificação de ícone (Badge);
  • UIUserNotificationSettings: A configuração das notificações, que inclui a instância de UIUserNotificationType como parâmetro;



Depois, chamamos o método “registerUserNotificationSettings”, passando como parâmetro a instância de UIUserNotificatonSettings que criamos.



A partir desse momento, o framework vai solicitar ao usuário, de maneira transparente para você, a permissão para que a app receba tais notificações. Este diálogo aparece automaticamente na tela:





Se o usuário permitir, então, dois “callbacks” poderão ser invocados em seu AppDelegate:
  • “application: didRegisterForRemoteNotificationsWithDeviceToken:”: O registro foi bem sucedido, e o device token está nos parâmetros;
  • “application: didFailToRegisterForRemoteNotificationsWithError:”: Houve um erro ao registrar a app, cuja causa está nos parâmetros;



No “callback” “application: didRegisterForRemoteNotificationsWithDeviceToken:”, você deve guardar o “device token” recebido. De preferência, deve enviar a um Servidor na Internet. Pode ser via REST, por exemplo. No nosso caso, estamos apenas mostrando na Console:



func application(application: UIApplication,   didRegisterForRemoteNotificationsWithDeviceToken deviceToken: NSData) {
  println(deviceToken)
}



O “device token” aparecerá na Console, algo como essa mensagem:



<f8f14c54 73ea163f 12f201c9 765bd749 69e4a1b3 87caf128 f91311c7 f921a9bf>



(Eu alterei propositadamente o número, então, este não é um device token real)



Ignore os caracteres “<” e “>”, e elimine os espaços.



Certificado e provisionamento da App




Se você vai enviar notificações remotas, sua app, tanto a parte mobile como o Servidor, vão necessitar de um Certificado para mensagens de Push, e de um novo Provisioning Profile, específico para a app que vai receber notificações.



É claro que, para isto, você já deve ter se registrado no programa iOS Developer Program, e deve ter o seu Certificado de desenvolvimento de app iOS. Mesmo que já tenha um Provisioning Profile, deverá criar outro, específico para esta app.



E, antes de criar um Certificado de Push e um Provisioning Profile, deve criar um App ID, ou identificador de aplicação. É baseado nesse identificador, que ambos (o Certificado de Push e o Provisioning Profile) serão criados.



Acesse o iOS Developer Program Member Center e faça logon, depois, abra a opção “Certificates”, selecionando o item “Identifiers / App IDs”:




Você deve criar um app ID para o Bundle Identifier da sua app. Ao criar um Projeto iOS, você seleciona o nome da app. Ele deve ser composto pelo seu Nome de Domínio Internet ao contrário, mas o nome do Projeto. É como um Java Package. Você pode ver isso ao clicar no Projeto no Xcode e ver a aba “General”.



O App ID será prefixado pelo código da sua “equipe”, que é criado quando você se registra no iOS Developer Program.



Ao criar o App ID, você deve informar o campo “Explicit App Id”, onde deve informar o Bundle Identifier da sua app. E também deve marcar “Push Notifications”, em “App Services”.



Ok. Agora, é preciso criar um Certificado para envio de Push Notifications. Primeiramente, abra o programa “Acesso às Chaves”, que fica dentro do “LaunchPad”, em uma pasta “outros”:


Vamos usar este programa para criar uma requisição de certificado, e enviar à Autoridade Certificadora da Apple. Selecione o menu: “Acesso às Chaves / Assistente de Certificado / Solicitar um certificado de uma autoridade de certificado”. Você verá um “popup”, com os campos necessários. Preencha:
  • Seu endereço de e-mail;
  • Endereço de e-mail da AC: deixe em branco;
  • A solicitação é: Selecione “Salva no disco”.



Ao terminar, escolha um nome de arquivo e salve o CSR (Certificate Sign Request).



Agora, vamos para o iOS Developer Program novamente.



Entre no grupo “Certificates / Development” e crie um Certificado para Push Notification, escolhendo o App ID que você criou, e fazendo “upload” do arquivo CSR que você acabou de criar.



Depois, baixe o certificado e dê um duplo-clique nele, para adicioná-lo à aplicação “Acesso às chaves”.



Agora, é necessário criar um Provisioning Profile específico para esta app móvel. No iOS Developer Program, selecione “Provisioning profiles / Development”, e crie um Provisioning Profile para o App ID que você gerou.




Mesmo que você já tenha um Provisioning Profile (o Xcode cria Profiles “genéricos” para nós), você deve criar um específico para aquele App ID que vai receber notificações “push”.



Baixe o Provisioning profile e dê um duplo-clique nele, para adicioná-lo ao Xcode.



Enviando notificações “push”




Para tudo funcionar, é necessário que um programa externo capture o “device token” e solicite o envio de notificações “push” ao APNS. Existem dois servidores APNS:
  • Desenvolvimento: “gateway.sandbox.push.apple.com”, porta: 2195;
  • Produção: “gateway.push.apple.com”, porta: 2195;



Enquanto estiver usando certificados e profiles de desenvolvimento, use a URL do Servidor de desenvolvimento.



Há várias maneiras de enviar notificações APNS, para várias linguagens, por exemplo:



Eu optei por usar o Node.js e criei um programa Javascript bem simples, baseado em um exemplo original de Holly Schinsky (http://devgirl.org/2012/10/19/tutorial-apple-push-notifications-with-phonegap-part-1/), que é bem simples e funciona perfeitamente. O código está dentro de “capt9/PushNotificationSample.zip”, dentro da pasta “servidor” - sendpush.js:


var http = require('http');
var apn = require('apn');
var url = require('url');
var myPhone = "*** Cole aqui seu device token ***";
 
var myDevice = new apn.Device(myPhone);
 
var note = new apn.Notification();
note.badge = 3;
note.sound = "notification-beep.wav";
note.alert = { "body" : "OI!!!", "action-loc-key" : "Play" , "launch-image" : "mysplash.png"};
note.payload = {'messageFrom': 'Teste'};
 
note.device = myDevice;
 
var callback = function(errorNum, notification){
    console.log('Error is: %s', errorNum);
    console.log("Note " + notification);
}
var options = {
    gateway: 'gateway.sandbox.push.apple.com', // this URL is different for Apple's Production Servers and changes when you go to production
    errorCallback: callback,
    cert: 'pushCert.pem',                
    key:  'privateKey.pem',                 
    passphrase: 'push',                 
    port: 2195,                       
    enhanced: true,                   
    cacheLength: 100                  
}
var apnsConnection = new apn.Connection(options);
apnsConnection.sendNotification(note);

Ele envia uma mensagem bem simples, que tem um “OI!!!”, toca um som e muda o ícone da app para um “badge number” = 3.



Porém, tem mais algumas configurações a serem feitas...



De modo a se comunicar via SSL com o Servidor APNS, o script necessita do Certificado de Push Notification, e da sua correspondente chave privada, ambos criados por você. E eles devem estar no formato “pem” (Privacy-enhanced Electronic Mail). Vamos usar o “openssl” para isto.



Geralmente, os Macs já vêm com o Open SSL instalado. Então, abra um Terminal e vamos trabalhar. Para começar, você deve ter baixado o certificado de Push que você gerou, e ele vem em um formato “cer”, geralmente: “aps_development.cer”, então, devemos transformá-lo em “pem”. Use o seguinte comando:



openssl x509 -in aps_development.cer -inform der -out pushCert.pem



Agora, temos que baixar a Private Key deste certificado, convertendo-a para “pem”. Para isto, abra novamente a aplicação “Acesso às chaves”, e selecione a Chave privada que você criou. É só procurar pelo “Common name” e escolher a linha que contém “chave privada”:




Então, toque com os dois dedos (botão direito do mouse, se você usa um MacMini com mouse), e escolha “Exportar...”. Com isto, será criado um arquivo “p12” contendo a Chave privada. Agora, temos que transformá-lo em “pem” com o comando:



openssl pkcs12 -nocerts -out privateKey.pem -in privateKey.p12



Ele vai solicitar uma “passphrase” para o arquivo “pem”. Eu escolhi: “push”.



Agora, estes dois arquivos “pem” devem ficar na mesma pasta que o script “sendpush.js”.



Estamos quase lá...



No script “sendpush.js”, nos usamos o módulo “npm” “node-apn”. Para rodar esta aplicação, você deve ter o Node.js e o NPM instalados. Pode fazer isso, acesse o site: https://nodejs.org, clique no botão “install”, que ele baixará um arquivo “pkg”. Você pode dar duplo-clique no “pkg” para instalar ambos, o Node.js e o npm.



O “npm” é um gerenciador de pacotes para o Node.js, e nós vamos usá-lo para baixar o “node-apn”. Na pasta onde está o script “sendpush.js” (e os dois arquivos “pem”), abra um terminal e digite:



npm install apn



Ele vai criar uma pasta “Node Modules” com o módulo “apn” instalado.



Agora, falta pouco! Edite o arquivo “sendpush.js” e configure os nomes dos arquivos “pem” e a “passphrase” do arquivo da chave primária:



cert: 'pushCert.pem',
key: 'privateKey.pem',
passphrase: 'push',

Seu Smartphone deve estar com a app instalada e registrada, além de estar conectado à Internet, seja por Wi-fi ou por dados celulares. A app não precisa estar executando, pode ir para a tela inicial. Então, rode o script no Terminal, com o comando:



node sendpush



Em poucos instantes, você ouvirá um som e verá a notificação aparecer, além do ícone da app mostrar o “badge” 3:




Pronto! Você recebeu a notificação. Na classe AppDelegate, tem um “callback” que será invocado sempre que chegar uma nova notificação:


func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
println("\(userInfo)")
}

Eis o conteúdo da notificação, mostrado na Console do Xcode:


[aps: {
    alert =     {
        "action-loc-key" = Play;
        body = "Your turn!";
        "launch-image" = "mysplash.png";
    };
    badge = 1;
    sound = "notification-beep.wav";
}, messageFrom: Holly]
[aps: {
    alert =     {
        "action-loc-key" = Play;
        body = "OI!!!";
        "launch-image" = "mysplash.png";
    };
    badge = 3;
    sound = "notification-beep.wav";
}, messageFrom: Teste]

O que fazer com a notificação




Isto depende da lógica de negócios da sua app. Você pode exibir uma lista das atualizações para o usuário, de modo que ele tenha conhecimento.



Porém, de qualquer forma, depois de avisar ao usuário, você deve “limpar” o ícone da app, retirando o “badge number”. Caso contrário, ela ficará para sempre com aquele número. Podemos fazer isso em dois “callbacks” do AppDelegate:



func applicationWillEnterForeground(application: UIApplication) {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}

func applicationDidBecomeActive(application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
UIApplication.sharedApplication().applicationIconBadgeNumber = 0
}



É só voltarmos o “applicationIconBadgeNumber” para zero, quando a aplicação se tornar ativa. Ao clicar em uma notificação, o usuário torna a app ativa, ou seja, em primeiro plano.



Notificações em versões anteriores do iOS




O processo de notificações remotas mudou, do iOS 7.x para o iOS 8 (Lançado em Setembro de 2014). No momento em que estou escrevendo este livro, já estamos no iOS 8.3.



Eu recomendo fortemente que você restrinja sua app ao iOS 8 em diante. Isto pode ser feito ao selecionar a raiz do projeto no Xcode, e mudar o “Deployment Target”:




Meus motivos são dois:
  1. Como houve muitas mudanças a partir da versão 8, você terá problemas em manter compatibilidade;
  2. Se o usuário não atualizou sequer para a versão 8, é muito improvável que ele compre qualquer app na AppStore.



Porém, se for realmente o caso de manter compatibilidade com a versão iOS 7, então só há um jeito: Testar se a versão é iOS 8. No AppDelegate, você pode testar isso verificando se ela responde ao seletor “registerUserNotificationSettings”:



if application.respondsToSelector("isRegisteredForRemoteNotifications")
{
application.registerUserNotificationSettings(notificationSettings)
application.registerForRemoteNotifications()
}
else {
application.registerForRemoteNotificationTypes(.Badge | .Sound | .Alert)
}

Notificações em “produção”




Antes de submeter sua app para avaliação pela AppStore, você precisa mudar algumas coisas. O certificado de desenvolvedor iOS, o certificado de push notification e o Provisioning Profile, precisam ser criados especificamente para “distribution”.



E a URL do servidor APNS deve mudar para: “gateway.push.apple.com”.



Compartilhando via iCloud




O iCloud, serviço de nuvem da Apple, permite que suas aplicações compartilhem dados em nuvem, sem te cobrar nada a mais por isso.



Ao invés de criar um Servidor específico para sua app móvel, só para poder compartilhar dados entre seus usuários, você pode usar um Container público da sua aplicação, permitindo que todos os usuários registrados no iCloud, possam ver e, se você quiser, alterar os dados.



Existem várias formas de compartilhar dados, desde pares de chave / valor, até documentos complexos, como imagens, por exemplo. Eu vou mostrar como compartilhar “registros” usando o iCloud.



Vamos supor que você queira criar uma app para os membros de uma equipe de desenvolvimento, que permita a eles registrarem os problemas com o sistema. Então, eu pretendo criar um “registro” assim:



{ “Problemas” : { “autor” : “String”, “dataHora” : “DateTime”, “descricao” : “String”}}



Eu criei um protótipo de app, que está em “capt9/iCloudStorage.zip”. Eis sua execução:





Ela simplesmente mostra em uma Text View todos os registros encontrados no Container.



Um Container é como um repositório de dados da sua app dentro do iCloud. Cada app tem o seu Container. Dentro dele, podemos ter Bancos de dados públicos, que podem ser vistos por todos os usuários da sua app, e privados, que só podem ser vistos pelo usuário do iCloud que os criou. Então, você pode criar bancos de dados privados para cada usuário, se for armazenar dados particulares dele, ou então públicos, para armazenar dados compartilhados por todos os usuários.



Antes de usar o iCloud




Nem sempre o usuário da sua aplicação deseja armazenar dados no iCloud. E nem sempre ele terá o iCloud disponível para uso. Por exemplo, ele pode optar por não usar o iCloud. Além do mais, pode acontecer que o usuário iCloud seja alterado, por exemplo, alguém deu seu iPhone para outra pessoa, ou então mudou sua conta no iCloud.



Então, existe um procedimento recomendado pela Apple para verificar se existe uma conta logada no iCloud e se houve alterações. Você pode acessá-lo em: https://developer.apple.com/library/ios/documentation/General/Conceptual/iCloudDesignGuide/Chapters/iCloudFundametals.html#//apple_ref/doc/uid/TP40012094-CH6-SW18



Vamos abrir o arquivo AppDelegate.swift, da app de exemplo, que ficará mais fácil explicar como configurar a app corretamente.



Eis o código que verifica se o iCloud está disponível:


  func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    
    if let currentiCloudToken = NSFileManager.defaultManager().ubiquityIdentityToken {
      let newTokenData = NSKeyedArchiver.archivedDataWithRootObject(currentiCloudToken)
      NSUserDefaults.standardUserDefaults().setObject(newTokenData, forKey: "com.apple.MyAppName.UbiquityIdentityToken")
    }
    else {
      NSUserDefaults.standardUserDefaults().removeObjectForKey("com.apple.MyAppName.UbiquityIdentityToken")
    }
    NSNotificationCenter.defaultCenter().addObserver(self, selector: "iCloudAccountChanged:", name: NSUbiquityIdentityDidChangeNotification, object: nil)
    
    return true
  }

  func iCloudAccountChanged(notification: NSNotification) {
    // Obtenha um novo ubiquityIdentityToken, compare com o que foi armazenado no NSUserDefaults, 
    // se for diferente, então o usuário mudou. Limpe qualquer cache que você tenha localmente.
  }



Usando o método “ubiquityIdentityToken”, do NSFileManager default, obtemos o Token de identificação do usuário que está atualmente logado no iCloud. Mesmo que ele tenha colocado o celular em “modo avião”, ele ainda retornará um valor. Se vier nil, então o usuário não está logado no iCloud, logo, você não deve usar o iCloud para nada.



Se houver um usuário logado, então salvamos o token no NSUserDefaults, e nos registramos para receber notificações de alteração de usuário do iCloud. Ou seja, se mudar a conta do iCloud no dispositivo, nossa app será avisada.



O que acontece se mudar a conta? Qualquer informação que você tenha guardada sobre o usuário anterior, deverá ser apagada.



Se o uso do iCloud para armazenamento é opcional em sua app, então, você deveria perguntar ao usuário se ele deseja usar o iCloud. Se o uso é obrigatório, você deveria informar isso a ele.



Usando o CloudKit para compartilhar registros




O CloudKit é um framework da Apple que permite usar facilmente os registros do iCloud, como se fosse um banco de dados. Um registro é como se fosse uma “entidade”, e você pode ver e administrar os registros através de um “dashboard”.



Para usar o CloudKit, é preciso adicionar essa capacidade à sua app. Selecione o nó raiz da app e clique na aba “Capabilities”:




Primeiramente, você deve ligar o flag do iCloud. Depois, deve selecionar quais tipos de informações deseja armazenar no iCloud. Se quiser apenas chave / valor, marque “Key-value”, se quiser usar registros, como um banco de dados, marque “CloudKit”.



Ao selecionar “CloudKit”, o Xcode vai criar um novo Container para sua app, cujo nome será “iCloud”.<bundle ID>.



Em qualquer código fonte que você queira usar o “CloudKit”, você deve importá-lo:



import CloudKit



Você pode criar tipos de registros a serem usados, e também pode adicionar instâncias desses registros. Para isto, pode fazer via código-fonte ou manualmente, usando o Dashboard. Clique no botão “CloudKit Dashboard”, dentro das “Capabilities”, para ser direcionado ao “Dashboard” do seu Container, onde poderá adicionar novos tipos de registro:




Este é o dashboard da app IcloudStorage. Podemos selecionar “Record Types”, clicar no botão “+” e adicionar nosso tipo de registro, com os atributos que desejamos, como na figura anterior.



Após criar um “Record Type”, podemos criar instâncias desse registro, seja no banco de dados privado ou no público. Neste caso, vamos criar uma instância no banco de dados público, clicando em “Public Data / Default Zone”. Selecionamos o nosso Record Type na lista “drop down”, e podemos adicionar instâncias de registros:




Agora, vamos ver o código que recupera os registros existentes no iCloud, e os adiciona à Text View. Abra o arquivo “ViewController.swift”:


  @IBOutlet weak var tvTexto: UITextView!
  override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    self.tvTexto.text = ""
    let container = CKContainer.defaultContainer()
    let publicDB = container.publicCloudDatabase
    let predicate = NSPredicate(value: true)
    let query = CKQuery(recordType: "Problemas", predicate: predicate)

    publicDB.performQuery(query, inZoneWithID: nil) {
      resultados, erro in
      if erro == nil {
        dispatch_async(dispatch_get_main_queue()) {
          for registro in resultados {
            let reg : CKRecord = registro as! CKRecord
            let descricao = reg.objectForKey("descricao") as! String
            self.tvTexto.text = self.tvTexto.text + "\n" + descricao
...

Primeiramente, instanciamos um CKContainer, pegando o Container default, que definimos em “Capabilities”. Depois, pegamos a instância do banco de dados público.



Para executar uma Query, precisamos do predicado (NSPredicate). Você pode consultar por qualquer campo, mas, para facilitar, eu estou recuperando todos os registros (NSPredicate(value: true).



Se quiser saber como montar outros predicados de consulta, veja o documento: https://developer.apple.com/library/mac/documentation/CloudKit/Reference/CKQuery_class/



O método “performQuery”, feito no bando de dados público, serve para executarmos a consulta. Ele permite especificarmos um “handler” assíncrono, onde devemos navegar pelos resultados. Como é um handler executado de forma assíncrona, se quisermos alterar qualquer elemento de UI, isto precisa ser feito dentro de um bloco “dispatch_async”, como já fizemos anteriormente.



O mesmo método ainda faz duas outras coisas, só para mostrar a você como funcionam: Alterar um registro e inclui um novo registro, do mesmo tipo. Eis a parte que faz isso:



            reg.setValue(descricao + " Visto", forKey: "descricao")
            publicDB.saveRecord(reg) {
              registro, erro in
              println("Registro Alterado")
              let novoReg = CKRecord(recordType: "Problemas")
              novoReg.setValue("Eu", forKey: "autor")
              novoReg.setValue(NSDate(), forKey: "dataHora")
              novoReg.setValue("Entendi", forKey: "descricao")
              publicDB.saveRecord(novoReg) {
                registro,erro in
                if erro != nil {
                  println("Erro ao incluir registro: " + erro.localizedDescription)
                }
              }
            }

O método “saveRecord”, do banco de dados, salva o registro e permite criarmos um “handler” assíncrono, de modo a lidarmos com o resultado.



Se você quiser deletar um registro, pode usar o método “deleteRecordWithID”, do banco de dados, passando o “ID” do registro a ser deletado. Ele pode ser obtido através da propriedade “recordID”, do objeto registro (classe CKRecord).



E você pode ver e editar os registros no Dashboard:



Conclusão




Vimos várias formas de comunicação neste capítulo, desde o simples compartilhamento, até o uso do iCloud, que é extremamente importante, pois pode evitar despesas para você, ao fornecer a parte servidora de uma app distribuída.



Mas ainda tem muita coisa sobre Comunicação que eu não falei. Muita coisa sobre iCloud também. Eu procurei ser o mais simples e objetivo possível.


Não se esqueça!

Acesse a página do curso para ver as outras lições, e sempre baixe novamente o zip do curso, pois, como é um trabalho em andamento, pode haver correções de erros e aprimoramentos.

Se tiver dúvidas, use o fórum!

Esse "curso" não dá diploma algum! E todo o material é liberado sob licença "Creative Commons" compartilha igual.

Você pode compartilhar esse material da forma que desejar, desde que mantenha o mesmo tipo de licença e as atribuições de autoria original.


O trabalho "Criando apps para iPhone com Swift " de Cleuton Sampaio de Melo Jr está licenciado com uma Licença Creative Commons - Atribuição-CompartilhaIgual 4.0 Internacional. Isso inclui: Textos, páginas, gráficos e código-fonte.