Evito di usare UITableViewController
, in quanto mette molte responsabilità in un singolo oggetto. Pertanto separo la UIViewController
sottoclasse dall'origine dati e dal delegato. La responsabilità del controller della vista è preparare la vista della tabella, creare un'origine dati con i dati e collegare tali elementi insieme. La modifica del modo in cui viene rappresentata la vista tabella può essere eseguita senza modificare il controller di visualizzazione e in effetti lo stesso controller di visualizzazione può essere utilizzato per più origini dati che seguono tutti questo schema. Allo stesso modo, cambiare il flusso di lavoro dell'app significa cambiare il controller di visualizzazione senza preoccuparsi di ciò che accade alla tabella.
Ho provato a separare i protocolli UITableViewDataSource
e UITableViewDelegate
in diversi oggetti, ma di solito finisce per essere una falsa divisione poiché quasi tutti i metodi sul delegato devono scavare nell'origine dati (ad esempio, sulla selezione, il delegato deve sapere quale oggetto è rappresentato dal riga selezionata). Quindi finisco con un singolo oggetto che è sia l'origine dati che il delegato. Questo oggetto fornisce sempre un metodo su -(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath
cui sia l'origine dati che gli aspetti delegati devono sapere su cosa stanno lavorando.
Questa è la mia separazione del livello "0" delle preoccupazioni. Il livello 1 si impegna se devo rappresentare oggetti di diverso tipo nella stessa vista tabella. Ad esempio, immagina di dover scrivere l'app Contatti: per un singolo contatto, potresti avere righe che rappresentano numeri di telefono, altre righe che rappresentano indirizzi, altre che rappresentano indirizzi e-mail e così via. Voglio evitare questo approccio:
- (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 …
}
Finora si sono presentate due soluzioni. Uno è quello di costruire dinamicamente un selettore:
- (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
}
In questo approccio, non è necessario modificare l' if()
albero epico per supportare un nuovo tipo: basta aggiungere il metodo che supporta la nuova classe. Questo è un ottimo approccio se questa vista tabella è l'unica che deve rappresentare questi oggetti o deve presentarli in un modo speciale. Se gli stessi oggetti saranno rappresentati in tabelle diverse con origini dati diverse, questo approccio si interrompe poiché i metodi di creazione delle celle devono essere condivisi tra le origini dati: è possibile definire una superclasse comune che fornisce questi metodi oppure è possibile farlo:
@interface PhoneNumber (TableViewRepresentation)
- (UITableViewCell *)tableView: (UITableView *)tableView representationAsCellForRowAtIndexPath: (NSIndexPath *)indexPath;
@end
@interface Address (TableViewRepresentation)
//more of the same…
@end
Quindi nella tua classe di origine dati:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
return [object tableView: tableView representationAsCellForRowAtIndexPath: indexPath];
}
Ciò significa che qualsiasi origine dati che deve visualizzare numeri di telefono, indirizzi ecc. Può semplicemente chiedere qualsiasi oggetto rappresentato per una cella di visualizzazione tabella. L'origine dati stessa non deve più sapere nulla sull'oggetto visualizzato.
"Ma aspetta," sento interporre un ipotetico interlocutore, "questo non rompe MVC? Non stai mettendo i dettagli di visualizzazione in una classe modello?"
No, non rompe MVC. Puoi pensare alle categorie in questo caso come un'implementazione di Decorator ; quindi PhoneNumber
è una classe di modello ma PhoneNumber(TableViewRepresentation)
è una categoria di visualizzazione. L'origine dati (un oggetto controller) media tra il modello e la vista, quindi l'architettura MVC è ancora valida.
Puoi vedere questo uso delle categorie come decorazione anche nei framework di Apple. NSAttributedString
è una classe del modello, con testo e attributi. AppKit fornisce NSAttributedString(AppKitAdditions)
e UIKit fornisce NSAttributedString(NSStringDrawing)
categorie di decoratori che aggiungono il comportamento del disegno a queste classi di modelli.