Evito di usare UITableViewController
, poiché mette molte responsabilità in un singolo oggetto. Pertanto separo la sottoclasse UIViewController
dall'origine dati e delegato. La responsabilità del controller di visualizzazione è di preparare la vista tabella, creare un'origine dati con dati e unirli insieme. Cambiando il modo in cui è rappresentata la vista tabella può essere fatto senza modificare il controller di visualizzazione, e infatti lo stesso controller di visualizzazione può essere utilizzato per più origini dati che seguono tutte questo schema. Allo stesso modo, la modifica del flusso di lavoro dell'app significa modifiche al controller della vista senza preoccuparsi di ciò che accade alla tabella.
Ho provato a separare i protocolli UITableViewDataSource
e UITableViewDelegate
in oggetti diversi, ma di solito si tratta di una falsa divisione poiché quasi ogni metodo sul delegato deve scavare nell'origine dati (ad esempio, sulla selezione, il delegato ha bisogno di sapere quale oggetto è rappresentato dalla riga selezionata). Quindi finisco con un singolo oggetto che è sia l'origine dati sia il delegato. Questo oggetto fornisce sempre un metodo -(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath
che sia l'aspetto dell'origine dati sia gli aspetti dei delegati devono sapere su cosa stanno lavorando.
Questa è la mia separazione di preoccupazioni di "livello 0". Il livello 1 viene attivato 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 email 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 …
}
Due soluzioni si sono presentate finora. 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'albero epico if()
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 verranno rappresentati in tabelle diverse con origini dati diverse, questo approccio si interromperà 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 effettuare questa operazione:
@interface PhoneNumber (TableViewRepresentation)
- (UITableViewCell *)tableView: (UITableView *)tableView representationAsCellForRowAtIndexPath: (NSIndexPath *)indexPath;
@end
@interface Address (TableViewRepresentation)
//more of the same…
@end
Quindi nella classe dell'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 fonte di dati che deve visualizzare numeri di telefono, indirizzi ecc. può solo chiedere qualunque oggetto è rappresentato per una cella di visualizzazione tabella. La fonte dei dati non ha più bisogno di sapere nulla sull'oggetto che viene visualizzato.
"Ma aspetta", sento un ipotetico interlocutore interlocutore, "non è che interrompe MVC? Non stai mettendo i dettagli della vista in una classe del modello?"
No, non infrange MVC. In questo caso puoi pensare alle categorie come implementazione di Decorator ; quindi PhoneNumber
è una classe del modello ma PhoneNumber(TableViewRepresentation)
è una categoria di vista. L'origine dati (un oggetto controller) media tra il modello e la vista, quindi l'architettura MVC mantiene ancora.
Puoi vedere questo uso delle categorie anche come decorazione nei framework Apple. NSAttributedString
è una classe del modello, contenente testo e attributi. AppKit fornisce NSAttributedString(AppKitAdditions)
e Uikit fornisce NSAttributedString(NSStringDrawing)
, categorie di decoratori che aggiungono un comportamento di disegno a queste classi modello.