Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. iOS

iOS do princípio com Swift: Básico de Table Views

by
Difficulty:BeginnerLength:LongLanguages:
This post is part of a series called iOS From Scratch With Swift.
iOS From Scratch With Swift: Auto Layout Basics
iOS From Scratch With Swift: Navigation Controllers and View Controller Hierarchies

Portuguese (Português) translation by David Batista (you can also view the original English article)

Table views estão entre os componentes mais usados do framework UIKit e são uma parte integral da experiência do usuário na plataforma iOS. Table views fazem uma coisa e fazem muito bem, apresentar uma lista ordenada de itens. A classe UITAbleView é um bom lugar para continuar nossa jornada através do framework UIKit, por ela combinar muitas conceitos-chave do Cocoa Touch e UIKit, incluindo views, protocolos e reusabilidade.

Data Source e Delegate

A classe UITableView, um dos componentes-chave do framework UIKit, é altamente otimizada para mostrar uma lista ordenada de itens. As Table views podem ser customizadas e adaptadas para uma vasta gama de casos, mas a ideia básica permanece a mesma, apresentar uma lista ordenada de itens.

A Plain Table View

A classe UITableView é responsável apenas por apresentar dados como uma lista de linhas (de tabela). Os dados exibidos são gerenciados pelo objeto data source das table views, acessível através da propriedade dataSource da table view. O data source pode ser qualquer objeto em conformidade com o protocolo UITableViewDataSource, um protocolo Objective-C. Como veremos mais tarde neste artigo, o data source da table view é frequentemente a view controller que gerencia a view, da qual a table view é uma subview.

Semelhantemente, a table view é responsável apenas por detectar os toques na table view. Ela não é responsável por responder aos toques. A table view também tem uma propriedade delegate. Sempre que a table view detecta um evento de toque, ela notifica a seu delegado sobre este evento. É responsabilidade do delegado da table view responder ao evento.

Por ter um data source administrando seus dados e um objeto delegado manipulando a interação do usuário, a table view pode focar na apresentação dos dados. O resultado é uma alta reusabilidade e performance do componente UIKit que esta em perfeito alinhamento com o padrão MVC (Model-View-Controller), que discutimos anteriormente nesta série. A classe UITableView herda da classe UIView, o que significa que ela é responsável apenas por mostrar os dados do aplicativo.

O objeto data source é similar, mas não idêntico, ao objeto delegado. Um objeto de delegado é encarregado de controlar a interface do usuário pelo objeto delegante. Um objeto data source, entretanto, é encarregado de controlar os dados.

A table view pergunta ao objeto data source pela informação que ela deve exibir. Isso implica que o objeto data source também é responsável por manipular a informação que ele oferece à table view.

Componentes da Table View

A classe UITableView herda da UIScrollView, uma subclasse UIView que fornece suporte para exibir conteúdo maior que a janela do aplicativo.

Uma instância UITableView é composta de linhas com cada linha contendo uma célula, uma instância da UITableViewCell ou uma subclasse dela. Em contraste com o equivalente do UITableView do OS X, NSTableView, instâncias de UITableView são de apenas uma coluna. Conjuntos de dados encadeados ou hierarquias podem ser exibidos usando uma combinação de table views e um navigation controller (UINavigationController). Falaremos sobre navigation controllers no próximo artigo desta série.

Eu já mencionei que table views são encarregadas apenas de exibir dados, entregues pelo objeto data source, e detectar eventos de toques, os quais são direcionados ao objeto delegado. Uma table view nada mais é do que uma view gerenciando uma quantidade de subviews, as table view cells.

Um novo projeto

Ao invés de sobrecarrega-lo com teoria, é melhor (e mais divertido) criar um novo projeto no Xcode e mostrar como configurar uma table view, popula-la com dados e receber resposta de eventos de toque.

Abra o Xcode, crie um novo projeto (File > New > Project...) e selecione o template Single View Application.

A New Project

Chame o projeto de Table View, defina um organization name e identifier e atribua iPhone para Devices. Informe ao Xcode onde você quer salvar o projeto e clique em Create.

Project Configuration

O novo projeto será familiar, porque escolhemos o mesmo template de projeto de anteriormente. O Xcode já criou uma classe application delegate para nós, AppDelegate, e ele também nos deu uma classe view controller inicial, ViewController.

Adicionando uma Table View

Compile e execute o projeto para ver o que temos de inicio. A tela branca que você vê quando executa o aplicativo no simulador é a view da view controller que o Xcode instância para nós no storyboard.

