Mostrando postagens com marcador iPhone. Mostrar todas as postagens
Mostrando postagens com marcador iPhone. Mostrar todas as postagens

sexta-feira, 1 de fevereiro de 2013

@selector, Blocks e NSInvocation

Olá povo,

O iOS trabalha fortemente com o padrão Delegate, onde você determina uma classe que implementa um dado protocolo e a mesma tratará algum tipo de evento. Isso nada mais é do que o padrão de projeto observer (levemente modificado). Mas em diversos pontos da API também vemos uma outra abordagem: as funções/metódos de callback. Os callbacks, bem famosos no JavaScript, são métodos ou funções que são passados como parâmetros para outros métodos, e são chamados em um dado momento da execução do método para qual ele foi passado.
Vou falar nesse post de como podemos usar os callbacks no iOS.

@selector
Uma forma de usar os callbacks é usando selectors. Digamos que você esteja implementando um método que fará uma série de processamento, e ao final você quer avisar para quem chamou esse método que o mesmo acabou. Isso seria a clássica implementação de um Observer (ou Delegate). Mas podemos fazer com selectors dessa forma:
// Precisamos pra chamar objc_msgSend
#import <objc/message.h>

- (void) executar:(SEL)funcaoDeCallback {
  NSLog(@"Um monte de processamento aqui…");
  if ([self respondsToSelector:funcaoDeCallback]){
    objc_msgSend(self, funcaoDeCallback, @"Glauber");
  }
}
O método executar tem um parâmetro do tipo SEL, que é um ponteiro para um método. Dentro do método checamos se a nossa classe (self) responderá aquele selector, ou seja, se ela declarou um método com essa assinatura. Em caso positivo, usamos a função objc_msgSend passando qual o objeto que tem o método, o selector (ou seja, o método) e um parâmetro (que eu quis passar só pra ilustrar). Então, para executarmos um selector, temos sempre que passar o target (que é o objeto que tem o método) e o selector.
Aqui nós usamos a função objc_msgSend ao invés do famoso performSelector. Isso porque com a migração para o ARC, o compilador não consegue garantir que a função existe e então exibe um Warning. Uma alternativa para essa função e continuar usando o performSelector é conforme abaixo:
#pragma clang diagnostic push
#pragma clang diagnostic ignored 
  "-Warc-performSelector-leaks"
[self performSelector:funcaoDeCallback 
  withObject:@"Glauber"];
#pragma clang diagnostic pop
E para chamar o método executar, de modo que depois ele chame o método aqui:
SEL selector = @selector(meuMetodo:);
[self executar:selector];
Digamos que o método aqui ficasse dessa forma:
- (NSString *)meuMetodo:(NSString *)texto {
    NSLog(@"O executar retornou: %@", texto);
    return @"OK";
}
Um exemplo clássico do uso de selector na API é na criação de botões para NavigationBar, onde determinamos o método que será chamado quando clicar no mesmo.
UIBarButtonItem *leftButton = [[UIBarButtonItem alloc] 
  initWithTitle:@"Esquerda" 
  style:UIBarButtonItemStyleBordered 
  target:self action:@selector(meuClick)];


NSIvocation
E como faríamos para pegar o retorno usando @selector? Temos uma outra alternativa. Utilizar a classe NSInvocation.
SEL selector = @selector(aqui:);
    
NSString *parametro = @"Glauber";
    
NSMethodSignature * assinatura = 
  [self methodSignatureForSelector:selector];
NSInvocation * invocacao = [NSInvocation 
  invocationWithMethodSignature:assinatura];
[invocacao setTarget:self];
[invocacao setSelector:selector];
[invocacao setArgument:&parametro atIndex:2];
[invocacao invoke];
    
NSString *retorno;
[invocacao getReturnValue:&retorno];
NSLog(@"Retorno: %@", retorno);
Notem que para passar o parâmetro usamos 2 para o primeiro (e único) parâmetro. Isso porque as posições 0 e 1 estão reservadas para self e _cmd respectivamente.

Blocks
A partir do iOS 4, foi disponibilizado uma outra forma de usar funções de callback, os blocks. Seu propósito é bem similar ao selector. Eles são particularmente úteis para executar ações concorrentes ou para callbacks. As principais vantagens dos blocks são: Eles permitem escrever código o código perto da sua chamada (como inner classes do Java); e permitem acessar variáveis locais. Vejamos abaixo a sintaxe e como ele funciona.
int multiplicador = 7;
int (^meuBloco)(int) = ^(int num) {
    return num * multiplicador;
};
 
NSLog("%d", meuBloco(3));
No código acima, declaramos um bloco que retorna um int, chama-se meuBloco e recebe um inteiro como parâmetro. O nome do parâmetro (num) é definido na inicialização do block. Se quisermos declarar um método que recebe um block que retorna void e recebe uma NSString por exemplo:
- (void)meuMetodoRecebeUmBlock:(void (^)(NSString *texto))bloco{
    NSLog(@"Faça alguma coisa");
    bloco(@"Terminou");
}
E para chamar:
[self meuMetodoRecebeUmBlock:^(NSString* texto){
    NSLog(@"meuMetodoRecebeUmBlock disse: %@", texto);
}];

Outra grande vantagem de utilizar os blocks em relação ao selector, é que não se faz necessário passar o target para o método. Podemos chamá-lo diretamente sem ter a referência do objeto que tem o método. Além disso, conseguimos pegar um retorno de um método mais facilmente do que com o @selector.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

Fontes: 
http://iphonedevblog.com/code/how-to-use-nsinvocation/
http://pragmaticstudio.com/blog/2010/7/28/ios4-blocks-1

quarta-feira, 5 de dezembro de 2012

Curso de iOS à distância

Olá povo,

O CESAR.edu está promovendo o curso à distância de Desenvolvimento de Aplicativos para iOS. As aulas serão ministradas por mim on line e ao vivo, o que permitirá maior interação com os alunos.

A plataforma iOS é uma das líderes no mercado mobile e a demanda por aplicativos para seus dispositivos vem crescendo, e consequente a busca por profissionais capacitados nessa plataforma.
Neste curso serão apresentados os conceitos fundamentais para desenvolvimento de aplicações para os dispositivos móveis da Apple: iPhone, iPad e iPod Touch. O Ambiente de desenvolvimento, a linguagem de programação Objective-C, os componentes de interface gráfica e os principais recursos destes dispositivos, serão demonstrados através de uma abordagem totalmente prática e focadas em problemas reais.

O objetivo do curso é preparar os alunos para desenvolver aplicativos para a plataforma iOS da Apple. Apresentando os principais conceitos, os alunos terão uma base sólida para criar aplicações robustas e que atendam as principais demandas do mercado. Os tópicos apresentados no curso focam em problemas reais, cobrindo os requisitos mais solicitados por aplicações desenvolvidas no mercado.

Aguardo vocês lá!

4br4ç05,
nglauber

terça-feira, 28 de agosto de 2012

iOS: Lendo JSON

Olá povo,

Nesse post vou mostrar como ler JSON utilizando a API nativa do iOS. Como exemplo, vamos obter os principais tópicos do Twitter conhecidos como Top Trends.
Crie um novo projeto no Xcode, e marque para utilizar Storyboards e o ARC (Automatic Reference Counting). No ViewController principal da aplicação, declare um NSMutableArray que armazenará a lista dos tópicos que serão mostrados na UITableView. Declare também um NSMutableData, que armazenará os bytes do arquivo JSON que iremos ler na conexão HTTP.
Note que nossa classe implementa o protocolo NSURLConnectionDelegate, que contém os métodos que são chamados pela conexão HTTP. No nosso caso, estamos utilizando para ler os bytes da conexão ser travar a Thread de UI.

#import <UIKit/UIKit.h>

@interface NGViewController : UITableViewController
  <NSURLConnectionDelegate> {

  NSMutableArray *trends;
  NSMutableData *data;
}
@end
O arquivo de implementação ficará como abaixo:
#import "NGViewController.h"

@implementation NGViewController

- (void)viewDidLoad {
  [super viewDidLoad];

  trends = [NSMutableArray new];
  data = [NSMutableData new];
    
  NSURL *url = [NSURL URLWithString:
    @"https://api.twitter.com/1/trends/23424768.json"];
  NSURLRequest *request = 
    [[NSURLRequest alloc]initWithURL:url];   
  NSURLConnection *conexao = [[NSURLConnection alloc]
    initWithRequest:request delegate:self];
  [conexao start];
}

- (void)viewDidUnload {
  [super viewDidUnload];
  trends = nil;
  data = nil;
}

// Métodos de UITableViewController

- (UITableViewCell *)tableView:
  (UITableView *)tableView 
  cellForRowAtIndexPath:(NSIndexPath *)indexPath {

  UITableViewCell *cell = [tableView
    dequeueReusableCellWithIdentifier:@"Cell"];
    
  cell.textLabel.text = 
    [trends objectAtIndex:indexPath.row];
    
  return cell;
}

- (NSInteger)numberOfSectionsInTableView:
  (UITableView *)tableView {
  return 1;
}

- (NSInteger)tableView:(UITableView *)tableView
  numberOfRowsInSection:(NSInteger)section {
  return trends.count;
}

// NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection
  didReceiveData:(NSData *)pdata {
  [data appendData:pdata];
}


- (void)connectionDidFinishLoading:
  (NSURLConnection *)connection {   
  id jsonObject = [NSJSONSerialization 
    JSONObjectWithData:data 
    options:NSJSONReadingMutableContainers error:nil];
    
  id jsonTrends =  [
    [jsonObject objectAtIndex:0] 
      objectForKey:@"trends"];
        
  for (NSDictionary *trend in jsonTrends) {
    [trends addObject:[trend objectForKey:@"name"]];
  }
    
  [self.tableView reloadData];
}

