web-dev-qa-db-ja.com

各UITableViewCellのビューモデルの作成

テーブルビューのセルのビューモデルを作成するという設計上の決定に固執しています。各セルのデータは、データソースクラスによって提供されます(Contactsの配列を持っています)。 MVVMでは、ビューモデルのみがモデルと通信できますが、すべてのセルのデータにアクセスできるようになるため、データソースをビューモデルに配置しても意味がありません。また、データソースを配置することも間違っていますデータへの参照があってはならないため、View Controllerで。他にもいくつかの重要な瞬間があります。

  • 各セルには、共有モデルではなく、ビューモデルの独自のインスタンスが必要です
  • cellForRowAtindexPathは、UI参照を含んではならないため、ビューモデルに配置しないでください。
  • View/ViewControllerのview-modelは、セルのview-modelと相互作用してはなりません

MVVMの関係にあるセルのデータソースを「挿入」する正しい方法は何ですか?ありがとう。

26
tesla

いくつかの理論から始めましょう。 MVVMは、MicrosoftのSilverlightおよびWPFの プレゼンテーションモデル (またはアプリケーションモデル)の特殊化です。このUIアーキテクチャパターンの背後にある主なアイデアは次のとおりです。

  • ビュー部分は、GUIフレームワークに依存する唯一のものです。つまり、iOSの場合、View Controllerはビューの一部です。
  • ビューはビューモデルとのみ通信できます。 モデルには決して
  • ビューモデルは、ビューの状態を保持します。この状態は、ビューモデルプロパティを介してビューに提供されます。これらのプロパティには、ラベルの値だけでなく、保存ボタンが有効になっている場合や評価ビューの色など、ビューに関連する他の情報も含まれています。ただし、状態の情報はUIフレームワークに依存しない必要があります。そのため、iOSの場合、色のプロパティは、たとえばUIColorではなく列挙型である必要があります。
  • ビューモデルは、UIアクションを処理するメソッドも提供します。このアクションはモデルと通信しますが、データに直接関連するビューの状態を変更することはありません。代わりに、モデルと通信し、必要な変更を要求します。
  • モデルはautonomousである必要があります。つまり、コマンドラインアプリケーションとUIインターフェイスのモデルに同じコードを使用できる必要があります。すべてのビジネスロジックを処理します。
  • モデルはビューモデルについては知りません。したがって、ビューモデルへの変更は、観測メカニズムを介して伝播されます。 iOSおよびプレーンなNSObjectサブクラスまたはコアデータを含むモデルの場合、そのためにKVOを使用できます(Swiftでも)。
  • ビューモデルは、モデルの変更を認識すると、保持している状態を更新する必要があります(値型を使用する場合は、更新されたものを作成して置換する必要があります)。
  • ビューモデルはビューについて知りません。元の概念では、iOSでは使用できないデータバインディングを使用します。そのため、ビューモデルの変更は、観測メカニズムを通じて伝播されます。ここでKVOを使用することもできますし、質問で述べたように、Swiftプロパティオブザーバーと組み合わせるとさらに簡単になります。RxSwiftなどのリアクティブフレームワークを好む人もいます。 ReactiveCocoa、またはSwift Bond。

利点はあなたが言ったとおりです:

  • 関心事のより良い分離。
  • UIの独立性:他のUIへの簡単な移行。
  • 懸念事項の分離とコードの分離された性質により、テスト性が向上します。

質問に戻ると、UITableViewDataSourceプロトコルの実装は、UIフレームワークへの依存関係のため、アーキテクチャのビュー部分に属します。コードでそのプロトコルを使用するには、そのファイルがUIKitをインポートする必要があることに注意してください。ビューを返すtableView(:cellForRowAt:)などのメソッドもUIKitに大きく依存しています。

次に、Contactsの配列、つまり実際にモデルが、ビュー(データソースなど)を介して操作またはクエリできません。代わりに、ビューモデルをTable View Controllerに渡します。最も単純なケースでは、2つのプロパティがあります(計算されたプロパティではなく、保存することをお勧めします)。それらの1つはセクションの数であり、もう1つはセクションごとの行の数です。

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

ビューモデルはモデルへの参照で初期化され、初期化の最後のステップとして、これら2つのプロパティの値を設定します。

View Controllerのデータソースは、Viewモデルのデータを使用します:

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

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

最後に、セルごとに異なるビューモデル構造を作成できます。

struct ContactCellViewModel {
    let name: String

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

UITableViewCellサブクラスは、その構造体の使用方法を知っています。

class ContactTableViewCell: UITableViewCell {
    
    var viewModel: ContactCellViewModel!

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

各セルに対応するビューモデルを作成するために、Table Viewビューモデルはそれらを生成するメソッドを提供し、ビューモデルの配列を設定するために使用できます。

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

ご覧のとおり、ここでビューモデルはモデル(Contacts配列)と通信する唯一のものであり、ビューはビューモデルとのみ通信します。

お役に立てれば。

86
Jorge Ortiz