web-dev-qa-db-fra.com

Création d'un modèle de vue pour chaque UITableViewCell

Je suis coincé dans une décision de conception avec la création de modèles de vue pour les cellules de la vue tableau. Les données de chaque cellule sont fournies par une classe de source de données (a un tableau de Contacts). Dans MVVM seul view-model peut parler au modèle, mais cela n'a pas de sens de mettre la source de données dans view-model car cela rendrait possible l'accès aux données pour toutes les cellules, il est également faux de mettre la source de données dans le contrôleur de vue car il ne doit pas avoir de référence aux données. Il y a d'autres moments clés:

  • Chaque cellule doit avoir sa propre instance de modèle de vue et non pas partagée
  • cellForRowAtindexPath ne doit pas être placé dans un modèle de vue car il ne doit contenir aucune référence d'interface utilisateur
  • Le modèle de vue de View/ViewController ne doit pas interagir avec le modèle de vue de la cellule

Quelle est la bonne façon "d'insérer" la source de données pour les cellules dans la relation de MVVM? Merci.

26
tesla

Permettez-moi de commencer par une théorie. MVVM est une spécialisation du modèle de présentation (ou modèle d'application) pour Silverlight et WPF de Microsoft. Les principales idées derrière ce modèle architectural de l'interface utilisateur sont:

  • La partie vue est la seule qui dépend du framework GUI. Cela signifie que pour iOS, le contrôleur de vue fait partie de la vue.
  • La vue ne peut parler qu'au modèle de vue. Jamais au modèle.
  • Le modèle de vue contient l'état de la vue. Cet état est proposé à la vue via les propriétés du modèle de vue. Ces propriétés contiennent non seulement la valeur des étiquettes, mais également d'autres informations liées à la vue, comme si le bouton Enregistrer est activé ou la couleur d'une vue d'évaluation. Mais l'information de l'État doit être indépendante du cadre de l'interface utilisateur. Ainsi, dans le cas d'iOS, la propriété de la couleur doit être une énumération, par exemple, au lieu d'une UIColor.
  • Le modèle de vue fournit également des méthodes qui prendront en charge les actions d'interface utilisateur. Ces actions parleront au modèle, mais elles ne modifient jamais l'état de la vue qui est directement lié aux données. Au lieu de cela, il parle au modèle et demande les modifications requises.
  • Le modèle doit être autonome, c'est-à-dire que vous devriez pouvoir utiliser le même code pour le modèle pour une application en ligne de commande et une interface utilisateur. Il prendra en charge toute la logique métier.
  • Le modèle ne connaît pas le modèle de vue. Les modifications apportées au modèle de vue sont donc propagées via un mécanisme d'observation. Pour iOS et un modèle avec de simples sous-classes NSObject ou même Core Data, KVO peut être utilisé pour cela (également pour Swift).
  • Une fois que le modèle de vue connaît les modifications du modèle, il doit mettre à jour l'état qu'il contient (si vous utilisez des types de valeur, il doit en créer un mis à jour et le remplacer).
  • Le modèle de vue ne connaît pas la vue. Dans sa conception originale, il utilise une liaison de données, non disponible pour iOS. Ainsi, les changements dans le modèle de vue se propagent via un mécanisme d'observation. Vous pouvez également utiliser KVO ici, ou comme vous le mentionnez dans la question, un modèle de délégation simple, encore mieux s'il est combiné avec Swift observateurs de propriété, fera l'affaire. Certaines personnes préfèrent les cadres réactifs, comme RxSwift, ReactiveCocoa, ou même Swift Bond.

Les avantages sont comme vous l'avez mentionné:

  • Meilleure séparation des préoccupations.
  • Indépendance de l'interface utilisateur: migration plus facile vers d'autres interfaces utilisateur.
  • Meilleure testabilité en raison de la séparation des préoccupations et de la nature découplée du code.

Pour en revenir à votre question, l'implémentation du protocole UITableViewDataSource appartient à la partie vue de l'architecture, en raison de ses dépendances sur le framework UI. Notez que pour utiliser ce protocole dans votre code, ce fichier doit importer UIKit. De plus, des méthodes comme tableView(:cellForRowAt:) qui renvoie une vue dépendent fortement d'UIKit.

Ensuite, votre tableau de Contacts, qui est en effet votre modèle, ne peut pas être exploité ou interrogé via la vue (source de données ou autre). Au lieu de cela, vous passez un modèle de vue à votre contrôleur de vue de table, qui, dans le cas le plus simple, possède deux propriétés (je recommande qu'elles soient stockées, et non des propriétés calculées). L'un d'eux est le nombre de sections et l'autre est le nombre de lignes par section:

var numberOfSections: Int = 0
var rowsPerSection: [Int] = []

Le modèle de vue est initialisé avec une référence au modèle et comme dernière étape de l'initialisation, il définit la valeur de ces deux propriétés.

La source de données dans le contrôleur de vue utilise les données du modèle de vue:

override func numberOfSections(in tableView: UITableView) -> Int {
    return viewModel.numberOfSections
}

override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return viewModel.rowsPerSection[section]
}

Enfin, vous pouvez avoir une structure de modèle de vue différente pour chacune des cellules:

struct ContactCellViewModel {
    let name: String

    init(contact: Contact) {
        name = contact.name ?? ""
    }
}

Et la sous-classe UITableViewCell saura comment utiliser cette structure:

class ContactTableViewCell: UITableViewCell {
    
    var viewModel: ContactCellViewModel!

    func configure() {
        textLabel!.text = viewModel.name
    }
}

Afin d'avoir le modèle de vue correspondant pour chacune des cellules, le modèle de vue de vue de table fournira une méthode qui les génère et qui peut être utilisée pour remplir le tableau de modèles de vue:

func viewModelForCell(at index: Int) -> ContactCellViewModel {
    return ContactCellViewModel(contact: contacts[index])
}

Comme vous pouvez le voir, les modèles de vue sont les seuls à parler au modèle (votre tableau Contacts), et les vues ne parlent qu'aux modèles de vue.

J'espère que cela t'aides.

86
Jorge Ortiz