Como evitar o UITableViewController grande e desajeitado no iOS?

35

Eu tenho um problema ao implementar o padrão MVC no iOS. Eu pesquisei na Internet, mas parece não encontrar uma boa solução para esse problema.

Muitas implementações UITableViewController parecem ser bastante grandes. A maioria dos exemplos que vi permite que o UITableViewController implemente <UITableViewDelegate> e <UITableViewDataSource> . Essas implementações são uma grande razão pela qual UITableViewController está ficando grande. Uma solução seria criar classes separadas que implementam <UITableViewDelegate> e <UITableViewDataSource> . É claro que essas classes teriam que ter uma referência ao UITableViewController . Existe alguma desvantagem ao usar essa solução? Em geral, acho que você deve delegar a funcionalidade a outras classes "Auxiliares" ou semelhantes, usando o padrão delegado. Existem formas bem estabelecidas de resolver este problema?

Eu não quero que o modelo contenha muita funcionalidade, nem a visão. Eu acredito que a lógica deveria estar na classe do controlador, já que esta é uma das pedras angulares do padrão MVC. Mas a grande questão é:

Como você deve dividir o controlador de uma implementação MVC em partes gerenciáveis menores? (Aplica-se ao MVC no iOS neste caso)

Pode haver um padrão geral para resolver isso, embora eu esteja procurando especificamente uma solução para iOS. Por favor, dê um exemplo de um bom padrão para resolver este problema. Por favor, forneça um argumento por que sua solução é incrível.

    
por Johan Karlsson 29.11.2012 / 14:20
fonte

5 respostas

42

Evito usar UITableViewController , pois coloca muitas responsabilidades em um único objeto. Portanto, separo a subclasse UIViewController da fonte de dados e delegado. A responsabilidade do controlador de visualização é preparar a visualização de tabela, criar uma fonte de dados com dados e ligar essas coisas. Alterar a maneira como a tableview é representada pode ser feito sem alterar o controlador de visualização e, de fato, o mesmo controlador de visualização pode ser usado para várias fontes de dados que seguem esse padrão. Da mesma forma, alterar o fluxo de trabalho do aplicativo significa alterações no controlador de exibição sem se preocupar com o que acontece com a tabela.

Eu tentei separar os protocolos UITableViewDataSource e UITableViewDelegate em objetos diferentes, mas isso geralmente acaba sendo uma divisão falsa, já que quase todo método no delegado precisa cavar na fonte de dados (por exemplo, na seleção, o delegado precisa saber qual objeto é representado pela linha selecionada). Então, acabo com um único objeto que é a fonte de dados e o delegado. Esse objeto sempre fornece um método -(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath , no qual tanto a origem de dados quanto os aspectos delegados precisam saber em que estão trabalhando.

Essa é a minha separação de nível "0". O nível 1 fica comprometido se eu tiver que representar objetos de diferentes tipos na mesma exibição de tabela. Por exemplo, imagine que você tenha que escrever o aplicativo Contatos - para um único contato, você pode ter linhas representando números de telefone, outras linhas representando endereços, outras representando endereços de e-mail e assim por diante. Eu quero evitar essa abordagem:

- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
  id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
  if ([object isKindOfClass: [PhoneNumber class]]) {
    //configure phone number cell
  }
  else if …
}

Duas soluções se apresentaram até agora. Uma é construir dinamicamente um seletor:

- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
  id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
  NSString *cellSelectorName = [NSString stringWithFormat: @"tableView:cellFor%@AtIndexPath:", [object class]];
  SEL cellSelector = NSSelectorFromString(cellSelectorName);
  return [self performSelector: cellSelector withObject: tableView withObject: object];
}

- (UITableViewCell *)tableView: (UITableView *)tableView cellForPhoneNumberAtIndexPath: (NSIndexPath *)indexPath {
  // configure phone number cell
}

Nesta abordagem, você não precisa editar a árvore epic if() para suportar um novo tipo - basta adicionar o método que suporta a nova classe. Essa é uma excelente abordagem se essa visualização de tabela for a única que precisa representar esses objetos ou precisa apresentá-los de uma maneira especial. Se os mesmos objetos forem representados em tabelas diferentes com origens de dados diferentes, essa abordagem será dividida conforme os métodos de criação de células precisarem de compartilhamento nas origens de dados - você poderia definir uma superclasse comum que fornece esses métodos ou fazer isso:

@interface PhoneNumber (TableViewRepresentation)

- (UITableViewCell *)tableView: (UITableView *)tableView representationAsCellForRowAtIndexPath: (NSIndexPath *)indexPath;

@end

@interface Address (TableViewRepresentation)

//more of the same…

@end

Em seguida, na sua classe de fonte de dados:

- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
  id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
  return [object tableView: tableView representationAsCellForRowAtIndexPath: indexPath];
}

Isso significa que qualquer fonte de dados que precise exibir números de telefone, endereços, etc. pode apenas perguntar a qualquer objeto que seja representado por uma célula de visão de tabela. A fonte de dados em si não precisa mais saber nada sobre o objeto sendo exibido.

"Mas espere", eu ouço uma intervenção hipotética do interlocutor, "isso não quebra MVC? Você não está colocando detalhes da visualização em uma classe de modelo?"

Não, não quebra o MVC. Você pode pensar nas categorias neste caso como sendo uma implementação de Decorator ; então PhoneNumber é uma classe de modelo, mas PhoneNumber(TableViewRepresentation) é uma categoria de visualização. A fonte de dados (um objeto controlador) faz a intermediação entre o modelo e a visualização, de modo que a arquitetura MVC ainda é válida.