@end
No método viewDidLoad inicializamos nossos dois atributos e logo em seguida começamos o processo de conexão com o servidor. Notem que a URL termina com 23424768.json, esse número é o woeid do Brasil, se quiser os tópicos mundiais, basta substituir por 1.json. Mas se quiser obter os tópicos de outro país, basta acessar http://developer.yahoo.com/yql/console/ e digitar o comando select * from geo.places where text="Seu Local". Mais informações sobre woeid aqui.
Em seguida criamos um request e enviamos essa solicitação através da conexão. O parâmetro delegate indica que nossa classe será notificada sobre eventos na conexão. O método viewDidUnload liberará os recursos alocados.
Não vou comentar os métodos de UITableViewController, mais detalhes aqui. Na nossa classe implementamos dois métodos do protocolo NSURLConnectionDelegate. O método connection:didReceiveData alimentará o nosso atributo data com os bytes recebidos pela conexão. Já o método connectionDidFinishLoading será chamado quando os dados terminarem de serem lidos. É nele que estamos fazendo a leitura do JSON.
A classe NSJSONSerializarion recebe um objeto NSData e retorna um objeto. Um documento JSON em Objective-C consta basicamente de NSArray ou NSDictionary. Se acessarmos a URL que definimos no primeiro método da nossa classe, vamos visualizar o arquivo JSON e se quisermos vê-lo de uma forma mais amigável, podemos utilizar o site http://jsonviewer.stack.hu/ e colar o texto do arquivo JSON lá. Então será apresentado o documento de uma forma hierárquica. Conforme a figura abaixo:
O elemento com [] é um array, enquanto que o {} é um dicionário (contento chave/valor). Sendo assim, a variável jsonObject contém um array de apenas um elemento. Dentro desse elemento temos um array chamado trends. Esse array está sendo representado pela variável jsonTrends. Em seguida, percorremos esse array (que tem vários dictionaries) e em cada dictionary obtemos o valor da chave name, que contém o nome do tópico mais comentado.
O resultado da aplicação é mostrado abaixo:
Qualquer dúvida, deixem seus comentários,

4br4ç05,
nglauber

quinta-feira, 12 de julho de 2012

Persistência no iOS com Core Data

Olá povo,

Eu já mostrei aqui no blog como persistir informações no iOS utilizando o SQLite. Para quem quer ter um  controle maior sobre o que está sendo salvo no banco de dados, é uma boa opção. Entretanto a Core Data realiza o mapeamento objeto-relacional de uma forma bem interessante e nos poupa de escrever um monte de código de baixo nível (uma vez que a lib do SQLite é escrita em C).

Crie um novo projeto no Xcode do tipo Single View Application e mãos à obra! Se você selecionar o template Master/Detail application no assistente, aparecerá a opção para utilizar Core Data. Isso servirá para incluir a lib do Core Data no projeto e inserir algum código para nós. Se não usar esse template, teremos que adicionar manualmente. Para isso, basta selecionar o projeto, e na aba Build Fases na seção Link Binary With Libraries e clicar em "+". Na lista que será exibida, selecione CoreData.framework.

Vamos agora adicionar ao projeto o arquivo onde ficarão as definições das entidades que serão persistidas no banco. No Xcode, selecione File | New | File... selecione a opção Core Data no lado esquerdo, e então selecione a opção Data Model. Clique em Next e nomeie o arquivo para CarroModel, em seguida,  clique em Create para concluir o assistente.

Selecione o arquivo recém criado, e clique no botão Add Entity que fica na parte inferior. Renomeie a entidade para Carro. Em seguida, clique no botão Add Attribute, selecione o atributo e o renomeie para "nome" e modifique seu tipo (na janela da direita) para String (através da propriedade Attribute Type). Adicione mais dois atributos, placa e ano, e modifique os seus tipos para String e Int16 respectivamente.
Abaixo a imagem do model após nossas alterações.
Vamos agora criar a classe carro que será persistida no banco de dados. Selecione a entidade Carro e clique no menu File | New | File... Na janela que for exibida, selecione Core Data no lado esquerdo, e então selecione NSManagedObject subclass. Será criada a classe Carro conforme abaixo.
// Carro.h -----------------------------
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>


@interface Carro : NSManagedObject

@property (nonatomic, retain) NSString * nome;
@property (nonatomic, retain) NSString * placa;
@property (nonatomic, retain) NSNumber * ano;

@end

// Carro.m -----------------------------
#import "Carro.h"

@implementation Carro

@dynamic nome;
@dynamic placa;
@dynamic ano;

@end
Notem que nossa classe herda de NSManagedObject o que quer dizer que os objetos dessa classe serão persistidos. Outro detalhe é que os atributos são implementados com @dynamic.

Crie uma nova classe chamada RepositorioCarro e deixe-a conforme abaixo.

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

@class Carro;

@interface RepositorioCarro : NSObject {
  NSManagedObjectModel *mom;
  NSPersistentStoreCoordinator *coordinator;
  NSManagedObjectContext *context;
}

- (void) test;
- (Carro *) newCarro;
- (void) salvar;
- (void) excluir:(Carro *)carro;
- (NSArray *) todosCarros;

@end
Nessa classe, declaramos um NSManagedObjectModel, NSPersistentStoreCoordinator e um NSManagedObjectContext. Vamos entender o papel de cada um:
- NSManagedObjectContext é a classe que vai realizar as operações com o banco. Logo, para inserir, alterar, excluir ou obter objetos (NSManagedObject) do banco, utilizamos métodos dessa classe.
- NSPersistentStoreCoordinator é como se fosse a conexão com o banco. É nela onde você define o local onde está armazenado o arquivo do banco e outras configurações adicionais.
- NSManagedObjectModel é o esquema do banco de dados. Que aqui no nosso projeto é o arquivo onde definimos o modelo da classe Carro.

O código abaixo inicia com o "construtor" da classe, que chama o método initContext. Esse método por sua vez chama o initCoordinator que por sua vez chama o initMom.

#import "RepositorioCarro.h"
#import "Carro.h"

@implementation RepositorioCarro

- (id) init {
  self = [super init];  
  [self initContext];
  return self;
}

// Retorna a URL para o diretório Documents
- (NSURL *)appDocsDir {
  return [[[NSFileManager defaultManager] 
    URLsForDirectory:NSDocumentDirectory 
      inDomains:NSUserDomainMask] lastObject];
}

// Carrega o arquivo de modelo
- (void) initMom {
  NSURL *modelURL = [[NSBundle mainBundle] 
    URLForResource:@"CarroModel" 
      withExtension:@"momd"];

  mom = [[NSManagedObjectModel alloc] 
    initWithContentsOfURL:modelURL];
}

// Abre a conexão com o banco
- (void) initCoordinator {
  [self initMom];    
  NSURL *storeURL = [[self appDocsDir] 
    URLByAppendingPathComponent:
     @"CarrosDB.sqlite"];
        
  NSError *error = nil;
  coordinator =
    [[NSPersistentStoreCoordinator alloc]
      initWithManagedObjectModel:mom];

  if (![coordinator 
    addPersistentStoreWithType:NSSQLiteStoreType
    configuration:nil URL:storeURL
    options:nil error:&error]) {
    NSLog(@"Erro... %@",
      [error localizedDescription]);
  }
}

// Inicializa o contexto
- (void) initContext {
  [self initCoordinator];
    
  context = [[NSManagedObjectContext alloc] init];
  [context setPersistentStoreCoordinator:coordinator];
}

- (void) test {
  // Testando inserção
  Carro *carro = [self newCarro];
    
  carro.nome  = @"Uno";
  carro.placa = @"UNO0001";
  carro.ano   = [NSNumber numberWithInt:2000];
    
  [self commit];

  // Testando alteração
  carro = @"Palio";
  [self commit];
  // Testando listagem...
  NSArray *fetchedObjects = [self todosCarros];
  for (Carro *carro in fetchedObjects) {
    NSLog(@"Nome: %@", carro.nome);
    NSLog(@"placa: %@", carro.placa);
  }
}

// A classe carro não deve ter construtor e 
// deve ser inicializada dessa forma.
- (Carro *) newCarro {
  return [NSEntityDescription
    insertNewObjectForEntityForName:@"Carro"
    inManagedObjectContext:context];

}

// As alterações que são feitas nos objetos
// são persistidas no banco ao dar commit
- (void) commit {
    NSError *error;
    if (![context save:&error]) {
      NSLog(@"Erro... %@", 
        [error localizedDescription]);
    }
}

- (void) excluir:(Carro *)carro {
    [context deleteObject:carro];  
}

- (NSArray *) todosCarros {
  NSFetchRequest *fetchRequest = 
    [[NSFetchRequest alloc] init];

  NSEntityDescription *entity = 
    [NSEntityDescription entityForName:@"Carro"
       inManagedObjectContext:context];

  [fetchRequest setEntity:entity];

  NSError *error;
  return [context 
    executeFetchRequest:fetchRequest 
      error:&error];
}

@end

O método newCarro cria uma nova instância de um objeto carro no contexto. Ou seja, se comitarmos esse objeto ele já estará persistido no banco. Se alterarmos uma propriedade de um objeto carro do array que é retornado pelo método todosCarros, e depois chamarmos o método commit, a alteração já é realizada no banco de dados.
O método test, como próprio nome diz, serve para testarmos nossa classe. Onde inserimos, alteramos e listamos as informações do banco.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

Fonte: http://www.raywenderlich.com/934/core-data-on-ios-5-tutorial-getting-started

sábado, 30 de junho de 2012

iOS: Dicas

Olá povo,

Como tô mexendo muito com iOS ultimamente vou registrar aqui algumas pequenas dicas no desenvolvimento de aplicações para iOS.

Dica 1 - Adicionando botões na navigation bar dinâmicamente
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
  initWithTitle:@"Salvar" 
  style:UIBarButtonItemStyleDone 
  target:self action:@selector(salvarDados)];
[self.navigationItem setRightBarButtonItem:saveButton];

Dica 2 - Ocultando o teclado virtual

O teclado virtual do iOS é exibido automaticamente quando você clica em uma caixa de texto (UITextFiedl), se tornando o "primeiro a responder" (firstResponder) a eventos de toque. Entretanto para fechá-la você vai codificar um pouquinho. Digamos que em uma tela você tenha 3 caixas de texto. Quando o usuário estiver na primeira caixa de texto ele terá a opção de pular para o próximo campo clicando no botão Next no teclado virtual. Para habilitar esse botão altere a propriedade Return Button do TextField para Next. Faça o mesmo no segundo (e nos outros que você precisar). No último coloque Done na mesma propriedade.
Agora ligue o evento Did End On Exit de todas as caixas de texto para o método abaixo.

