Evito usar UITableViewController
, pois coloca muitas responsabilidades em um único objeto. Portanto, separo a UIViewController
subclasse da fonte de dados e delego. A responsabilidade do controlador de exibição é preparar a exibição de tabela, criar uma fonte de dados com dados e conectar essas coisas. A alteração da maneira como a visualização da tabela é representada pode ser feita sem alterar o controlador de exibição e, de fato, o mesmo controlador de exibiçã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 o UITableViewDataSource
e UITableViewDelegate
protocolos em diferentes objetos, mas que normalmente acaba por ser uma falsa divisão como quase todos os métodos para as necessidades de delegado para cavar a fonte de dados (por exemplo, na seleção, as necessidades de delegado para saber o que objeto é representado pela linha selecionada). Então, acabo com um único objeto que é a fonte de dados e o delegado. Este objeto sempre fornece um método-(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath
qual os aspectos da fonte de dados e do delegado precisam saber no que estão trabalhando.
Essa é a minha separação "nível 0" de preocupações. O nível 1 é ativado se eu tiver que representar objetos de tipos diferentes na mesma exibição de tabela. Como exemplo, imagine que você precise 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 email 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 o épico if()
árvore para oferecer suporte a um novo tipo - basta adicionar o método que suporta a nova classe. Essa é uma ótima abordagem se essa exibição de tabela for a única que precisa representar esses objetos ou precisa apresentá-los de uma maneira especial. Se os mesmos objetos serão representados em tabelas diferentes com fontes de dados diferentes, essa abordagem será interrompida conforme os métodos de criação de células precisem ser compartilhados entre as fontes de dados - você poderá definir uma superclasse comum que forneça esses métodos ou faça o seguinte:
@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 qualquer objeto representado para uma célula de exibição de tabela. A própria fonte de dados não precisa mais saber nada sobre o objeto que está sendo exibido.
"Mas espere", ouço um interlocutor hipotético interpor ", não é? quebra o MVC? Você não está colocando detalhes da exibição em uma classe de modelo?"
Não, não quebra o MVC. Você pode pensar nas categorias nesse caso como uma implementação do Decorator ; entãoPhoneNumber
é uma classe de modelo, mas PhoneNumber(TableViewRepresentation)
é uma categoria de exibição. A fonte de dados (um objeto do controlador) medeia entre o modelo e a visualização, portanto a arquitetura MVC ainda é válida.
Você pode ver esse uso de categorias como decoração também nas estruturas da Apple. NSAttributedString
é uma classe de modelo, contendo algum texto e atributos. O AppKit fornece NSAttributedString(AppKitAdditions)
e o UIKit fornece NSAttributedString(NSStringDrawing)
categorias de decorador que adicionam comportamento de desenho a essas classes de modelo.