Desenvolvimento

6 mai, 2015

UITableViewControllers com máquina de estados e agregador de Delegates/Datasources

Publicidade

Quem já fez interfaces muito dinâmicas baseadas em table views sabe que o código do controller tende a ficar grande, bagunçado e feio. Depois do lançamento do Storyboard, ficou muito mais simples fazer tabelas estáticas para formulários. Também ficou simples a criação de células dinâmicas sem ter que fazer o registro de cada Nib/ReuseID na mão. Mas, ainda assim, como fazer quando temos um número diferente de seções e/ou número variável de células por seção para cada estado da table view?

Como exemplo, vamos examinar a sequência abaixo.

passos

Temos uma tela feita como table view. Essa tabela tem 3 seções: Passo 1, Passo 2 e Passo 3. Para todos os passos, temos 3 seções. No estado passo 1, a primeira seção tem duas células. Seria o título onde está o texto passo 1 e o conteúdo em si. As seções 2 e 3 têm apenas uma célula, que é a de título. No estado passo 2, temos a seção 1 e 2 com duas células cada e a seção 3 com apenas uma. Finalmente, no estado passo 3, todas as seções têm 2 células.

Pausa para uma reflexão. Claramente a célula de título é um cabeçalho e, dessa forma, eu poderia dizer que a seção 1 tem 1 célula, e as seções 2 e 3 têm 0 células. Mas, se eu definir a célula de título como cabeçalho da seção, quando o usuário rolar a tabela, a célula vai passar por trás do cabeçalho, e para esse tipo de tela queremos efeito de cabeçalho fixo. Então, fazemos como outra célula na seção, e não como cabeçalho.

Imagine que no passo 1 o usuário entra com dados de uma operação específica que deseja realizar. Teríamos, por exemplo, um campo texto para digitação de um código de barras e abaixo outro campo de texto para mensagens de validação desse código que confirme o que o usuário digita. Ao fim, o usuário pressiona o botão OK, consultamos uma API e, em caso de sucesso, vamos para o passo 2.

No passo 2, temos na seção 1 o código de barras digitado e, na seção 2, temos os dados desse boleto, como valores, cedente etc. O usuário pode tocar no código de barras, o que causaria uma volta ao passo 1, ou pressionar o botão OK para confirmar o pagamento. Nesse último caso, iria ao passo 3, que seria a exibição do comprovante de pagamento. No estado 3, temos o código de barras pago no passo 1, os valores, cedente etc. no passo 2 e os dados referentes ao comprovante no passo 3.

A animação abaixo mostra como fica a interação do usuário com essa tela e a troca de estados.

animacao

Então, como implementar isso em código, de forma que o controller fique o mais limpo possível?

Primeiro, vamos pensar na máquina de estado. Para implementar isso em código de forma limpa, definimos um enum e uma property da seguinte forma:

codigo_enum

Feito isso, a primeira tentativa seria uma implementação na qual o controller é o delegate e o datasource dessa tabela, e dentro de cada método do delegate/datasource seria feito um if testando qual o estado atual da tabela e, dependendo do estado, executando uma ação diferente ou retornando um valor diferente. Mesmo que esse delegate e datasource fossem criados em uma classe separada, ainda assim teríamos o if em cada método e o código ficaria muito ruim de entender e manter.

A solução que eu tenho usado é baseada em um recurso chamado Datasource Aggregate. Esse conceito é apresentado com exemplos neste vídeo da WWDC 2014: Advanced User Interfaces with Collection Views.

O agregador de datasources, que também pode ser usado para delegates, é nesse caso uma subclasse de UITableViewDataSource que implementa os métodos desejados do datasource e instancia ou referencia para outras subclasses de UITableViewDataSource. Cada uma dessas instâncias é um datasource para um estado e, baseado no estado, o agregador apenas redireciona a chamada do método para o mesmo método no datasource referente àquele estado.

Exemplificando:

Agregador : AgregadorTableViewDataSource

Passo1 : Passo01TableViewDataSource

Passo2 : Passo02TableViewDataSource

Passo3 : Passo03TableViewDataSource

O código de exemplo completo do agregador pode ser visto neste gist.

Dúvidas? Sugestões? Aproveite o campo abaixo! Até a próxima.