A forma mais fácil de adicionar uma table view na view da view controller está no storyboard principal do projeto. Abra o Main.storyboard e localize o Object Library na direita. Navegue no Object Library e arraste uma instância da UITableView para a view da view controller.

Adding a Table View

Se a dimensão da table view não se ajustar automaticamente para o tamanho da view da view controller, então ajuste manualmente arrastando os quadrados brancos da borda da table view. Lembre-se que os quadrados brancos são visíveis apenas quando a table view for selecionada.

Adjusting the Dimensions of the Table View

Adicione as restrições de layout necessárias para certifica-se que a table view irá se expandir ao tamanho e altura da sua view pai. O que será fácil se você leu o artigo anterior sobre Auto Layout.

Adding Layout Constraints

Isto é tudo o que precisamos fazer para adicionar uma table view a view da nossa view controller. Compile e execute o projeto para ver o resultado no simulador. Você ainda verá uma tela em branco já que a table view não tem nenhum dado para exibir.

Uma table view tem dois estilos padrão, simples (plain) e agrupada (grouped). Para alterar o estilo atual da table view (Plain), selecione a table view no storyboard, abra a Attributes Inspector e mude o atributo estilo para Grouped. Para este projeto, iremos trabalhar com uma table view simples (plain), então tenha certeza de voltar o estilo da table view para simples (plain).

A Grouped Table View

Conectando o Data Source e o Delegate

Você já sabe que uma table view deve ter um data source e um delegado. Até o momento, a table view não tem um data source ou um delegate. Precisamos conectar os outlets dataSource e delegate da table view para um objeto em conformidade com os protocolos UITableViewDataSource e UITableViewDelegate.

Na maioria dos casos, este objeto é a view controller que gerencia a view da qual a table view e uma subview. Selecione a table view no storyboard, abra a Connections Inspector na direita e arraste do outlet dataSource (o circulo vazio em sua direita) para a View Controller. Faça o mesmo da outlet delegate. Nossa view controller agora esta ligada para atuar como o data source e o delegado da table view.

Connecting Data Source and Delegate

Se você executar o aplicativo, ele irá dar erro instantaneamente. A razão para isso ficará clara em instantes. Antes de entender melhor o protocolo UITableViewDataSource, precisamos atualizar a classe ViewController.

Os objetos data source e delegado da table view precisam estar em conformidade com os protocolos UITableViewDataSource UITableViewDelegate. Como vimos anteriormente nesta série, protocolos são listados após a superclasse da classe. Múltiplos protocolos são separados por vírgula. 

Criando o Data Source

Antes de começarmos a implementação dos métodos do protocolo data source, precisamos de alguns dados para exibir na table view. Armazenaremos os dados em um array, então primeiro adicionaremos uma novo propriedade à classe ViewController. Abra o arquivo ViewController.swift e adicione uma propriedade, fruits, do tipo [String].

No método viewDidLoad() da view controller, popularemos a propriedade fruits com uma lista frutas, a qual vamos mostrar na table view um pouco mais tarde. O método viewDidLoad() é chamado automaticamente depois que a view e as subviews da view controller forem carregadas na memória, por isso o nome do método. Portanto, este é um bom lugar para popular o array fruits.

A classe UIViewController, a superclasse da classe ViewController, também define um método viewDidLoad(). A classe ViewController faz o override do método viewDidLoad() definido pela classe UIViewController. Isso é indicado pela palavra-chave override.

Efetuar o overriding de um método de um superclasse nunca é feito sem riscos. E se a classe UIViewController fizer algo importante no método viewDidLoad()? Como podemos nos certificar que não causamos nenhum erro quando efetuamos o override do método viewDidLoad()?

Em situações como esta, é importante chamar primeiro o método viewDidLoad() da superclasse antes de efetuar qualquer outra coisa no método viewDidLoad(). A palavra-chave super refere-se à superclasse e enviamos a ela a mensagem do viewDidLoad(), chamando o método viewDidLoad() da superclasse. Isto é um conceito importante, então certifique-se que você o entendeu antes de seguir em frente.

Protocolo Data Source

Como atribuímos a view controller como o objeto data source da table view, a table view pergunta à view controller o que deve ser exibido. A primeira informação que a table view precisa do seu data source é o número de seções que ela exibirá.

