Estou com um problema ao implementar o padrão MVC no iOS. Eu procurei na Internet, mas parece não encontrar nenhuma solução legal para esse problema.
Muitas UITableViewController
implementações parecem ser bastante grandes. A maioria dos exemplos que eu vi permite UITableViewController
implementar <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
. Existem desvantagens ao usar esta solução? Em geral, acho que você deve delegar a funcionalidade para outras classes "Helper" ou similares, usando o padrão de delegação. Existem formas bem estabelecidas de resolver esse problema?
Não quero que o modelo contenha muita funcionalidade nem a visualização. Acredito que a lógica realmente deve estar na classe do controlador, pois esse é um dos pilares 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 esse problema. Forneça um argumento sobre por que sua solução é incrível.
UITableViewController
mecânica me parece bastante estranha, para que eu possa me relacionar com o problema. Na verdade, estou feliz por usarMonoTouch
, porqueMonoTouch.Dialog
especificamente torna isso muito mais fácil trabalhar com tabelas no iOS. Enquanto isso, estou curioso para saber o que outras pessoas maisRespostas:
Evito usar
UITableViewController
, pois coloca muitas responsabilidades em um único objeto. Portanto, separo aUIViewController
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
eUITableViewDelegate
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:
Duas soluções se apresentaram até agora. Uma é construir dinamicamente um seletor:
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:Em seguida, na sua classe de fonte de dados:
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ão
PhoneNumber
é uma classe de modelo, masPhoneNumber(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 forneceNSAttributedString(AppKitAdditions)
e o UIKit forneceNSAttributedString(NSStringDrawing)
categorias de decorador que adicionam comportamento de desenho a essas classes de modelo.fonte
cellForPhotoAtIndexPath
método da fonte de dados e, em seguida, chama um método de fábrica apropriado. O que, obviamente, só é possível se determinadas classes ocuparem previsivelmente linhas específicas. Acho que seu sistema de geração de categorias em modelos é muito mais elegante na prática, embora seja talvez uma abordagem pouco ortodoxa para o MVC! :)As pessoas tendem a incluir muito no UIViewController / UITableViewController.
A delegação para outra classe (não o controlador de exibição) geralmente funciona bem. Os delegados não precisam necessariamente de uma referência de volta ao controlador de exibição, pois todos os métodos de delegados passam uma referência ao
UITableView
, mas precisam acessar de alguma forma os dados para os quais estão delegando.Algumas idéias para reorganização para reduzir o comprimento:
se você estiver construindo as células de exibição de tabela no código, considere carregá-las a partir de um arquivo de ponta ou de um storyboard. Os storyboards permitem células de tabela estáticas e protótipo - confira esses recursos se você não estiver familiarizado
se os métodos delegados contiverem muitas instruções 'if' (ou instruções de troca), isso é um sinal clássico de que você pode refatorar
Sempre me pareceu um pouco engraçado que o
UITableViewDataSource
responsável por controlar o bit correto de dados e configurar uma exibição para mostrá-lo. Um bom ponto de refatoração pode ser alterar o seucellForRowAtIndexPath
para obter um controle sobre os dados que precisam ser exibidos em uma célula e delegar a criação da visualização da célula para outro delegado (por exemplo, criar umCellViewDelegate
ou similar) que é passado no item de dados apropriado.fonte
Aqui está aproximadamente o que estou fazendo atualmente ao enfrentar um problema semelhante:
Mova as operações relacionadas a dados para a classe XXXDataSource (que herda de BaseDataSource: NSObject). O BaseDataSource fornece alguns métodos convenientes, como a
- (NSUInteger)rowsInSection:(NSUInteger)sectionNum;
subclasse substitui o método de carregamento de dados (como os aplicativos geralmente têm algum tipo de método de carregamento de cache externo,- (void)loadDataWithUpdateBlock:(LoadProgressBlock)dataLoadBlock completion:(LoadCompletionBlock)completionBlock;
para que possamos atualizar a interface do usuário com os dados em cache recebidos no LoadProgressBlock enquanto atualizamos as informações da 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 oUITableViewDataSource
protocolo.Em BaseTableViewController (que se adapta à
UITableViewDataSource
eUITableViewDelegate
protocolos) que tem a referência BaseDataSource, que crio durante a inicialização do controlador. EmUITableViewDataSource
parte do controlador, simplesmente retorno valores de dataSource (like- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.tableViewDataSource sectionsCount]; }
).Aqui está o meu cellForRow na classe base (não é necessário substituir nas subclasses):
configureCell deve ser substituído por subclasses e createCell retorna UITableViewCell, portanto, se você desejar 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 as
BaseTableViewController
subclasses é:Substituir configureCell (isso geralmente se transforma em pedir ao dataSource o objeto para o caminho do índice e alimentá-lo com o método configureWithXXX: da célula ou obter a representação UITableViewCell do objeto, como na resposta do usuário4051)
Substituir didSelectRowAtIndexPath: (obviamente)
Escreva a subclasse BaseDataSource que cuida do trabalho com a parte necessária do Model (suponha que existam 2 classes
Account
eLanguage
, portanto, as subclasses serão AccountDataSource e LanguageDataSource).E isso é tudo para ver parte da tabela. Posso postar algum código no GitHub, se necessário.
Edit: algumas recomendações podem ser encontradas em http://www.objc.io/issue-1/lighter-view-controllers.html (que tem um link para esta pergunta) e artigo complementar sobre os tableviewcontrollers.
fonte
Minha opinião sobre isso é que o modelo precisa fornecer uma matriz de objetos chamados ViewModel ou viewData encapsulados em um cellConfigurator. o CellConfigurator mantém o CellInfo necessário para removê-lo e configurar a célula. ele fornece à célula alguns dados para que ela possa se configurar. isso também funciona com a seção se você adicionar algum objeto SectionConfigurator que mantenha os CellConfigurators. Comecei a usar isso há algum tempo, inicialmente, apenas fornecendo à célula um viewData e o ViewController lidava com o desenfileiramento da célula. mas li um artigo que apontava para este repositório do gitHub.
https://github.com/fastred/ConfigurableTableViewController
isso pode mudar a maneira como você está abordando isso.
fonte
Recentemente, escrevi um artigo sobre como implementar delegados e fontes de dados para o UITableView: http://gosuwachu.gitlab.io/2014/01/12/12/uitableview-controller/
A idéia principal é dividir responsabilidades em classes separadas, como fábrica de células, fábrica de seções e fornecer alguma interface genérica para o modelo que o UITableView exibirá. O diagrama abaixo explica tudo:
fonte
Seguindo o SOLID princípios do resolverá qualquer tipo de problema como esse.
Se você quiser suas aulas de ter apenas uma única responsabilidade, você deve definir separada
DataSource
eDelegate
aulas e simplesmente injetar -los para otableView
proprietário (poderia serUITableViewController
ouUIViewController
ou qualquer outra coisa). É assim que você supera a separação de preocupações .Mas se você quer apenas um código limpo e legível, e quer se livrar desse enorme arquivo viewController e está no Swif , pode usar
extension
s para isso. Extensões da classe única podem ser gravadas em arquivos diferentes e todas elas têm acesso uma à outra. Mas isso é nit resolve realmente o problema do SoC , como mencionei.fonte