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

Um comentário:

Raul disse...

Muito bom o post, Glauber!
Me ajudou muito num projeto que estou fazendo!

É difícil achar material bom, atualizado e em português sobre o assunto!

A única correção que precisa ser feita para a versão mais nova do XCode é nessa parte:

"Selecione UIViewController subclass e clique em Next (...)"

Já que agora o caminho é selecionando Objective C - class e depois marcando a subclass. ^^


Abraço e continue fazendo posts sobre o IOs! =D