A table view faz isso chamando o método numberOfSectionsInTableView(_:) do seu data source. Esse é um método opcional do protocolo UITableViewDataSource. Se o data source da table view não implementar este método, a table view assume que precisa exibir apenas uma seção. Implementamos este método de qualquer forma já que precisaremos dele mais tarde neste artigo.

Você deve estar se perguntando "O que é uma seção da table view?" Uma seção da table view é um grupo de linhas. O aplicativo de Contatos do iOS, por exemplo, agrupa contatos com base na primeira letra do primeiro ou ultimo nome. Cada grupo de contatos forma uma seção, que é composta por um section header na parte superior da seção e/ou um section footer na parte inferior da seção.

O método numberOfSectionsInTableView(_:) recebe um argumento, tableView, que é a table view que envia a mensagem para o objeto data source. Isso é importante, porque isso permite ao objeto data source ser o data source de múltiplas table views se necessário. Como você pode ver, a implementação do numberOfSectionInTableView(_:) é muito simples.

Agora que a table view sabe quantas seções precisa exibir, ela pede ao seu data source quantas linhas há em cada seção. Para cada seção na table view, a table view chama o método tableView(_:numberOfRowsInSection:) do seu data source. Este método recebe dois argumentos, a table view que está chamando o método e o índice da seção da qual a table view quer saber o número de linhas.

A implementação deste método é muito simples, como você pode ver abaixo. Começamos declarando uma constante, numberOfRows, e atribuímos a ela o número de itens no array fruits, chamando o método count do array. Retornamos o numberOfRows no final do método.

A implementação deste método é tão simples que poderiamos torna-lo um pouco mais conciso. Dê uma olhada na implementação abaixo e certifique-se que você entende o que foi alterado.

Se você tentar compilar o projeto neste estado atual, o compilador irá mostrar um erro. O erro nos diz que a classe ViewController não está em conformidade com o protocolo UITableViewDataSource, porque ainda temos que implementar os métodos obrigatórios do protocolo. A table view espera que o data source, a instância ViewController, retorne uma table view cell para cada linha na table view.

Precisamos implementar o tableView(_:cellForRowAtIndexPath:), outro método do protocolo UITableViewDataSource. O nome do método é muito descritivo. Chamando este método do data source, a table view pede a seu data source pela table view cell da linha específica pelo indexPath, o segundo argumento do método.

Antes de continuar, eu gostaria de falar por um minuto sobre a classe NSIndexPath. Como a documentação explica, "A classe NSIndexPath representa o caminho para um nó específico em uma árvore de uma coleção de array encadeado." Uma instância desta classe pode manter um ou mais índices. No caso de uma table view, ele mantém um índice para a seção que o item está e a linha deste item na seção.

Uma table view nunca tem mais de dois leveis de profundidade, o primeiro level sendo a seção e o segundo level sendo a linha na seção. Mesmo o NSIndexPath sendo uma classe Foundation, o framework UIKit adicionou alguns métodos extras à classe, que tornam simples o trabalho com table views. Vamos examinar a implementação do método tableView(_:cellForRowAtIndexPath:).

Reusando as Table View Cells

Anteriormente nesta série, eu falei que as views são componentes importantes do um aplicativo iOS. Mas você também deve saber que as views são pesadas em termos do memória e poder de processamento que consomem. Quando trabalhamos com table views, é importante fazer o reuso de table view cells sempre que possível. Reutilizando as table view cells, a table view não tem que inicializar uma nova table view cell do zero toda vez que uma nova linha precisar de uma table view cell.

As table view cells que saem do campo de visão da tela não são descartadas. As table view cells podem ser marcadas para reuso especificando um identificador de reuso durante a inicialização. Quando uma table view cell marcada para reuso sai do campo de visão da tela, a table view a coloca na fila de reuso para usa-la mais tarde.

Quando o data source pede a sua table view por uma nova table view cell e específica um identificador de reuso, a table view primeiro verifica na fila de reuso se há uma table view cell com identificador de reuso especificado disponível. Se não houver table view cell disponível, a table view instanciará uma nova e a passará para seu data source. Isso é o que está acontecendo na primeira linha de código.

O data source da table view pede à table view por uma table view cell chamando o método dequeueReusableCellWithIdentifier(_:forIndexPath:). Este método recebe o identificador de reuso que eu mencionei anteriormente tanto como o índice de caminho da table view cell.

O compilador te informará que o cellIdentifier é um "identificador não resolvido". Isso significa que estamos usando uma variável ou constante que ainda não declaramos. Acima da declaração da propriedade fruits, adicione a seguinte declaração para o cellIdentifier.

