J'ai un problème lors de l'implémentation du modèle MVC sur iOS. J'ai cherché sur Internet mais ne semble pas avoir trouvé de solution satisfaisante à ce problème.
De nombreuses UITableViewController
implémentations semblent être plutôt grandes. La plupart des exemples que j'ai vus permettent de UITableViewController
mettre en œuvre <UITableViewDelegate>
et <UITableViewDataSource>
. Ces mises en œuvre sont l'une des principales raisons pour lesquelles il UITableViewController
devient gros. Une solution serait de créer des classes séparées qui implémentent <UITableViewDelegate>
et <UITableViewDataSource>
. Bien sûr, ces classes devraient faire référence à la UITableViewController
. Y a-t-il des inconvénients à utiliser cette solution? En général, je pense que vous devriez déléguer la fonctionnalité à d'autres classes "Helper" ou similaires, en utilisant le modèle de délégué. Existe-t-il des moyens bien établis de résoudre ce problème?
Je ne veux pas que le modèle contienne trop de fonctionnalités, ni la vue. Je crois que la logique devrait vraiment être dans la classe du contrôleur, car c'est l'une des pierres angulaires du modèle MVC. Mais la grande question est:
Comment diviser le contrôleur d'une implémentation MVC en de plus petites pièces gérables? (S'applique à MVC dans iOS dans ce cas)
Il pourrait y avoir un schéma général pour résoudre ce problème, bien que je recherche spécifiquement une solution pour iOS. Veuillez donner un exemple d’un bon modèle pour résoudre ce problème. Veuillez expliquer pourquoi votre solution est géniale.
UITableViewController
mécanique me semble assez étrange, donc je peux comprendre le problème. Je suis en fait heureux usage IMonoTouch
, car enMonoTouch.Dialog
fait expressément que beaucoup plus facile de travailler avec des tables sur iOS. En attendant, je suis curieux de savoir ce que pourraient suggérer d'autres personnes plusRéponses:
J'évite d'utiliser
UITableViewController
, car cela confère beaucoup de responsabilités à un seul objet. Par conséquent, je sépare laUIViewController
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
etUITableViewDelegate
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:
Deux solutions se sont présentées jusqu'à présent. L'une consiste à construire dynamiquement un sélecteur:
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:Puis dans votre classe de source de données:
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 fournitNSAttributedString(AppKitAdditions)
et UIKit fournit desNSAttributedString(NSStringDrawing)
catégories de décorateur qui ajoutent un comportement de dessin à ces classes de modèle.la source
cellForPhotoAtIndexPath
méthode de la source de données, puis d'appeler une méthode de fabrique appropriée. Ce qui, bien entendu, n’est possible que si des classes particulières occupent de manière prévisible des lignes particulières. Votre système de génération de vues sur les modèles est beaucoup plus élégant dans la pratique, même s'il s'agit peut-être d'une approche peu orthodoxe de MVC! :)Les gens ont tendance à accumuler beaucoup de choses dans UIViewController / UITableViewController.
La délégation à une autre classe (pas le contrôleur de vue) fonctionne généralement bien. Les délégués n'ont pas nécessairement besoin d'une référence vers le contrôleur de vue, car toutes les méthodes de délégation reçoivent une référence à
UITableView
, mais ils auront besoin d'accéder d'une manière ou d'une autre aux données pour lesquelles ils délèguent.Quelques idées de réorganisation pour réduire la longueur:
Si vous construisez les cellules de la vue tableau dans le code, envisagez de les charger à partir d'un fichier nib ou d'un storyboard. Les storyboards permettent d'utiliser des cellules de prototype et de table statique - vérifiez ces fonctionnalités si vous ne les connaissez pas.
si vos méthodes de délégué contiennent beaucoup d'instructions 'if' (ou d'instructions switch), c'est un signe classique de la possibilité de refactoriser
Cela m'a toujours semblé un peu amusant de penser qu’il
UITableViewDataSource
était responsable d’obtenir le contrôle du bit de données correct et de configurer une vue pour le montrer. Un bon point de refactoring pourrait être de changer votrecellForRowAtIndexPath
pour obtenir un handle sur les données qui doivent être affichées dans une cellule, puis de déléguer la création de la vue de cellule à un autre délégué (par exemple make aCellViewDelegate
ou similaire) qui est passé dans l'élément de données approprié.la source
Voici à peu près ce que je suis en train de faire face à un problème similaire:
Déplacez les opérations liées aux données vers la classe XXXDataSource (qui hérite de BaseDataSource: NSObject). BaseDataSource fournit des méthodes pratiques, telles que
- (NSUInteger)rowsInSection:(NSUInteger)sectionNum;
, la sous-classe substitue la méthode de chargement des données (les applications ayant généralement une sorte de méthode de chargement en cache offlie,- (void)loadDataWithUpdateBlock:(LoadProgressBlock)dataLoadBlock completion:(LoadCompletionBlock)completionBlock;
nous permettant de mettre à jour l'interface utilisateur avec les données en cache reçues dans LoadProgressBlock pendant la mise à jour des informations depuis le réseau et dans le bloc d'achèvement. nous actualisons l'interface utilisateur avec les nouvelles données et supprimons les indicateurs de progression, le cas échéant). Ces classes ne sont pas conformes auUITableViewDataSource
protocole.Dans BaseTableViewController (qui est conforme à
UITableViewDataSource
et auxUITableViewDelegate
protocoles), j'ai une référence à BaseDataSource, que je crée pendant l'initialisation du contrôleur. Dans uneUITableViewDataSource
partie du contrôleur, je renvoie simplement les valeurs de DataSource (comme- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [self.tableViewDataSource sectionsCount]; }
).Voici mon cellForRow dans la classe de base (pas besoin de remplacer dans les sous-classes):
configureCell doit être remplacé par des sous-classes et createCell renvoie UITableViewCell. Si vous souhaitez une cellule personnalisée, remplacez-la également.
Une fois les éléments de base configurés (en fait, dans le premier projet utilisant un tel schéma, cette partie peut ensuite être réutilisée). Ce qui reste pour les
BaseTableViewController
sous-classes est le suivant:Remplace configureCell (cela se transforme généralement en demandant à dataSource pour un objet de chercher le chemin d'index et en l'envoyant à la méthode configureWithXXX de la cellule ou en obtenant la représentation UITableViewCell de l'objet comme dans la réponse de l'utilisateur4051)
Remplacer didSelectRowAtIndexPath: (évidemment)
Ecrit la sous-classe BaseDataSource qui s’occupe de travailler avec la partie nécessaire de Model (supposons qu’il existe 2 classes
Account
etLanguage
, par conséquent, les sous-classes seront AccountDataSource et LanguageDataSource).Et c'est tout pour la partie vue de table. Je peux poster du code sur GitHub si nécessaire.
Edit: quelques recommandations peuvent être trouvées sur http://www.objc.io/issue-1/lighter-view-controllers.html (qui contient un lien vers cette question) et un article associé sur tableviewcontrollers.
la source
Mon point de vue est que le modèle doit donner un tableau d'objets appelés ViewModel ou viewData encapsulés dans un cellConfigurator. le CellConfigurator détient le CellInfo nécessaire pour l'exécuter et pour configurer la cellule. il donne des données à la cellule afin que celle-ci puisse se configurer elle-même. cela fonctionne aussi avec section si vous ajoutez un objet SectionConfigurator contenant les CellConfigurators. Au début, je commençais à utiliser cette méthode tout en donnant à la cellule un viewData. mais j'ai lu un article qui pointait vers ce dépôt gitHub.
https://github.com/fastred/ConfigurableTableViewController
cela peut changer votre façon de voir les choses.
la source
J'ai récemment écrit un article sur la mise en oeuvre de délégués et de sources de données pour UITableView: http://gosuwachu.gitlab.io/2014/01/12/uitableview-controller/
L'idée principale est de diviser les responsabilités en plusieurs classes, telles qu'une fabrique de cellules, une fabrique de sections, et de fournir une interface générique pour le modèle que UITableView va afficher. Le diagramme ci-dessous explique tout:
la source
Suivre les principes de SOLID résoudra tout type de problèmes de ce type.
Si vous voulez vos classes d'avoir une seule responsabilité, vous devez définir séparément
DataSource
et lesDelegate
classes et simplement injecter eux autableView
propriétaire (peut - êtreUITableViewController
ouUIViewController
ou quoi que ce soit d' autre). Voici comment vous surmontez la séparation des préoccupations .Mais si vous voulez juste avoir un code propre et lisible et que vous voulez vous débarrasser de cet énorme fichier viewController et que vous êtes dans Swif , vous pouvez utiliser
extension
s pour cela. Les extensions de la classe unique peuvent être écrites dans différents fichiers et tous ont accès les uns aux autres. Mais c’est vraiment ce qui résout le problème des SoC comme je l’ai mentionné.la source