- (IBAction)hideKeyboard:(id)sender {
  if ([sender isEqual:edtName] == YES){
    [edtAddress becomeFirstResponder];
  } else if ([sender isEqual:edtAddress] == YES){
    [edtPhone becomeFirstResponder];
  } else {
    [edtName resignFirstResponder];
    [edtAddress resignFirstResponder];
    [edtPhone resignFirstResponder];
  }
}
Isso resolve o problema da navegação. Mas uma coisa comum é, ao clicarmos fora da caixa de texto, o teclado desaparecer. Para fazer isso, clique na View da tela e mude seu tipo de classe de UIView para UIControl. Depois associe o evento Touch Up Inside para o método acima.
O parâmetro sender identifica qual componente disparou o evento. Se não foi nenhum dos dois primeiros TextFields ocultamos o teclado chamando o método resignFirstResponder (o TextField agora é o "primeiro respondedor"). Pronto! Agora ao clicar em qualquer área da tela, o teclado desaparecerá.

Dica 3 - Editando células da UITableView

Quando precisamos, em algum tipo de listagem, excluir um registro, podemos recorrer a um recurso bacana do iOS: deslizar sobre célula. Quando isso acontece o método tableView:editingStyleForRowAtIndexPath é chamado. Aqui podemos retornar um estilo de edição para a célula: UITableViewCellEditingStyleDelete ou UITableViewCellEditingStyleInsert.
-(UITableViewCellEditingStyle)tableView:
    (UITableView *)tableView 
  editingStyleForRowAtIndexPath:
    (NSIndexPath *)indexPath{

    return UITableViewCellEditingStyleDelete;
}
Quando deslizar o dedo para a direita sobre a célula, aparecerá ao botão Delete na célula. Para tratar o evento desse botão devemos implementa o método abaixo:
-(void)tableView:(UITableView *)tableView 
 didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath {
}
Uma outra opção é colocar utilizar o código abaixo ao clicar em um UIBarButton.
-(void)habilitarExcluir:(id)sender {
  [self.tableView setEditing:
    !self.tableView.editing animated:YES];
}
E realizar o código da exclusão no método abaixo:
- (void)tableView:(UITableView *)tableView 
  commitEditingStyle:(UITableViewCellEditingStyle)editingStyle 
  forRowAtIndexPath:(NSIndexPath *)indexPath {

  if (editingStyle == 
    UITableViewCellEditingStyleDelete) {
  }   
}

Dica 4 - Abrindo uma URL no Browser

O código abaixo também aplica-se a telefone (tel:88990099) e mapas...
NSURL *url = [NSURL URLWithString:
  @"http://nglauber.blogspot.com"];
[[UIApplication sharedApplication] openURL:url];

Dica 5 - iPhone ou iPad?

Em uma aplicação Universal (que roda em iPhone e iPad) para checar se o device é um iPhone (ou iPod) ou iPad utilizamos o código abaixo:
if ([[UIDevice currentDevice] userInterfaceIdiom] == 
  UIUserInterfaceIdiomPhone) {
  //iPhone ou iPod
} else {
  // iPad
}

Dica 6 - Salvando Objetos com NSUserDefaults

Para salvar um objeto todo no NSUserDefaults você deve implementar os métodos que vão serializar e deserializar os dados...
// NSPessoa.h --------------------------------
#import 

@interface NSPessoa : NSObject

@property (nonatomic, strong) NSString *nome;
@property (nonatomic, strong) NSString *email;

@end

// NSPessoa.m --------------------------------
#import "NSPessoa.h"

@implementation NSPessoa

@synthesize nome, email;

- (void)encodeWithCoder:(NSCoder *)encoder {
  [encoder encodeObject:self.nome  forKey:@"nome"];
  [encoder encodeObject:self.email forKey:@"email"];
}

- (id)initWithCoder:(NSCoder *)decoder {
  if((self = [super init])) {
    self.nome  = [decoder decodeObjectForKey:@"nome"];
    self.email = [decoder decodeObjectForKey:@"email"];
  }
  return self;
}
@end

Abaixo o código para carregar e salvar os dados.
// Carregando ----------------------------------
NSUserDefaults *prefs = 
  [NSUserDefaults standardUserDefaults];

NSData *data = [prefs objectForKey:@"pessoa"];
NSPessoa *pessoa = (NSPessoa *)
  [NSKeyedUnarchiver unarchiveObjectWithData: data];

// se os dados da pessoa não existirem, 
// inicia um novo objeto    
if (!pessoa){
  pessoa = [NSPessoa new];
}

// Salvando ------------------------------------
NSData *data = [NSKeyedArchiver 
  archivedDataWithRootObject:pessoa];

NSUserDefaults *defaults = 
  [NSUserDefaults standardUserDefaults];

[defaults setObject:data forKey:@"pessoa"];

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

segunda-feira, 25 de junho de 2012

SQLite no iOS

Olá povo,

Nesse post vou mostrar como utilizar o SQLite em aplicações iOS através da criação de um cadastro simples. Vou utilizar também o recurso de Storyboards (veja esse post) para agilizar a criação das telas.
Comece criando um projeto no Xcode do tipo "Single View Application" que vou nomeá-lo de ExemploSQLiteBlog. Lembre-se de deixar habilitadas as opções Use storyboards e Use automatic reference counting. Após criar o projeto, devemos adicionar a biblioteca do SQLite ao nosso projeto. Para tal, clique sobre o projeto, depois selecione a aba Build phases. Na seção Link Binary With Libraries clique no botão "+" e selecione libsqlite3.dylib.
Uma vez que o projeto está configurado, vamos fazer toda a parte de "negócio" da aplicação, onde criaremos a classe de dados da aplicação, assim como a classe que fará a persistência dos dados da aplicação.
Crie uma nova classe chamada NGContact conforme abaixo (a utilização de prefixos no nome da classe em Objective-C é comum para evitar duplicidade):
// NGContact.h ----------------------
#import <Foundation/Foundation.h>

@interface NGContact : NSObject

@property (assign) NSInteger _id;
@property (strong, nonatomic) NSString *name;
@property (strong, nonatomic) NSString *address;
@property (strong, nonatomic) NSString *phone;

- (id)initWithName:(NSString *)name 
  andAddress:(NSString *)address 
  andPhone:(NSString *)phone;

@end

// NGContact.m ----------------------
#import "NGContact.h"

@implementation NGContact

@synthesize _id = __id, name = _name, 
  address = _address, phone = _phone;

- (id)initWithName:(NSString *)name 
  andAddress:(NSString *)address 
  andPhone:(NSString *)phone {

  self = [super init];
  if (self){
    self.name = name;
    self.phone = phone;
    self.address = address;
  }
  return self;
}

@end
Aqui temos uma classe simples com 4 propriedades (id, nome, endereço e telefone) e um método para inicializar esses atributos. Para quem é "do Java" (como eu) esse método servirá como um construtor (lembrando que Objective-C não tem construtores).
Vamos agora criar a classe que fará a persistência desses objetos e utilizará a API do SQLite. Ela servirá de repositório da aplicação e a chamaremos de NGContactDB, e o arquivo de cabeçalho (.h) deve ficar conforme abaixo.
// NGContactDB.h
#import <Foundation/Foundation.h>
#import <sqlite3.h>

@class NGContact;

@interface NGContactDB : NSObject {
  sqlite3 *contactDB;
  NSString *databasePath;
}

- (void)createDatabase;
- (void)insertContact:(NGContact *)contact;
- (void)updateContact:(NGContact *)contact;
- (void)deleteContact:(NGContact *)contact;
- (NSArray *)getAllContacts;

@end
Como podemos observar, essa classe contém dois atributos, o primeiro é uma referência para sqlite3, e o segundo é o caminho do arquivo do banco. Além disso, temos também cinco métodos:
- createDatabase, que como o próprio nome diz, criará o banco caso ele não exista;
- insertContact, updateContact e deleteContact irão respectivamente inserir, atualizar e excluir um contato no banco;
- e getAllContacts irá obter todos os contatos cadastrados no banco.
Deixe o arquivo de implementação conforme abaixo:
#import "NGContactDB.h"
#import "NGContact.h"
#import <sqlite3.h>

@implementation NGContactDB

// Inicializa o banco -------------------------------
- (void)initDB {   
  // Obtém os diretório de arquivos da aplicação
  NSArray *dirPaths = 
    NSSearchPathForDirectoriesInDomains(
      NSDocumentDirectory, NSUserDomainMask, YES);
  // Obtém o diretório para salvar o arquivo do banco
  NSString *docsDir = [dirPaths objectAtIndex:0];
  // Cria o caminho do arquivo do banco
  databasePath = [[NSString alloc] initWithString: 
    [docsDir stringByAppendingPathComponent: 
      @"contacts.db"]];
  
  const char *dbpath = [databasePath UTF8String];
  // Inicializa o atributo contactDB com o banco  
  if (sqlite3_open(dbpath, &contactDB) != SQLITE_OK){
    NSLog(@"Failed to open/create database - 1");
  }
}

// Cria a tabela no banco ---------------------------
- (void)createDatabase {   
  // Chama o método acima...
  [self initDB];
    
  char *errMsg;
  const char *sql_stmt = "CREATE TABLE IF NOT EXISTS 
    CONTACTS (ID INTEGER PRIMARY KEY AUTOINCREMENT, 
    NAME TEXT, ADDRESS TEXT, PHONE TEXT)";

  // Cria a tabela no banco se ela não existir          
  if (sqlite3_exec(contactDB, sql_stmt, NULL, NULL, 
    &errMsg) == SQLITE_OK) {
    NSLog(@"Database successfully created");
  } else {
    NSLog(@"Failed to create table");
  }
            
  sqlite3_close(contactDB);
}

// Inserir um contato no banco ---------------------
- (void)insertContact:(NGContact *)contact {
  [self initDB];
      
  char *sql = "INSERT INTO CONTACTS (NAME,ADDRESS, 
    PHONE) VALUES (?, ?, ?);";

  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2(contactDB, sql, -1, 
    &stmt, nil) == SQLITE_OK) {

    sqlite3_bind_text(stmt, 1, 
      [contact.name UTF8String], -1, NULL);
    sqlite3_bind_text(stmt, 2, 
      [contact.address UTF8String], -1, NULL); 
    sqlite3_bind_text(stmt, 3, 
      [contact.phone UTF8String], -1, NULL); 
  }
  if (sqlite3_step(stmt) == SQLITE_DONE){
    NSLog(@"Record added");
  } else {
    NSLog(@"Failed to add contact");
  }
  sqlite3_finalize(stmt);
  sqlite3_close(contactDB);
}

