Mostrando postagens com marcador Blocks. Mostrar todas as postagens
Mostrando postagens com marcador Blocks. 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

segunda-feira, 24 de setembro de 2012

Mapas no iOS

Olá povo,

Esse post é grande, mas é bem legal. Nele, vou falar sobre a utilização de Mapas no iOS. Conheceremos as principais classes do MapKit, e além desse objetivo principal, utlizaremos um conceito interessante do Objective-C, os blocks.
Para começar crie um novo projeto no Xcode (pode ser Single View Application sem utilizar Storyboards). Adicione os frameworks MapKit e CoreLocation à sua aplicação. Em seguida, crie uma nova classe chamada Endereço e deixe-a conforme abaixo:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>

@interface Endereco : NSObject<MKAnnotation>

@property (strong, nonatomic) NSString *titulo;
@property (strong, nonatomic) NSString *subtitulo;

- (id)initWithCoordinate:(CLLocationCoordinate2D) c;
@end
#import "Endereco.h"

@implementation Endereco

@synthesize titulo, subtitulo;

@synthesize coordinate;

- (id)initWithCoordinate:(CLLocationCoordinate2D) c {
    coordinate = c;
    NSLog(@"%f,%f", c.latitude, c.longitude);
    return self;
}

- (NSString *)subtitle {
    return subtitulo;
}

- (NSString *) title {
    return titulo;
}
@end
A classe acima está implementando o protocolo MKAnnotation que exige que exista uma propriedade coordinate e os métodos subtitle e title. Isso porque, usaremos essa classe para representar um ponto no Mapa.
Agora deixe o ViewController da sua aplicação conforme abaixo (o nome da classe obviamente pode ser alterado...).
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#import "Endereco.h"

@interface NGViewController : UIViewController 
  <UISearchBarDelegate, MKMapViewDelegate>

@property (weak, nonatomic) IBOutlet 
  UISearchBar *searchBar;
@property (weak, nonatomic) IBOutlet 
  MKMapView *mapa;
@property (weak, nonatomic) IBOutlet 
  UISegmentedControl *segMapType;
@property (strong, nonatomic) 
  Endereco *endereco;

- (IBAction)mapViewChanged:(id)sender;

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

@implementation NGViewController

@synthesize searchBar;
@synthesize mapa;
@synthesize segMapType;
@synthesize endereco;

#define API_KEY @"SUA_CHAVE"
#define TEMPLATE_URL 
  @"http://maps.google.com/maps/geo?q=%@&output=csv&key=%@"

#pragma mark - View lifecycle
- (void)viewDidUnload {
    [self setSearchBar:nil];
    [self setMapa:nil];
    [self setSegMapType:nil];
    [super viewDidUnload];
}

#pragma mark - SegmentedControl value changed
- (IBAction)mapViewChanged:(id)sender {
  // De acordo com a posição selecionada no 
  // SegmentedControl, mudamos o tipo de 
  // visualização do mapa
  switch ([segMapType selectedSegmentIndex]) {
    case 0: mapa.mapType = MKMapTypeStandard;  break;
    case 1: mapa.mapType = MKMapTypeSatellite; break;
    case 2: mapa.mapType = MKMapTypeHybrid;    break;
  }
}

#pragma mark - SearchBar methods
-(void)searchBarCancelButtonClicked:(UISearchBar *)searchBar {
  // Ao clicar em cancelar na barra de busca, 
  // ocultamos o teclado
  [self.searchBar resignFirstResponder];
}

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar {
  [self.searchBar resignFirstResponder];    
    
  [self retrieveLocation:self.searchBar.text];
}

#pragma mark - Utility Methods

- (void) retrieveLocation:(NSString *)search {
  // Criando URL de requisição
  NSString *query = 
    [search stringByAddingPercentEscapesUsingEncoding:
      NSUTF8StringEncoding]; 
   
  NSString *urlString = [NSString stringWithFormat:
    TEMPLATE_URL, query, API_KEY];
    
  // Preparando request
  NSURLRequest *request = [[NSURLRequest alloc]
    initWithURL:[NSURL URLWithString:urlString]];
    
  // Declarando o Block "trataResposta" para tratar 
  // retorno. Esse block é uma função que retorna 
  // void e recebe 3 parâmetros: uma response, os 
  // dados e um erro. 
  id trataResposta = ^(NSURLResponse *response, 
    NSData *data, NSError *error) {
      if (error){
        [self showErrorDialog];
            
      } else {
        [self trataResposta:data];
      }
    };
    
  // Fazendo a requisição assícrona para evitar que 
  // a tela pareça estar travada. Aqui usamos o 
  // recurso de blocks, declarado acima e que será 
  // chamado após a requisição ser concluída
  [NSURLConnection sendAsynchronousRequest:request 
    queue:[NSOperationQueue mainQueue] 
      completionHandler:trataResposta];
}