Como a table view sabe como criar uma nova table view cell? Em outras palavras, como a table view sabe que classe usar para instanciar uma nova table view cell? A resposta é simples. No storyboard, criamos um célula protótipo e demos a ela um identificador de reuso. Vamos fazer isso agora.

Criando uma célula protótipo

Abra o Main.storyboard, selecione a table view que você adicionou anteriormente e abra a Attribute Inspector na direita. O campo Prototype Cell esta atualmente definido com 0. Crie uma célula protótipo atribuindo 1 para ele. Você deve ver agora uma célula protótipo na table view.

Creating a Prototype Cell

Selecione a célula protótipo e dê uma olhada na Attributes Inspector na direita. O Style está atualmente definido como Custom. Mude ele para Basic. Uma table view cell básica contém uma label. O que é suficiente para o aplicativo que estamos construindo. Antes de voltarmos para a classe ViewController, atribua CellIdentifier para o Identifier. O valor deve ser idêntico ao valor atribuído à constante cellIdentifier que declaramos a pouco tempo atrás.

Configuring the Prototype Cell

Configurando a Table View Cell

O próximo passa envolve a população da table view cell com o dado armazenado no array fruits. Isso significa que precisamos saber qual elemento usar do array fruits. Que por sua vez, significa que precisamos saber a linha ou o índice da table view cell.

O argumento indexPath do método tableView(_:cellForRowAtIndexPath:) contém esta informação. Como eu mencionei anteriormente, ele tem alguns métodos extras para tornar o trabalho com table views mais simples. Um destes métodos é row, que retorna a linha para a table view cell. Acessamos a fruta correta pedido ao array fruits pelo item no indexPath.row, usando a conveniente sintaxe subscritava do Swift.

Finalmente, atribuímos ao texto da propriedade textLabel da table view cell o nome da fruta que acessamos do array fruits. A classe UITableViewCell é uma subclasse da UIView e ela tem um número de subviews. Uma dessas subviews é uma instância da UILabel e usamos esta label para exibir o nome da fruta na table view cell. A propriedade textLabel pode ser nil dependendo do estilo da UITableViewCell. Por isso a propriedade textLabel é acompanhada por um ponto de interrogação. Isso é mais conhecido como optional chaining.

O método tableView(_:cellForRowAtIndexPath:) espera nos retornar uma instância da classe UITableViewCell (ou uma subclasse dela) e é o que estamos fazendo no final do método.

Execute o aplicativo. Você deve ter agora uma table view totalmente funcional populada com o array de nomes de fruta armazenada na propriedade fruits da view controller.

Seções

Antes de olharmos o protocolo UITableViewDelegate, eu quero modificar a implementação atual do protocolo UITableViewDataSource adicionando seções à table view. Se a lista de frutas começar a crescer com o tempo, seria melhor e mais amigável classificar as frutas por ordem alfabética e agrupando-as em seções baseadas na primeira letra de cada fruta.

Se queremos adicionar seções à table view, o array atual de frutas não é suficiente. Em vez disso, os dados precisam ser dividido em seções com os frutos em cada seção em ordem alfabética. O que precisamos é de um dicionario. Comece declarando uma nova propriedade, alphabetizedFruits, do tipo [String: [String]] na classe ViewController.

No viewDidLoad(), usamos o array fruits para criar um dicionário de frutas. O dicionário deve conter um array de frutas para cada letra do alfabeto. Se não tiver frutas com uma letra em particular, omitiremos a letra do dicionário.

O dicionário é criado com a ajuda de um método auxiliar, alphabetizeArray(_:). Ele recebe o array fruits como argumento. O método alphabetizeArray(_:) pode ser um pouco complexo, mas sua implementação é muito simples.

Criamos um dicionário mutável, result do tipo [String: [String]], o qual usamos para armazenar os arrays das frutas alfabetizadas, um array para cada letra do alfabeto. Percorremos então os itens no array, o primeiro argumento do método, e extraímos a primeira letra do nome do item, transformando ele em maiúscula. Se o result já possuir um array para a letra, adicionamos o item ao array. Se não tiver, criamos um array com o item e adicionamos ele ao result.

Os itens agora estão agrupados pela sua primeira letra. Entretanto, os grupos não estão em ordem alfabética. É isso que está acontecendo no segundo laço for. Percorremos o array result e classificamos cada array em ordem alfabética.

Não se preocupe se a implementação do alphabetizeArray(_:) não estiver clara. Neste tutorial, focamos na table view, não na criação de uma lista em ordem alfabética de frutas.