// Atualizar contato no banco ----------------------
- (void)updateContact:(NGContact *)contact {
  [self initDB];
      
  char *sql = "UPDATE CONTACTS SET NAME = ?, 
    ADDRESS = ?, PHONE = ? WHERE ID = ?;";

  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2(contactDB, sql, -1, 
    &stmt, nil) == SQLITE_OK) {
    sqlite3_bind_text(stmt, 1, 
      [contact.name UTF8String], -1, NULL);
    sqlite3_bind_text(stmt, 2,
      [contact.address UTF8String], -1, NULL); 
    sqlite3_bind_text(stmt, 3, 
      [contact.phone UTF8String], -1, NULL); 
    sqlite3_bind_int(stmt, 4, contact._id);
  }

  if (sqlite3_step(stmt) == SQLITE_DONE){
    NSLog(@"Record updated");
  } else {
    NSLog(@"Failed to update contact");
  }
  sqlite3_finalize(stmt);
  sqlite3_close(contactDB);
}

// Excluir contato ---------------------------------
- (void)deleteContact:(NGContact *)contact {
  [self initDB];
      
  char *sql = "DELETE FROM CONTACTS WHERE ID = ?;";
  sqlite3_stmt *stmt;
  if (sqlite3_prepare_v2(contactDB, sql, -1, 
    &stmt, nil) == SQLITE_OK) {

    sqlite3_bind_int(stmt, 1, contact._id); 
  }
  if (sqlite3_step(stmt) == SQLITE_DONE){
    NSLog(@"Record removed");
  } else {
    NSLog(@"Failed to removed contact");
  }
  sqlite3_finalize(stmt);
  sqlite3_close(contactDB);
}

// Obter lista de objetos do banco -----------------
- (NSArray *)getAllContacts;
{
  [self initDB];
    
  NSMutableArray *contacts = 
    [[NSMutableArray alloc]init];
    
  sqlite3_stmt *statement;
    
  NSString *querySQL = [NSString stringWithFormat: 
    @"SELECT * FROM contacts"];
        
  const char *query_stmt = [querySQL UTF8String];
        
  if (sqlite3_prepare_v2(contactDB, query_stmt, -1, 
    &statement, NULL) == SQLITE_OK){

    while (sqlite3_step(statement) == SQLITE_ROW)
    {
      NSInteger contactId = 
        sqlite3_column_int(statement, 0);
               
      NSString *nameField = [[NSString alloc] 
        initWithUTF8String:(const char *) 
          sqlite3_column_text(statement, 1)];
                
      NSString *addressField = [[NSString alloc] 
        initWithUTF8String:(const char *) 
          sqlite3_column_text(statement, 2)];
                
      NSString *phoneField = [[NSString alloc] 
        initWithUTF8String:(const char *) 
          sqlite3_column_text(statement, 3)];
                
      NGContact *contact = [[NGContact alloc]init];
      contact._id = contactId;
      contact.name = nameField;
      contact.address = addressField;
      contact.phone = phoneField;
                
      [contacts addObject:contact];
    }
    sqlite3_finalize(statement);
  }
  sqlite3_close(contactDB);
    
  return [NSArray arrayWithArray:contacts];
}

@end
O código está comentado, mas vou fazer algumas observações. O método initDB começa tentando obter o caminho do diretório de documentos, na qual a aplicação pode salvar seus arquivos. Em seguida monta o caminho do arquivo de banco de dados com a variável databasePath. Por fim, utilizamos a função sqlite3_open (isso mesmo, função, já que o sqlite é escrito em C) para abrir o arquivo de banco de dados e inicializar o atributo contactDB.
Já o método createDatabase criará a tabela Contatcts no banco de dados caso ela não exista. Esse método deve ser chamado apenas uma vez durante o ciclo de vida da aplicação. Pois, uma vez criada, a tabela permanecerá no banco. Esse método começa chamado o initDB para inicializar o atributo contactDB depois monta o SQL para criação da tabela. Note que esse SQL é montado com um char*, que é o tipo esperado pela biblioteca (e não NSString).  Para executar o comando no banco, utilizamos a função sqlite3_exec, passando a referência para o banco e a sentença SQL (para os demais parâmetros olhe aqui).
Os métodos insertContact, updateContact e deleteContact são bem similares. Chamamos o método initDB para inicializar o atributo contactDB. Depois declaramos um sqlite3_stmt que é inicializado através da função sqlite3_prepare_v2 que recebe a referência do banco, a instrução SQL e a referência para o statement. Cada uma das "?" será um parâmetro que será preenchida com um valor através da função sqlite3_bind_* (onde * é o tipo do parâmetro que se quer passar). Para executar a instrução SQL, utilizamos a função sqlite3_step, se tudo correr bem, será retornado SQLITE_DONE. Por fim, fechamos o statement e o banco com as funções sqlite3_finalizesqlite3_close respectivamente.
O método que retorna uma lista de objetos NGContact utiliza os mesmos conceitos dos métodos anteriores, a diferença é que para percorrer os registros retornados, a função sqlite3_step retorna SQLITE_ROW quando temos uma linha para ler. E para ler essa coluna, utilizamos as funções sqlite3_column_* (onde * é o tipo da coluna).

Bem pessoal. Da parte de SQLite é isso, em um próximo post vou mostrar como integrar essa classe com uma UITableViewController (para mostrar os dados) e criar uma tela para inserir e alterar os registros. Mas quem quiser ir se adiantando, pode usar esse post aqui.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

segunda-feira, 11 de junho de 2012

Facebook API no iOS

Olá povo,

Logo após escrever um post sobre como utilizar a API do Facebook no Android, tive que fazer o mesmo para o iOS, então resolvi compartilhar com vocês.

Registrando a aplicação do Facebook

Devemos primeiro criar/registrar nossa aplicação no Facebook. Para tal, acesse: https://developers.facebook.com/apps, faça o login com uma conta do facebook (aconselho criar uma conta de teste) e clique na opção Create New App. A janela abaixo será exibida:
Digite o nome da aplicação e clique em Continue.

Nota: Por questões de segurança o Facebook pede que você coloque uma informação que te identifique. No meu caso, eu tive que preencher o número do meu celular. Uma vez que cadastrei, o Facebook envia um código de ativação via SMS. Clique no link que foi enviado para o seu aparelho ou digite o código de confirmação para concluir o seu cadastro.

Uma vez que a aplicação está cadastrada, habilite a opção Native iOS App.


Preencha o campo iOS Bundle ID com o ID da sua aplicação (configurado no Xcode) e habilite as opções Configured for iOS SSO e iOS Native Deep Linking.

Importanto o projeto do Facebook SDK

O código do Facebook SDK está disponível em um repositório GIT, mas você pode baixá-lo no formato ZIP. Acesse https://github.com/facebook/facebook-ios-sdk e selecione a opção Download this repository as a ZIP file". Terminado o download, descompacte onde desejar.

Após descompactar, abra o terminal para gerar a lib que nos permitirá utilizar o Automatic Reference Counting (ARC) recurso do iOS que nos permite delegar para o sistema operacional limpeza dos objetos da memória. Digite o seguinte comando no diretório onde você descompactou o facebook SDK:

./scripts/build_facebook_ios_sdk_static_lib.sh

Será gerado o diretório lib/facebook. Você irá copiar esse diretório para o diretório da sua aplicação.

Criando o projeto

Crie um novo projeto no Xcode do tipo Single View Application e clique em Next. Preencha no nome do projeto e o company identifier de modo que juntos formem o campo iOS Bundle ID utilizado no cadastro do Facebook. Conclua o assistente de criação do projeto. Adicione a pasta lib que foi gerada na seção anterior para seu projeto (botão direito sobre o projeto, Add files).

Abra o delegate da sua aplicação e deixe-o conforme abaixo:

#import <UIKit/UIKit.h>
#import "FBConnect.h"

@interface NGAppDelegate : UIResponder 
  <UIApplicationDelegate, FBSessionDelegate>

@property (strong, nonatomic) UIWindow *window;

@property (strong, nonatomic) Facebook *facebook;

@end
#import "NGAppDelegate.h"

@implementation NGAppDelegate

@synthesize window = _window;
@synthesize facebook;

// Método que trata a autenticação após o facebook
// validar o login e senha do usuário no browser
- (BOOL)application:(UIApplication *)application 
  openURL:(NSURL *)url 
  sourceApplication:(NSString *)sourceApplication 
  annotation:(id)annotation {

  return [facebook handleOpenURL:url];
}

// *** Métodos do Protocolo FBSessionDelegate
// Salva o accessToken e data de expirar
-(void)fbDidLogin {
  NSUserDefaults *defaults = 
    [NSUserDefaults standardUserDefaults];
  [defaults setObject:[facebook accessToken] 
    forKey:@"FBAccessToken"];
  [defaults setObject:[facebook expirationDate] 
    forKey:@"FBExpirationDateKey"];
  [defaults synchronize];
}

// Ao fazer logout, apaga o accessToken
-(void)fbDidLogout {
  NSUserDefaults *defaults = 
    [NSUserDefaults standardUserDefaults];
  [defaults removeObjectForKey:@"FBAccessToken"];
  [defaults removeObjectForKey:@"FBExpirationDateKey"];
  [defaults synchronize];    
}

-(void)fbDidNotLogin:(BOOL)cancelled {}

-(void)fbDidExtendToken:(NSString *)accessToken 
  expiresAt:(NSDate *)expiresAt{}

- (void)fbSessionInvalidated {}

// *** Métodos de UIApplicationDelegate ***
- (BOOL)application:(UIApplication *)application 
  didFinishLaunchingWithOptions:
   (NSDictionary *)launchOptions
{
  facebook = [[Facebook alloc] 
    initWithAppId:@"ID_SUA_APP" andDelegate:self];
    
  // Carregar o AccessToken pra ver se está logado
  NSUserDefaults *defaults = 
    [NSUserDefaults standardUserDefaults];
  if ([defaults objectForKey:@"FBAccessToken"] && 
      [defaults objectForKey:@"FBExpirationDateKey"]){

    facebook.accessToken = 
      [defaults objectForKey:@"FBAccessToken"];
    facebook.expirationDate = 
      [defaults objectForKey:@"FBExpirationDateKey"];
  }
  // Se não estiver logado, exibe a tela de login
  if (![facebook isSessionValid]) {

    NSArray *permissions = [NSArray 
      arrayWithObjects:@"publish_stream", nil];
        
    [facebook authorize:permissions];
  }                            
    
  return YES;
}
Agora vamos mexer no ViewController
#import <UIKit/UIKit.h>
#import "Facebook.h"