- (void) trataResposta:(NSData *)data {
  NSString *locationString = [[NSString alloc]
    initWithData:data encoding:NSUTF8StringEncoding];

  // Nesse array, o primeiro elemento é o código de 
  // resposta HTTP (200 é sucesso) os próximos (se 
  // deu tudo certo) é a latitude e longitude.
  NSArray *listItems = [locationString 
    componentsSeparatedByString:@","];
    
  double longitude = 0;
  double latitude = 0;
    
  if ([listItems count] > 0 && 
     [[listItems objectAtIndex:0] 
       isEqualToString:@"200"]){

    latitude = [[listItems objectAtIndex:2] 
      doubleValue];
    longitude = [[listItems objectAtIndex:3] 
      doubleValue];
  }
    
  // Cria o objeto location fazer obter informações 
  // do local via GeoCoder
  CLLocation *location = [[CLLocation alloc] 
    initWithLatitude:latitude longitude:longitude];
  [self retrieveGeocoder:location];
}

- (void) retrieveGeocoder:(CLLocation *)location {
  CLGeocoder *geocoder = [[CLGeocoder alloc] init];
    
  // Mais uma chamada assícrona que recebe 
  // um block como parâmetro
  [geocoder reverseGeocodeLocation:location 
    completionHandler:
      ^(NSArray *placemarks, NSError *error) {
         
    if ([placemarks count] > 0){
             
      CLPlacemark *placemark = 
        [placemarks objectAtIndex:0];
             
      [self addAnnotation:location.coordinate 
         withMark:placemark];
             
    } else {
      [self showErrorDialog];
    }
  }];
}

- (void)showErrorDialog {
  UIAlertView *alert = [[UIAlertView alloc] 
    initWithTitle:@"Erro" 
    message:@"Erro ao obter dados da localização" 
    delegate:nil cancelButtonTitle:@"OK" 
    otherButtonTitles:nil, nil];
  [alert show];
}

- (void)addAnnotation:
  (CLLocationCoordinate2D)coordinate 
   withMark:(CLPlacemark *)placemark {

  // Se já houver uma marcação no mapa, remova
  if (self.endereco != nil) {
    [mapa removeAnnotation:self.endereco];
    self.endereco = nil;
  }
    
  // Criando MKNotation para adicionar no Mapa
  self.endereco = [[Endereco alloc]
  initWithCoordinate:coordinate];
    
  // Configura o título e subtítulo da MKNotation 
  // baseado no CLPlacemark
  self.endereco.titulo = [NSString stringWithFormat:
    @"%@, %@\n%@", 
      placemark.locality, 
      placemark.administrativeArea, 
      placemark.postalCode];
    
  self.endereco.subtitulo = 
    [NSString stringWithFormat:@"%@, %f,%f", 
      placemark.thoroughfare,
      coordinate.latitude, 
      coordinate.longitude];
    
  // Adiciona a notação ao mapa
  [self.mapa addAnnotation:self.endereco];

  /* A MKCoordinateSpan representa a quantidade 
     de área usada pela view e é usada para 
     determinar o zoom. Quanto maior o valor, 
     maior a área visível. */
  MKCoordinateSpan span;
  span.latitudeDelta = 0.02;
  span.longitudeDelta = 0.02;
    
  // A MKCoordinateRegion serve para movimentar o 
  // mapa até a região determinada no MKNotation
  MKCoordinateRegion region;
  region.center = coordinate;
  region.span = span;
    
  [self.mapa setRegion:region animated:YES];
  [self.mapa regionThatFits:region]; 
}

#pragma mark - MapView
-(MKAnnotationView *)mapView:(MKMapView *)mapView 
  viewForAnnotation:(id<MKAnnotation>)annotation {
  
  /* Ao adicionar uma notação no mapa, esse método 
     é chamado. É aqui que criamos o pin que aparece
     no mapa */
  MKPinAnnotationView *pin = 
    [[MKPinAnnotationView alloc] initWithAnnotation:
      annotation reuseIdentifier:@"currentLoc"];
    
  pin.pinColor = MKPinAnnotationColorGreen;
  pin.animatesDrop = TRUE;
  pin.canShowCallout = YES;
  pin.calloutOffset = CGPointMake(-5, 5);
    
  return pin;
}

@end
O código está comentado, qualquer dúvida, deixe seus comentários. Agora abra o XIB referente a essa tela e adicione uma SearchBar, um MapView e um SegmentedControl de modo que ele fique conforme a figura abaixo:
No SearchBar, ligue-o com a propriedade searchBar e sete o delegate. Para o MapView, faça o mesmo com a propriedade mapa. E por fim, com o SegmentedControl ligue-o com a propriedade segMapType,  e no evento Value Changed ligue com o método mapViewChanged.

Agora é só rodar e pesquisar pelos lugares desejados.
Qualquer dúvida, deixem seus comentários.

4br4ç05,
nglauber