Você também pode ver esse uso de categorias como decoração nos frameworks da Apple. NSAttributedString é uma classe de modelo, mantendo algum texto e atributos. O AppKit fornece NSAttributedString(AppKitAdditions) e o UIKit fornece NSAttributedString(NSStringDrawing) , categorias de decoradores que adicionam comportamento de desenho a essas classes de modelo.

    
por 04.12.2012 / 09:43
fonte
3

As pessoas tendem a empacotar muito no UIViewController / UITableViewController.

A delegação para outra classe (não o controlador de visualização) geralmente funciona bem. Os delegados não precisam necessariamente de uma referência ao controlador de visualização, pois todos os métodos delegados recebem uma referência para o UITableView , mas precisarão acessar de alguma forma os dados para os quais estão delegando.

Algumas ideias para reorganização para reduzir o tamanho:

  • Se você estiver construindo as células de visualização de tabela no código, considere carregá-las em vez de um arquivo de ponta ou de um storyboard. Os storyboards permitem protótipos e células de tabelas estáticas - confira esses recursos se você não estiver familiarizado

  • se seus métodos delegados contiverem muitas instruções 'if' (ou instruções de troca), isso é um sinal clássico de que você pode refatorar algumas coisas

Sempre me pareceu um pouco engraçado que o UITableViewDataSource fosse responsável por entender o bit de dados correto e configurando uma exibição para exibi-lo. Um bom ponto de refatoração pode ser alterar seu cellForRowAtIndexPath para obter um identificador dos dados que precisam ser exibidos em uma célula e delegar a criação da visualização de célula a outro delegado (por exemplo, criar CellViewDelegate ou similar) no item de dados apropriado.

    
por 30.11.2012 / 15:42
fonte
2

Aqui está mais ou menos o que estou fazendo atualmente ao enfrentar problemas semelhantes:

  • Mova as operações relacionadas a dados para a classe XXXDataSource (que herda de BaseDataSource: NSObject). BaseDataSource fornece alguns métodos convenientes como - (NSUInteger)rowsInSection:(NSUInteger)sectionNum; , subclasse substitui o método de carregamento de dados (como aplicativos geralmente têm algum tipo de método de carga de cache offlie parece - (void)loadDataWithUpdateBlock:(LoadProgressBlock)dataLoadBlock completion:(LoadCompletionBlock)completionBlock; para que possamos atualizar UI com dados em cache recebidos em LoadProgressBlock enquanto estamos atualizando informações de na rede, e no bloco de conclusão, atualizamos a interface do usuário com novos dados e removemos os indicadores de progresso, se houver). Essas classes NÃO estão em conformidade com o protocolo UITableViewDataSource .

  • No BaseTableViewController (que está em conformidade com os protocolos UITableViewDataSource e UITableViewDelegate ) eu tenho referência a BaseDataSource, que eu crio durante o init do controlador. Em UITableViewDataSource parte do controller eu simplesmente retorno valores de dataSource (como %código%).

Aqui está o meu cellForRow na classe base (não é necessário sobrescrever subclasses):

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *cellIdentifier = [NSString stringWithFormat:@"%@%@", NSStringFromClass([self class]), @"TableViewCell"];
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
    if (!cell) {
        cell = [self createCellForIndexPath:indexPath withCellIdentifier:cellIdentifier];
    }
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}

configureCell deve ser substituído por subclasses e createCell retorna UITableViewCell, portanto, se você quiser uma célula personalizada, substitua-a também.

  • Depois que as coisas básicas são configuradas (na verdade, no primeiro projeto que usa esse esquema, depois que essa parte pode ser reutilizada), o que resta para - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.tableViewDataSource sectionsCount]; } subclasses é:

    • Substitui configureCell (isso geralmente se transforma em solicitar dataSource para o objeto para caminho de índice e alimentá-lo para o método configureWithXXX: da célula ou obter a representação UITableViewCell do objeto como na resposta de user4051)

    • Substituir didSelectRowAtIndexPath: (obviamente)

    • Escreva a subclasse BaseDataSource, que cuida do trabalho com a parte necessária do Model (suponha que haja duas classes BaseTableViewController e Account , portanto, as subclasses serão AccountDataSource e LanguageDataSource).

E isso é tudo para a parte de exibição de tabela. Eu posso postar algum código para o GitHub, se necessário.

Editar: algumas recomendações podem ser encontradas no link (que tem link para este link) questão) e um artigo complementar sobre os controladores de tableview.

    
por 21.10.2013 / 20:26
fonte
2

Minha opinião sobre isso é que o modelo precisa fornecer uma matriz de objetos que são chamados de ViewModel ou viewData encapsulados em um cellConfigurator. O CellConfigurator contém o CellInfo necessário para deque-lo e configurar a célula. ele fornece à célula alguns dados para que a célula possa se configurar. isso também funciona com a seção se você adicionar algum objeto SectionConfigurator que mantenha os CellConfigurators. Eu comecei a usá-lo um tempo atrás inicialmente apenas dando à célula um viewData e tive o ViewController lidando com o dequeuing da célula. mas eu li um artigo que apontava para esse repositório do gitHub.

link

isso pode mudar a forma como você está se aproximando disso.

    
por 29.04.2016 / 19:05
fonte
2

Recentemente, escrevi um artigo sobre como implementar delegados e fontes de dados para o UITableView: link

A ideia principal é dividir as responsabilidades em classes separadas, como fábrica de células, fábrica de seção e fornecer alguma interface genérica para o modelo que o UITableView exibirá. O diagrama abaixo explica tudo:

    
por 17.01.2014 / 00:37
fonte