@interface NGViewController : UIViewController 
  <FBRequestDelegate> {

  Facebook *facebook;    
}
@property (weak, nonatomic) IBOutlet 
  UITextField *txtMensagem;

- (IBAction)updateStatusClick:(id)sender;
- (IBAction)logoutClick:(id)sender;

@end
#import "NGViewController.h"
#import "NGAppDelegate.h"

@implementation NGViewController
@synthesize txtMensagem;

- (void)viewDidLoad
{
  [super viewDidLoad];
    
  NGAppDelegate *delegate = (NGAppDelegate *)
    [UIApplication sharedApplication].delegate;
  facebook = delegate.facebook;
}

- (void)viewDidUnload
{
  [self setTxtMensagem:nil];
  [super viewDidUnload];
  facebook = nil;
}

- (BOOL)shouldAutorotateToInterfaceOrientation:
  (UIInterfaceOrientation)interfaceOrientation
{
  return (interfaceOrientation != 
    UIInterfaceOrientationPortraitUpsideDown);
}

- (IBAction)updateStatusClick:(id)sender {
  NSMutableDictionary *params = 
    [[NSMutableDictionary alloc]init];

  [params setObject:txtMensagem.text 
    forKey:@"message"];
    
  [facebook requestWithGraphPath:@"me/feed" 
    andParams:params andHttpMethod:@"POST" 
    andDelegate:self];
}

- (IBAction)logoutClick:(id)sender {
  if (![facebook isSessionValid]) {
    NSArray *permissions = [NSArray arrayWithObjects:
      @"publish_stream", nil];
        
    [facebook authorize:permissions];
  } else {
    [facebook logout];
  }
}

-(void)request:(FBRequest *)request 
  didReceiveResponse:(NSURLResponse *)response {
  UIAlertView *alert = [[UIAlertView alloc] 
    initWithTitle:@"Info" message:response.description 
    delegate:nil cancelButtonTitle:@"OK" 
    otherButtonTitles:nil, nil];
  [alert show];
}

@end
Abra agora o *.plist com o nome da sua aplicação no modo XML e adicone o trecho abaixo dentro da tag <dic>:
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>fbID_SUA_APP</string>
    </array>
  </dict>
</array>
Arraste uma caixa de texto e ligue com o Outlet txtMensagem. Arraste dois botões e associe o evento Touch Up Inside aos métodos logoutClick e updateStatusClick. Feito isso é só rodar a aplicação:

Na primeira vez que a aplicação for executada, será exibida a tela acima, onde o usuário deverá autorizar nossa aplicação a utilizar a conta do facebook do usuário. Após aceitar a permissão, a nossa aplicação volta para foreground, e aparece conforme abaixo:
Pronto! Digite sua mensagem e clique no botão para atualizar o status do facebook.

Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

segunda-feira, 28 de maio de 2012

iOS: UIActionSheet + UIDatePicker

Olá povo,

Hoje pela manhã estava fazendo uma telinha no iOS que tinha 2 campos de data. E como o iOS tem um componente padrão para datas, o UIDatePicker, resolvi usá-lo. Entretanto ele ocupa muito espaço e fica inviável colocar dois na tela. Foi aí que me surgiu a ideia de utilizar uma UIActionSheet personalizada. Onde eu poderia selecionar a data, e em seguida a ActionSheet desapareceria da tela.

Para exemplificar isso, crie um novo projeto no Xcode, se tiver dúvidas, dê uma olhada aqui. Na tela da sua aplicação, arraste dois Labels e dois Buttons e deixe-os conforme abaixo. Nos botões, mudei a propriedade Type para Detail Disclosure e a propriedade Tag para 1 e 2 respectivamente.

Crie os Outlets para os dois Labels e utilize o método changeDateClick no Touch Up Inside dos dois botões. Nós saberemos qual botão foi clicado através da propriedade Tag.
#import <UIKit/UIKit.h>

@interface NGDetailViewController : UIViewController 
  <UIActionSheetDelegate>

- (IBAction)changeDateClick:(id)sender;

@property (weak, nonatomic) 
  IBOutlet UILabel *txtData1;
@property (weak, nonatomic) 
  IBOutlet UILabel *txtData2;

@end
Note que nossa classe implementa o protocolo UIActionSheetDelegate, necessário para sabermos qual botão o usuário clicou na ActionSheet. Deixe a implementação da classe conforme abaixo:
#import "NGDetailViewController.h"

@implementation NGDetailViewController

@synthesize txtData1;
@synthesize txtData2;

- (id)initWithNibName:(NSString *)nibNameOrNil 
  bundle:(NSBundle *)nibBundleOrNil {

  self = [super initWithNibName:
    nibNameOrNil bundle:nibBundleOrNil];
  if (self) {
      // Custom initialization
  }
  return self;
}

- (void)viewDidLoad {
  [super viewDidLoad];
}

- (void)viewDidUnload {
    [self setTxtData1:nil];
    [self setTxtData2:nil];
    [super viewDidUnload];
}

- (BOOL)shouldAutorotateToInterfaceOrientation:
  (UIInterfaceOrientation)interfaceOrientation {
  return (interfaceOrientation != 
    UIInterfaceOrientationPortraitUpsideDown);
}

- (IBAction)changeDateClick:(id)sender {
  UIButton *button = sender;
    
  UIDeviceOrientation orientation = 
    [[UIDevice currentDevice] orientation];

  int topMargin = 
    (orientation == UIDeviceOrientationPortrait) ? 
      40 : 60;
    
  UIActionSheet *dateActionSheet = 
    [[UIActionSheet alloc] initWithTitle:
      @"Selecione a data" 
      delegate:self 
      cancelButtonTitle:@"Cancelar" 
      destructiveButtonTitle:nil 
      otherButtonTitles:@"OK", nil];
  [dateActionSheet setTag:button.tag];
  [dateActionSheet setActionSheetStyle:
    UIActionSheetStyleBlackTranslucent];
  [dateActionSheet showInView:self.view];    
  [dateActionSheet setFrame:CGRectMake(
    0, topMargin, self.view.frame.size.width, 400)];
}

- (void)willPresentActionSheet:
  (UIActionSheet *)actionSheet {

  // Inicializa o picker e adiciona ao actionSheet
  UIDatePicker *pickerView = 
    [[UIDatePicker alloc] init];
    
  [pickerView setDatePickerMode:UIDatePickerModeDate];
  [actionSheet insertSubview:pickerView atIndex:1];
  // A actionSheet tem 3 subviews agora,
  // o picker e os dois botões  
  UIView *titleView   = 
    [actionSheet.subviews objectAtIndex:0];
  UIButton *btnOk     =  
    [actionSheet.subviews objectAtIndex:2];
  UIButton *btnCancel = 
    [actionSheet.subviews objectAtIndex:3];
    
  UIDeviceOrientation orientation = 
    [[UIDevice currentDevice] orientation];
    
  if (orientation == UIDeviceOrientationPortrait) {
    // Mudou o Y do picker para ficar abaixo do título
    pickerView.frame  = CGRectMake(
      pickerView.frame.origin.x, 
      titleView.frame.size.height + 20, 
      pickerView.frame.size.width, 
      pickerView.frame.size.height);

    // Aqui também... só o Y, ficando abaixo do picker
    btnOk.frame = CGRectMake(
      btnOk.frame.origin.x, 
      pickerView.frame.origin.y + 
        pickerView.frame.size.height + 10, 
      btnOk.frame.size.width, 
      btnOk.frame.size.height);
    
    // De novo só o Y, ficando abaixo do btnOk
    btnCancel.frame = CGRectMake(
      btnCancel.frame.origin.x, 
      btnOk.frame.origin.y + 
        btnOk.frame.size.height + 10, 
      btnCancel.frame.size.width, 
      btnCancel.frame.size.height);

  } else {
    // Aqui dei um espaço de 10 em X,
    // em Y ficou abaixo do título 20px,
    // na largura, o picker ocupa 2/3 da tela,
    // e altura não mudou
    pickerView.frame  = CGRectMake(
      10, 
      titleView.frame.size.height + 20, 
      ((pickerView.frame.size.width) / 3 ) * 2, 
      pickerView.frame.size.height);

   // No eixo X, o botão fica a direita do picker,
   // em Y, fica alinhado com o topo do picker,
   // a largura do botão é área entre o fim do 
   // picker e a margem direita da tela,
   // a altura não mudou
   btnOk.frame = CGRectMake(
     pickerView.frame.origin.x + 
       pickerView.frame.size.width + 10, 
     pickerView.frame.origin.y, 
     (actionSheet.frame.size.width - 
       pickerView.frame.size.width -  
       pickerView.frame.origin.x - 20), 
     btnOk.frame.size.height);
        
   // Aqui é similar ao anterior, a diferença é 
   // que em Y, o botão está abaixo do btnOk.
   btnCancel.frame = CGRectMake(
     pickerView.frame.origin.x + 
       pickerView.frame.size.width + 10, 
     btnOk.frame.origin.y + 
       btnOk.frame.size.height + 10,
     (actionSheet.frame.size.width - 
       pickerView.frame.size.width - 
       pickerView.frame.origin.x - 20), 
     btnCancel.frame.size.height);
  }
}

- (void)actionSheet:(UIActionSheet *)actionSheet 
  clickedButtonAtIndex:(NSInteger)buttonIndex {
  if (buttonIndex == 0){
    UIDatePicker *picker = 
      [actionSheet.subviews objectAtIndex:1];
        
    NSString *dataStr = [NSDateFormatter 
      localizedStringFromDate:picker.date 
      dateStyle:NSDateFormatterShortStyle 
      timeStyle:NSDateFormatterNoStyle];
        
    if (actionSheet.tag == 1){
      txtData1.text = [NSString 
        stringWithFormat:@"Data: %@", dataStr];
    } else {
      txtData2.text = [NSString 
        stringWithFormat:@"Data: %@", dataStr];
    }
  }
}

@end

