J'évite d'utiliser UITableViewController
, car cela confère beaucoup de responsabilités à un seul objet. Par conséquent, je sépare la UIViewController
sous - classe de la source de données et le délégué. La responsabilité du contrôleur de vue est de préparer la vue tableau, de créer une source de données avec des données et de lier ces éléments entre eux. Il est possible de changer la façon dont la table est représentée sans changer de contrôleur de vue, et le même contrôleur de vue peut être utilisé pour plusieurs sources de données qui suivent toutes ce modèle. De même, modifier le flux de travail de l'application signifie modifier le contrôleur de vue sans se soucier de ce qu'il advient de la table.
J'ai essayé de séparer les protocoles UITableViewDataSource
et UITableViewDelegate
dans des objets différents, mais cela finit généralement par être une fausse division car presque toutes les méthodes du délégué doivent creuser dans la source de données (par exemple, lors de la sélection, le délégué doit savoir quel objet est représenté par le rangée sélectionnée). Je me retrouve donc avec un seul objet qui est à la fois la source de données et le délégué. Cet objet fournit toujours une méthode -(id)tableView: (UITableView *)tableView representedObjectAtIndexPath: (NSIndexPath *)indexPath
dont la source de données et les aspects délégués ont besoin de savoir sur quoi ils travaillent.
C'est ma séparation de niveau "niveau 0". Le niveau 1 est engagé si je dois représenter des objets de types différents dans la même vue de table. Par exemple, imaginez que vous deviez écrire l'application Contacts: pour un seul contact, vous pourriez avoir des lignes représentant des numéros de téléphone, d'autres lignes représentant des adresses, d'autres représentant des adresses e-mail, etc. Je veux éviter cette approche:
- (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 …
}
Deux solutions se sont présentées jusqu'à présent. L'une consiste à construire dynamiquement un sélecteur:
- (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
}
Dans cette approche, vous n'avez pas besoin de modifier l' if()
arborescence épique pour prendre en charge un nouveau type. Ajoutez simplement la méthode prenant en charge la nouvelle classe. C'est une excellente approche si cette vue sous forme de tableau est la seule qui doit représenter ces objets, ou doit les présenter de manière spéciale. Si les mêmes objets doivent être représentés dans différentes tables avec différentes sources de données, cette approche échoue, car les méthodes de création de cellules doivent être partagées entre les sources de données. Vous pouvez définir une super-classe commune fournissant ces méthodes, ou procédez comme suit:
@interface PhoneNumber (TableViewRepresentation)
- (UITableViewCell *)tableView: (UITableView *)tableView representationAsCellForRowAtIndexPath: (NSIndexPath *)indexPath;
@end
@interface Address (TableViewRepresentation)
//more of the same…
@end
Puis dans votre classe de source de données:
- (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath {
id object = [self tableView: tableView representedObjectAtIndexPath: indexPath];
return [object tableView: tableView representationAsCellForRowAtIndexPath: indexPath];
}
Cela signifie que toute source de données ayant besoin d'afficher des numéros de téléphone, des adresses, etc. peut simplement demander quel objet est représenté pour une cellule d'affichage de tableau. La source de données elle-même n'a plus besoin de rien savoir de l'objet affiché.
"Mais attendez," j'entends un interlocuteur hypothétique, "ça ne rompt pas MVC? Ne mettez-vous pas les détails de vue dans une classe modèle?"
Non, ça ne casse pas MVC. Vous pouvez considérer les catégories dans ce cas comme une implémentation de Decorator ; Il en PhoneNumber
va de même d’une classe de modèle, mais d’ PhoneNumber(TableViewRepresentation)
une catégorie de vue. La source de données (un objet contrôleur) sert de médiateur entre le modèle et la vue, de sorte que l'architecture MVC est toujours valable.
Vous pouvez également voir cette utilisation des catégories comme décoration dans les cadres d’Apple. NSAttributedString
est une classe modèle contenant du texte et des attributs. AppKit fournit NSAttributedString(AppKitAdditions)
et UIKit fournit des NSAttributedString(NSStringDrawing)
catégories de décorateur qui ajoutent un comportement de dessin à ces classes de modèle.