Números de Seções

Com o novo data source pronto, a primeira coisa que precisamos fazer é atualizar a implementação do numberOfSectionsInTableView(_:). Na implementação atualizada, pedimos ao dicionário, alphabetizedFruits, por suas chaves. Ele nós entrega um array que contém todas as chaves do dicionário. O número de itens no keys é igual ao número de seções na table view. Simples assim.

Também precisamos atualizar o tableView(_:numberOfRowsInSection:). Como fizemos no numberOfSectionsInTableView(_:), pedimos ao alphabetizedFruits por suas chaves e ordenamos o resultado. Ordenar o array de chaves é importante, porque o par chave-valor do dicionário está desordenado. Está é a principal diferença entre arrays e dicionários. Isso é algo que muitas vezes confunde os novos programadores.

Buscamos então a chave no sortedKeys que corresponde à section, o segundo parâmetro do tableView(_:numberOfRowsInSection:). Usamos a chave para acessar o array de frutas pela seção atual, usando optional binding. Finalmente, retornamos o número de itens no array resultante de frutas.

As alterações que precisamos fazer ao tableView(_:cellForRowIndexPath:) são parecidas. Alteramos apenas a forma como buscamos o nome da fruta que a table view cell exibe em sua label.

Se você executar o aplicativo, você não verá titulo em nenhuma seção como você vê no aplicativo Contatos. Isso por que precisamos dizer à table view o que será exibido no titulo de cada seção.

A escolha mais obvia é exibir o nome de cada seção, isso é, uma letra do alfabeto. A forma mais fácil de fazer isso é implementando o método tableView(_:tileForHeaderInSection:), outro método definido no protocolo UITableViewDataSource. Dê uma olhada na implementação abaixo. É parecida com a implementação do tableView(_:numberOfRowsInSection:). Execute o aplicativo e veja o que a table view exibe nas seções.

Delegação

Em adição ao protocolo UITableViewDataSource, o framework UIKit também define o protocolo UITableViewDelegate, o protocolo que o delegado da table view precisa estar em conformidade.

No storyboard, já atribuímos a view controller como o delegado da table view. O aplicativo funciona normalmente mesmo que não tenhamos implementado nenhum dos métodos definidos no delegado do protocolo UITableViewDelefate. Isso por que todos os métodos do protocolo UITableViewDelegate são opcionais.

Entretanto, seria bom ser capaz de responder aos eventos de toque. Sempre que o usuário tocar uma linha, devemos ser capazes de imprimir o nome da fruta correspondente no console do Xcode. Mesmo isso não sendo muito útil, isso lhe mostrará como o padrão delegate funciona.

A implementação desta função é simples. Tudo que precisamos fazer é implementar o método tableView(_:didSelectRowAtIndexPath:) do protocolo UITableViewDelegate.

Já estamos familiarizados a buscar o nome da fruta que corresponde à linha selecionada. A unica diferença é que imprimimos o nome da fruta no console do Xcode.

Você pode se surpreender que usamos o dicionário alphabetizedFruits para procurar a fruta correspondente. Por que não pedimos à table view ou a table view cell pelo nome da fruta? Esta é uma ótima pergunta. Deixe-me explicar o que acontece.

Uma table view cell é uma view e seu único proposito é exibir informações para o usuário. Ela não sabe o que está exibindo, apenas como exibir. A table view em si não tem a responsabilidade de saber sobre a origem de seus dados, ela sabe apenas como exibir as seções e linhas que ela contém e gerência.

Este exemplo é outra boa ilustração da separação de preocupações do padrão Model-View-Controller (MVC) que falamos anteriormente nesta série. As views não sabem nada sobre os dados da aplicação além de como exibi-los. Se você quer escrever aplicativos iOS confiáveis e robustos, é muito importante saber sobre e respeitar esta separação de responsabilidades.

Conclusão

As table views não são tão complicadas uma vez que você entenda como se comportam e sobre os componentes envolvidos, tais como o data source e os objetos delegados que a table view se comunica.

Neste tutorial, vimos apenas uma pequena parte do que uma table view é capaz. No restante desta série, iremos revisitar a classe UITableView e explorar mais algumas peças do quebra cabeça. No próximo capitulo desta série, daremos uma olhada na navigation controller.

Se você tiver alguma pergunta ou comentário, você pode deixa-los nos comentários abaixo ou me procurar no Twitter.

Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!

Advertisement
Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.