O método shouldAutorotateToInterfaceOrientation define que nossa aplicação apenas não suporta a orientação de Portrait invertido (ou seja, o aparelho de cabeça pra baixo).
A implementação do método changeDateClick inicia pegando a referência do botão que foi clicado. Em seguida pegamos a orientação do aparelho para definir a distância da ActionSheet para margem superior do aparelho. Depois criamos a ActionSheet passando as informações necessárias: título, botões, e o mais importante, o delegate (ou listener) que será chamado quando clicar em algum dos botões. Como nossa classe está implementando esse protocolo, passamos a instância da prórpria classe (self).
Na linha seguinte, setamos a tag da actionSheet para podermos identificar qual dos botões foi clicado para alterar a caixa de texto correspondente. Por fim, alteramos a área (frame) da actionSheet.

O método willPresentActionSheet irá personalizar a actionSheet, adicionando o UIDateTimePicker e reposicionando os botões de acordo com a orientação do aparelho. O código deste método está comentado. Então não vou entrar em detalhes.

O último método, assim como o anterior, é definido no protocolo UIActionSheetDelegate, ele vai ver se clicamos no primeiro botão (OK) e em caso positivo, pega a referência do UIDatePicker e obtém a data selecionada no componente. Em seguida, converte a data em uma NSString usando a classe NSDateFormatter. Feito isso, verifica a tag da actionSheet e altera o Label correspondente.

Se você executar a aplicação e clicar em um dos botões, a actionSheet é exibida abaixo.
Aproveite e teste a aplicação também em landscape.
É isso pessoal. Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

sexta-feira, 11 de maio de 2012

iOS: Storyboards

Olá povo,

Mais vídeo-post aqui no blog. Desta vez, falando sobre Storyboard, um novo recurso do iOS 5 SDK. Com ele podemos criar todo o fluxo navegacional da nossa aplicação com apenas alguns cliques.

Espero que gostem,
Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

sábado, 14 de abril de 2012

iOS: Lendo XML

Olá povo,

Neste post vou mostrar como ler arquivos XML em aplicações iOS. O XML que estou lendo, eu baixei no site do livro Google Android do Ricardo Lecheta (eu tento, mas não deixo o Android :)
O modelo do estrutura do XML que iremos ler é apresentado abaixo:
<?xml version="1.0" encoding="utf-8"?>
<carros>
  <carro>
    <nome>Modelo do Carro</nome>
    <desc>Descrição do Carro</desc>
    <url_info>http://www.ferrari.com/</url_info>
    <url_foto>http://www.livroandroid.com.br/</url_foto>
  </carro>
  ...
</carros>
Crie um novo projeto no Xcode utilizando o template "Single View Application". Agora acesse o site do livro e baixe o XML, em seguida adicione-o ao seu projeto no Xcode na pasta "Supporting Files". A estrutura do projeto (no final) deve ficar conforme abaixo:

Como você deve ter observado, o carro descrito no XML tem os atributos nome, desc, url_info e url_foto. Vamos criar uma classe que representará esse objeto em código.
// Carro.h ---------------------------------------
#import <Foundation/Foundation.h>

@interface Carro : NSObject

@property (strong, nonatomic) UIImage *image;
@property (strong, nonatomic) NSString *nome;
@property (strong, nonatomic) NSString *descricao;
@property (strong, nonatomic) NSString *urlInfo;
@property (strong, nonatomic) NSString *urlFoto;

@end
// Carro.m ---------------------------------------
#import "Carro.h"

@implementation Carro

@synthesize nome, descricao, urlFoto, urlInfo, image;

@end
Vou alterar o AppDelegate da aplicação para ficar desta forma:
// NGAppDelegate.h -------------------------------
#import <UIKit/UIKit.h>

@class NGViewController;

@interface NGAppDelegate : 
  UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) 
  UIWindow *window;
@property (strong, nonatomic) 
  UINavigationController *navigationController;

@end
// NGAppDelegate.m -------------------------------
#import "NGAppDelegate.h"
#import "NGViewController.h"

@implementation NGAppDelegate

@synthesize window = _window;
@synthesize navigationController;

- (BOOL)application:
   (UIApplication *)application
     didFinishLaunchingWithOptions:
   (NSDictionary *)launchOptions{

  self.window = [[UIWindow alloc]
     initWithFrame:[[UIScreen mainScreen] bounds]];

  NGViewController *viewController =
     [[NGViewController alloc] initWithNibName:
       @"NGViewController" bundle:nil];

  navigationController =
     [[UINavigationController alloc]
       initWithRootViewController:viewController];

  self.window.rootViewController =
     self.navigationController;

  [self.window makeKeyAndVisible];
  return YES;
}
// Outros métodos não alterados
@end
Nesse template, a aplicação é criada com uma UIViewController simples. Vamos utilizar uma UITableViewController que listará os dados lidos através de um objeto da class NSXMLParser. Sua utilização é bem similar ao SAX do Java, e teremos que implementar um protocolo NSXMLParserDelegate, e a medida que as tags são lidas os métodos desse protocolo são chamados.
#import <UIKit/UIKit.h>
#import "Carro.h"

@interface NGViewController : UITableViewController
   <NSXMLParserDelegate>{

  NSMutableArray *carros;
  Carro *carro;
  NSString *currTag;
}

@end
Um NSMutableArray armazenará os objetos Carro criados a partir da leitura do XML. Durante o parser do XML, precisamos saber que tag está sendo lida, para tal setarmos o atributo currTag com tag que corresponde a propriedade da classe Carro. Por exemplo, quando o parser encontrar uma tag "carro", criamos um objeto Carro, já quando encontramos uma tag "nome", devemos definir o a propriedade nome do objeto carro. Por fim, ao encontrar a tag "/carro" devemos adicionar o objeto carro na lista.
Vamos agora a implementação da tela (lembrando que só estamos mostrando os métodos alterados):
#import "NGViewController.h"
#import "Carro.h"

@implementation NGViewController

- (void)viewDidLoad{
  [super viewDidLoad];
  self.title = @"Carros";
      carros = [[NSMutableArray alloc] init];
  // Pega o caminho do arquivo XML
  NSString *xmlPath = [[NSBundle mainBundle]
     pathForResource:@"carros" ofType:@"xml"];
  // Obtém os bytes do arquivo
  NSData *xmlData =
     [NSData dataWithContentsOfFile:xmlPath];
  // Cria o objeto que fará o parser do XML
  NSXMLParser *xmlParser =
     [[NSXMLParser alloc] initWithData:xmlData];
  [xmlParser setDelegate:self];
  [xmlParser parse];
}

// Métodos chamado quando NSXMLParser
// inicia a leitura de uma TAG
-(void)parser:(NSXMLParser *)parser
   didStartElement:(NSString *)elementName
   namespaceURI:(NSString *)namespaceURI
   qualifiedName:(NSString *)qName
   attributes:(NSDictionary *)attributeDict {

  currTag = elementName;      
  // Se achou a TAG carro, crie um novo objeto
  if ([currTag compare:@"carro"] == NSOrderedSame){
    carro = [[Carro alloc] init];  
  }
}

// Método chamado quando vai ler o 
// conteúdo da TAG
-(void)parser:(NSXMLParser *)parser
    foundCharacters:(NSString *)string {

  if ([currTag compare:@"nome"] ==
     NSOrderedSame && carro.nome == nil){
    carro.nome = string;
  } else if ([currTag compare:@"desc"] ==
     NSOrderedSame && carro.descricao == nil){
    carro.descricao = [string
       stringByTrimmingCharactersInSet:
        [NSCharacterSet
           whitespaceAndNewlineCharacterSet]];
  } else if ([currTag compare:@"url_info"] ==
     NSOrderedSame && carro.urlInfo == nil){
    carro.urlInfo = [string
       stringByTrimmingCharactersInSet:
        [NSCharacterSet
            whitespaceAndNewlineCharacterSet]];
  } else if ([currTag compare:@"url_foto"] ==
     NSOrderedSame && carro.urlFoto == nil){
    carro.urlFoto = [string
       stringByTrimmingCharactersInSet:
        [NSCharacterSet
           whitespaceAndNewlineCharacterSet]];
  }
}

// Método chamado quando termina de ler uma tag
-(void)parser:(NSXMLParser *)parser
   didEndElement:(NSString *)elementName
   namespaceURI:(NSString *)namespaceURI
   qualifiedName:(NSString *)qName {
  // Se a tag lida for "carro", adiciona à lista
  if ([elementName compare:@"carro"]==NSOrderedSame){
    [carros addObject:carro];
  }
}

// Método de UITableViewController
-(NSInteger)tableView:(UITableView *)tableView
   numberOfRowsInSection:(NSInteger)section {
  return [carros count];
}

-(NSInteger)numberOfSectionsInTableView:
  (UITableView *)tableView {
  return 1;
}

-(UITableViewCell *)tableView:(UITableView *)
  tableView cellForRowAtIndexPath:
   (NSIndexPath *)indexPath {
  static NSString *CellIdentifier = @"Cell";
  UITableViewCell *cell = [tableView
    dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) {
    cell = [[UITableViewCell alloc]
       initWithStyle:UITableViewCellStyleSubtitle
       reuseIdentifier:CellIdentifier];
  }
  Carro *_carro = [carros objectAtIndex:indexPath.row];
  cell.textLabel.text = _carro.nome;
  cell.detailTextLabel.text = _carro.urlInfo;
  return cell;
}

-(void)tableView:(UITableView *)tableView
   didDeselectRowAtIndexPath:(NSIndexPath *)indexPath {
  // Obtém o carro da linha selecionada
  Carro *_carro = [carros objectAtIndex:indexPath.row];
  // Abre o site do carro selecionado  
  NSURL *url = [NSURL URLWithString:_carro.urlInfo];
  [[UIApplication sharedApplication] openURL:url];
}
@end
O código acima está comentado e parte relacionada com a UITableViwController foi detalhada nesse post aqui.

Feito isso, precisamos fazer alguns ajustes no NIB. Abra o NGViewController.xib e remova a UIView e adicione uma UITableViewController. Ligue esse componente ao Outlet da propriedade view, e o dataSource e delegate.
Além disso, alterei também a propriedade "Top Bar" para "Navigation Bar". Feito isso, podemos rodar nossa aplicação. O resultado é apresentado abaixo.
Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

terça-feira, 3 de abril de 2012

iOS: Internacionalização

Olá povo,

Aqui vai o primeiro vídeo-post aqui do blog: Internacionalização no iOS.


Espero que vocês gostem. Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

sábado, 31 de março de 2012

iOS: UITableView

Olá povo,

Depois de estudar por algumas vezes a programação para a plataforma iOS, resolvi escrever uma série de posts sobre o assunto. Como eu já fiz um "Hello World" e um exemplo básico de tratamento de evento (aqui e aqui respectivamente) vou começar falando do componente UITableView. Esse componente serve para exibir informações em uma lista.
Vamos começar criando um novo projeto no XCode. Selecione File > New > New Project... Na janela que for exibida, selecione Empty Application. Depois preencha os campos conforme a imagem abaixo.

Clique em Next e depois selecione em que diretório deseja salvar o projeto e clique em Create. Será criado um projeto vazio com a estrutura mostrada abaixo:
Neste momento o projeto não faz nada, então vamos criar a primeira tela que exibirá uma lista de nomes. Para tal, clique com o botão direito sobre o projeto e selecione New File...
Selecione UIViewController subclass e clique em Next. No campo Class, preencha com ListagemViewController e em Subclass of, coloque UITableViewController. Clique em Next e em seguida, Create. Criada a classe que representará a primeira tela da aplicação vamos alterar o NGAppDelegate para instanciar nossa tela. No arquivo .h adicione a propriedade do tipo UINavigationController. Ela servirá para abrirmos uma outra tela e já controlar o fluxo entre elas.
#import <UIKit/UIKit.h>

@interface NGAppDelegate : 
  UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) 
  UIWindow *window;

@property (strong, nonatomic) 
  UINavigationController *navegador;

@end
No arquivo .m já foram criados alguns métodos pelo template do XCode, mas só vamos mexer no application:didFinishLaunchingWithOptions.
#import "NGAppDelegate.h"
#import "ListagemViewController.h"

@implementation NGAppDelegate

@synthesize window = _window;
@synthesize navegador;

- (BOOL)application:(UIApplication *)application 
  didFinishLaunchingWithOptions:(NSDictionary *)opts
{
  self.window = [[UIWindow alloc] initWithFrame:[
    [UIScreen mainScreen] bounds]];

    
  ListagemViewController *lista = 
    [[ListagemViewController alloc] init];

  navegador = [[UINavigationController alloc]
    initWithRootViewController:lista];
    
  self.window.backgroundColor = [UIColor whiteColor];
    
  self.window.rootViewController = navegador;
    
  [self.window makeKeyAndVisible];
  return YES;
}
Nesse método instanciamos a nossa tela, e em seguida instanciamos o UiNavigationController passando a nossa tela como tela "raiz", ou seja, a principal. Depois associamos o UINavigationController ao objeto UIWindow, que representa a tela do aparelho.
Neste ponto, você já pode mandar rodar a aplicação, mas não teremos nada para listar. Então vamos a implementação da nossa listagem. No arquivo ListagemViewController.h, declare um array chamado nomes, conforme abaixo:
#import <UIKit/UIKit.h>

@interface ListagemViewController : 
  UITableViewController {

  NSArray *nomes;
}

@end
Como podemos observar, nossa classe herda de UITableViewController. Essa classe implementa dois protocolos (que em Java são interfaces) UITableViewDelegate e UITableViewDataSource. O primeiro trata de eventos disparados pela lista e o segundo define métodos que irão prover informações para a lista. Ao abrirmos o arquivo, podemos notar que temos vários métodos implementados, por isso só vou colocar no código abaixo os métodos que teremos que modificar.
#import "ListagemViewController.h"

@implementation ListagemViewController

#pragma mark - View lifecycle

- (void)viewDidLoad
{
  [super viewDidLoad];
    
  self.navigationItem.title = @"Listagem";
    
  nomes = [NSArray arrayWithObjects:
    @"Nelson", @"Glauber", 
    @"Vasconcelos", @"Leal", nil];
}

// ... um monte de métodos :)

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:
  (UITableView *)tableView
{
  return 1;
}

- (NSInteger)tableView:(UITableView *)tableView 
  numberOfRowsInSection:(NSInteger)section
{
  return nomes.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView
  cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
  static NSString *CellIdentifier = @"Cell";
    
  UITableViewCell *cell = [tableView 
    dequeueReusableCellWithIdentifier:CellIdentifier];
  if (cell == nil) {
    cell = [[UITableViewCell alloc] 
      initWithStyle:UITableViewCellStyleDefault 
      reuseIdentifier:CellIdentifier];
  }

  cell.textLabel.text = 
    [nomes objectAtIndex:indexPath.row];
    
  return cell;
}

@end
No método viewDidLoad, inicializamos nossa lista de nomes e alteramos o título da tela. Já no método numberOfSectionsInTableView retornamos a quantidade de sessões que a lista terá. As sessões servem para agrupar opções de uma lista, mas no nosso caso, teremos apenas uma. No método tableView:numberOfRowsInSection retornamos quantas linhas têm cada sessão, como só temos uma, retornamos a quantidade de itens do nosso array de pessoas.
O último método que alteramos foi o tableView:cellForRowAtIndexPath, ele cria um objeto UITableViewCell para cada item da lista. Para evitar a criação de muitos objetos ele tenta reaproveitar linhas que não estejam mais visíveis na tela. Isso é feito no método dequeueReusableCellWithIdentifier:CellIdentifier. Caso não haja uma linha pra reciclar, criamos uma UITableViewCell com o estilo padrão (UITableViewCellStyleDefault). Em seguida, alteramos o texto da UITableViewCell (que internamente contém um UILabel) utilizando o nosso array de nomes. Para obter a posição a ser exibida, utilizamos a propriedade row o parâmetro indexPath. Pronto! Basta rodar nossa aplicação, e o resultado deverá ficar como abaixo:
Vamos criar uma tela para exibir o item selecionado. Botão direito no projeto, New File... Selecione UIViewController subclass (como fizemos anteriormente) e clique em Next. O nome da classe será DetalheViewController e será subclasse de UIViewController. Marque a opção With XIB for user interface. Clique em Next e depois em Create.
Abra o DetalheViewController.xib e arraste um Label para a tela e faça os ajustes de posicionamento e tamanho que desejar. Em seguida, vamos criar o IBOutlet para esse label: clique com o botão direito sobre o botão, e em Referencing Outlets clique em New Referencing Outlet e arraste para o arquivo .h.
Será exibido um popup para preencher o nome do nosso Outlet. Preencha com txtDetalhe e clique em Connect. Em seguida, vou adicionar a propriedade "texto" para essa tela que será atribuída pela tela de listagem. Quando clicarmos em um item da lista, criaremos uma instância de DetalheViewController a atribuiremos essa propriedade com o item selecionado da lista. O arquivos .h e .m deverão ficar como abaixo (lembrando só listamos o que foi modificado).
#import <UIKit/UIKit.h>

@interface DetalheViewController : UIViewController

@property (weak, nonatomic) 
  IBOutlet UILabel *txtDetalhe;

@property (strong, nonatomic) NSString *texto;

@end
#import "DetalheViewController.h"

@implementation DetalheViewController
@synthesize txtDetalhe, texto;

- (void)viewDidLoad
{
    [super viewDidLoad];
    txtDetalhe.text = texto;
}
Estamos quase lá. Agora volte ao arquivo ListagemController.m implemente o método tableView:didSelectRowAtIndexPath que é o método chamado quando clicamos em um item da lista.
// Adicionar import no começo do arquivo
#import "DetalheViewController.h"

- (void)tableView:(UITableView *)tableView 
  didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
  DetalheViewController *detailViewController = 
    [[DetalheViewController alloc] initWithNibName:
      @"DetalheViewController" bundle:nil];
     
  detailViewController.texto = 
    [nomes objectAtIndex:indexPath.row];

  [self.navigationController pushViewController:
    detailViewController animated:YES]; 
}
O método acima instancia nosso DetalheViewController passando o arquivo *.xib (sem a extensão), em seguida atribui a propriedade texto e utiliza o navigationController (que criamos no AppDelegate) para exibir a tela. O resultado pode ser visto abaixo:
Podemos notar que o navigation controller já coloca um botão para voltarmos para a tela anterior. Em breve devo colocar mais posts sobre iOS. Qualquer dúvida, deixem seus comentários. 4br4ç05, nglauber

quarta-feira, 30 de março de 2011

Eventos Simples no iOS

Olá povo,

Dando continuidade ao post sobre iPhone, mostrarei nesse como tratar eventos simples na plataforma iOS. Nosso exemplo constará de uma tela com uma caixa de texto e um botão. Ao clicar nesse botão, será exibida uma mensagem com o conteúdo digitado.

Inicie um novo projeto no Xcode, selecione a opção View-based Application e indique o local onde deseja salvar seu projeto juntamente com seu nome (utilizei ExemploBlog2). Um novo projeto será iniciado e começaremos nosso exemplo desenhando a tela da aplicação. Dê um duplo clique sobre o arquivo ExemploBlog2ViewController.xib que está dentro da pasta Resources. O Interface Builder será aberto para que possamos editar o arquivo.
Arraste da janela Library (se ela não estiver sendo exibida pressione Shift-Command-L): um Label, um TextField e um RoundRectButton. Altere a texto do Label e do botão dando um duplo-clique sobre eles. Na janela de propriedades (se não estiver ativa, pressione Command-1) altere a propriedade Text Input Traits > Return Key para Done. Isso servirá para quando terminarmos de digitar o texto desejado, possamos fechar o teclado virtual. Por fim, arraste os componentes de na tela deixando-os similar a figura abaixo.
Salve o arquivo e volte ao Xcode. Vamos agora declarar esses componentes no código da classe que tratará os eventos da UI. Abra o arquivo ExemploBlog2ViewController.h e deixe-o conforme abaixo.

#import <UIKit/UIKit.h>

@interface ExemploBlog2ViewController :
UIViewController {

IBOutlet UITextField* txtNome;
}

- (IBAction) doneDoTeclado;
- (IBAction) cliqueDoBotao;

@end


No arquivo .h é onde declaramos os atributos da classe (que ficam entre as chaves) e as assinaturas de métodos (após a chave de fechamento e a diretiva @end). No código acima, declaramos um UITextField que representará a caixa de texto que definimos no InterfaceBuilder. Notem que antes da declaração, colocamos o texto IBOutlet, que é necessário para que possamos interligar essa variável ao componente definido no Interface Builder.

Logo abaixo declaramos dois métodos, o primeiro servirá para fechar o teclado virtual e o segundo servirá para tratar o clique do botão. A implementação desses métodos fica no arquivo com exetnsão .m. Abra o arquivo ExemploBlog2ViewController.m e deixe-o de acordo com o código abaixo:

#import "ExemploBlog2ViewController.h"

@implementation ExemploBlog2ViewController

- (IBAction) doneDoTeclado {
[txtNome resignFirstResponder];
}

- (IBAction) cliqueDoBotao{
[self doneDoTeclado];
NSString *mensagem = [[NSString alloc]
initWithFormat:@"Você digitou %@", txtNome.text];

UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:@"Informação"
message:mensagem
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
[mensagem release];
}

- (void)dealloc {
[txtNome release];
[super dealloc];
}

@end


O iOS (sistema operacional do iPhone/iPad/iPod Touch) utiliza uma cadeia de "respondedores" de eventos. Ao entrar em uma caixa de texto, o teclado virtual passa a tratar os eventos da caixa de texto. Para reatribuir o controle ao TextField chamamos o método resignFirstResponder.
No método do clique do botão, chamamos o método doneDoTeclado, para que, se o usuário clicar no botão sem clicar no Done do teclado, o teclado virtual desapareça. Em seguida criamos uma string (representada pela classe NSString) para concatenar a mensagem que desejamos com o texto digitado.
Notem que foi utilizado o método alloc seguido pelo método initWithFormat. Diferentemente de Java, no Objective-C temos que alocar e desalocar a memória utilizada pelo objeto. O método alloc chamado a partir da classe faz a alocação e o método release (como usamos mais abaixo) faz a liberação. Objective-C não tem o conceito de construtores, por isso são usados métodos com prefixo init para inicializar o objeto.
A classe que exibirá a mensagem é a UIAlertView, a qual inicializamos um objeto passando o título do diálogo, a mensagem, um objeto que trate o clique do botão do diálogo, o texto do primeiro botão, e por último o texto de mais botões que você desejar.
Como você deve ter notado, chamadas de método são feitas usando a notação diferente das linguagens basedas em C (como Java, C#, C++). Ele utiliza a notação In-Fix que coloca palavras antes de cada parâmetro para explicar para que serve. No código acima é fácil identificar para que serve cada parâmetro para criar o UIAlertView. Criado o diálogo, o exibimos chamando o método show.

Por fim, o método dealloc deve liberar todos os recursos alocados pela tela. No nosso caso, estamos liberando a memória alocada para o TextField.

A parte de código está pronta. Falta agora fazer a ligação entre o arquivo .xib do InterfaceBuilder com esse código. Volte para o InterfaceBuilder e vamos fazer essa ligação. Selecione o TextField e a partir da janela Connections (se não estiver sendo exibida, Command-2) clique em New Referencing Outlet e arraste para o File's Owner conforme a figura abaixo. Na lista que for exibida, selecione txtNome.



Faça agora a ligação arrastando o evento Did End on Exit para o File's Owner e na lista que será exibida selecione doneDoTeclado. Agora selecione o botão e faça agora a ligação arrastando o evento Touch Up Inside para o File's Owner, e na lista que for exibida selecione cliqueDoBotao.

Feito isso, volte para o Xcode e pressione o botão Build and Run. O resultado será semelhante as figuras abaixo.




Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber

terça-feira, 8 de fevereiro de 2011

Hello World iPhone

Olá povo,

A muito tempo que deveria ter escrito esse post. Passei 2010 entre iPhone e Android graças ao meu mestrado, e hoje vou apresentar pra vocês como começar a desenvolver aplicativos para o iOS, o sistema operacional dos dispositivos móveis da Apple (iPhone, iPad e iPod touch).
Para começo de conversa, precisamos de um computador que rode o Mac OS X. Pelas vias legais, você deve ter um Macbook, iMac, Mac Pro ou Mac mini. Entretanto você pode recorrer a um bom e velho Hackintosh ou instalar uma máquina virtual com o sistema operacional da Apple.
O kit de desenvolvimento está disponível no site da Apple (developer.apple.com) e pode ser baixado livremente, bastando apenas fazer o registro gratuitamente. Ele só é "um pouquinho" grande, tem cerca de 3.6GB, então se for baixar via dial-up, esqueça! :) (não que eu acredite que um desenvolvedor ainda acesse internet assim). A instalação é bem simples, basta seguir o assistente. A última versão do SDK exige a versão 10.6.4 ou superior do Mac OS, e uma recomendação é que sua máquina tenha 2GB de memória pra trabalhar bem.

As principais ferramentas do SDK são: Xcode, Interface Builder e os Emuladores do iPhone e iPad.
A primeira, é a IDE, onde criamos e gerenciamos nossos projetos, além de escrever o código fonte, compilar e executar a aplicação. O Interface Builder é um editor visual onde podemos criar as telas da aplicação e fazer a associação de componentes visuais para atributos da classe (criada no Xcode), e de eventos disparados por componentes a métodos declarados no código fonte. Quando o código fonte é compilado, ele pode ser testado no emulador. Na versão atual, temos emuladores para iPhone e iPad, porém funcionalidades como acelerômetro e GPS não estão disponíveis neles. Sendo assim, para testar esse tipo de funcionalidade precisamos de um aparelho real, e para tal, você deve pagar por uma licença de desenvolvedor. Com esse licença, você obtém uma assinatura que deve ser usada em suas aplicações de modo a instalá-las no aparelho. Mas se você já fez o Jailbreak no seu iPhone, podem testar sem licença mesmo :)

Nosso exemplo fará um bom e velho HelloWorld. Para começãr, execute o Xcode e vá até o menu File > New project. Será exibida a tela abaixo:

O Xcode disponibiliza alguns templates que criam a estrutura básica do projeto dependendo de sua funcionalidade. Utilizaremos o template "View based Application", selecione-o e clique em Choose. Em seguida, dê o nome ao seu projeto (eu coloquei ExemploBlog) e clique em ok.
O nosso projeto será exibido no Xcode conforme a imagem abaixo.


No lado direito é exibido o conteúdo de uma pasta na parte superior e o conteúdo do arquivo na parte inferior. No lado esquerdo fica a estrutura do nosso projeto organizado em pastas. As principais são as pastas Classes e Resources. Na primeira ficam os arquivos de código fonte, que assim como em C e C++ fica divido em arquivos de cabeçalho (.h) e de implementação (.m). Já as imagens e os arquivos que definem a interface gráfica (chamados de NIBs mas com extensão .xib) ficam no diretório Resources.

Vamos resumir como um programa iOS começa. Ele inicia com um arquivo chamado main.m (que fica na pasta Other Resources) que lê um arquivo que tem o mesmo nome do projeto e com extensão .plist. Nesse arquivo, existe a definição de qual será o NIB principal da aplicação. Nesse NIB constam: uma referência para a classe que implementa UIApplicationDelegate; uma instância de UIWindow que representa a tela do aparelho; e opcionalmente uma referência para UIViewController que trata os eventos de interface gráfica.

Vejam que nosso projeto tem a classe ExemploBlogAppDelegate. Que tem o seguinte código:

// Arquivo .h
#import <UIKit/UIKit.h>

@class ExemploBlogViewController;

@interface ExemploBlogAppDelegate : NSObject
<UIApplicationDelegate>{

UIWindow *window;
ExemploBlogViewController *viewController;
}

@property (nonatomic, retain) IBOutlet
UIWindow *window;
@property (nonatomic, retain) IBOutlet
ExemploBlogViewController *viewController;

@end

// Arquivo .m
#import "ExemploBlogAppDelegate.h"
#import "ExemploBlogViewController.h"

@implementation ExemploBlogAppDelegate

@synthesize window;
@synthesize viewController;

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:
(NSDictionary *)launchOptions {

[self.window addSubview:viewController.view];
[self.window makeKeyAndVisible];

return YES;
}

- (void)dealloc {
[viewController release];
[window release];
[super dealloc];
}
@end

O arquivo .h acima define: de qual estamos herdando; (opcionalmente) quais interfaces ele implementa; as declarações de atributos; e declarações de métodos. Nesse arquivo estão definidos dois atributos: window e viewController. Em Objective-C temos o conceito de propriedades muito conhecido dos desenvolvedores Delphi e C#. Não vamos entrar em detalhes sobre isso, mas pensem nelas como sendo atalhos para métodos GET e SET.
Ainda no código acima, temos o método application:didFinishLaunchingWithOptions que é chamado quando a aplicação é carregada, e quando isso ocorre, a View (que define uma tela da aplicação) do ViewController (que trata os evento dessa tela) é adicionada a Window, e logo em seguida, torna-se visível. Ao final, o método dealloc, libera a memória alocada pela tela.
Como você pode ver, o ViewController declarado é do tipo ExemploBlogViewController (que está declarada nos arquivos de mesmo nome com extensão .h e .m). TodoViewController tem uma View associada que dispara eventos para ele. No nosso projeto, essa View está definida no arquivo ExemploBlogViewController.xib. Dê um duplo clique nesse arquivo e será aberto o InterfaceBuilder.

O InterfaceBuilder é composto por quatro janelas principais: Main, View, Attributes e Library. A primeira exibe a estrutura do arquivo; a segunda, é o editor visual onde adicionamos os componentes e podemos ter uma idéia de como ficará a tela; na janela attributes podemos alterar as propriedades dos componentes da tela; na última, temos todos os componentes que podemos adicionar à tela.


Adicione um Label da janela Library para a janela Window. Dê um duplo-clique no componente adicionado e digite um novo valor para o texto. Selecione o Label, e na janela Attributes altere a cor do texto (você consegue achar né? :). Depois altere o tamanho do texto. Por fim, salve as alterações e volte para o Xcode.

Clique no Build and Run, e o emulador do iPhone iniciará com nossa aplicação executando! \o/


Que beleza hein!? Um HelloWorld sem digitar nenhuma linha de código! :)
No próximo post vamos falar um pouco sobre Objective-C e veremos como tratar eventos de componentes.
Espero que tenham gostado. Dúvidas e melhorias, deixem seus comentários.

4br4ç05,
nglauber