为每个UITableViewCell创建视图模型

时间:2016-06-25 22:45:23

标签: ios swift uitableview design-patterns mvvm

我坚持一个设计决策,为表视图的单元格创建视图模型。每个单元格的数据由数据源类提供(具有Contacts数组)。在MVVM中,只有视图模型可以与模型对话,但将数据源放在视图模型中是没有意义的,因为它可以访问所有单元格的数据,将数据源放入其中也是错误的。查看控制器,因为它不能引用数据。还有其他一些关键时刻:

  • 每个单元格必须拥有自己的视图模型实例,而不是共享的
  • cellForRowAtindexPath不得放在视图模型中,因为 它不应包含任何UI引用
  • View / ViewController的视图模型不应与单元格的视图模型交互

MVVM关系中为单元格“插入”数据源的正确方法是什么?感谢。

2 个答案:

答案 0 :(得分:84)

让我从一些理论开始。 MVVM是Microsoft的Silverlight和WPF的Presentation Model(或应用程序模型)的专业化。 这种UI架构模式背后的主要思想是:

  • 视图部分是唯一依赖于GUI框架的部分。这意味着对于iOS,视图控制器是视图的一部分。
  • 视图只能与视图模型对话。 从不到模特。
  • 视图模型保存视图的状态。通过视图模型属性向视图提供此状态。这些属性不仅包含标签的值,还包含其他视图相关信息,如启用了保存按钮或评级视图的颜色。但是国家的信息必须是独立于UI框架的。因此,对于iOS,颜色的属性应该是枚举,例如,而不是UIColor。
  • 视图模型还提供了处理UI操作的方法。此操作将与模型通信,但它们永远不会更改与数据直接相关的视图状态。相反,它会与模型进行对话并询问所需的更改。
  • 模型应该是自治,即您应该能够为命令行应用程序和UI界面使用相同的模型代码。它将照顾所有业务逻辑。
  • 该模型不了解视图模型。因此,视图模型的更改通过观察机制传播。对于iOS和具有普通NSObject子类甚至核心数据的模型,可以使用KVO(也适用于Swift)。
  • 一旦视图模型知道模型中的更改,它应该更新它所拥有的状态(如果使用值类型,那么它应该创建一个更新的并替换它)。
  • 视图模型不了解视图。在其最初的概念中,它使用数据绑定,这不适用于iOS。因此,视图模型中的更改通过观察机制传播。你也可以在这里使用KVO,或者正如你在问题中提到的那样,一个简单的委托模式,如果与Swift属性观察者结合使用会更好。有些人更喜欢反应式框架,比如RxSwift,ReactiveCocoa,甚至是Swift Bond。

好处如你所说:

  • 更好地分离关注点。
  • UI独立性:更轻松地迁移到其他UI。
  • 由于关注点分离和代码的解耦性质,可测试性更好。

回到你的问题,UITableViewDataSource协议的实现属于体系结构的视图部分,因为它依赖于UI框架。请注意,为了在代码中使用该协议,该文件必须导入UIKit。此外,返回视图的tableView(:cellForRowAt:)等方法在很大程度上依赖于UIKit。

然后,您的数组Contacts,确实是您的模型,无法通过视图(数据源或其他方式)进行操作或查询。相反,您将视图模型传递给表视图控制器,在最简单的情况下,它具有两个属性(我建议它们存储,而不是计算属性)。其中一个是节数,另一个是每个节的行数:



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




视图模型使用对模型的引用进行初始化,作为初始化的最后一步,它设置了这两个属性的值。

视图控制器中的数据源使用视图模型的数据:



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
    }
}




为了获得每个单元格的相应视图模型,表视图视图模型将提供一个生成它们的方法,并且可以用于填充视图模型的数组:



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




正如您所看到的,这里的视图模型是唯一与模型(您的Contacts数组)对话的模型,而视图只与视图模型对话。

希望这有帮助。

答案 1 :(得分:-4)

除非您遇到使用Model-View-ViewModel解决的特定问题,否则尝试仅针对“最佳做法”采用该问题最终会导致许多不必要的复杂性。

您的数据源负责填充表格。除了您的数据源之外,其他任何内容都不需要引用contacts,因为它会使用此数据更新您的表格。

View Models只有在您需要进行复杂的UI交互和更新时才会发挥作用。 VM负责封装您的视图状态,例如......

  1. 文本字段的值
  2. 选择了哪些复选框/单选按钮
  3. 元素的颜色
  4. 动画逻辑
  5. UI元素之间的依赖关系
  6. 对视图进行更改后,View Model有责任对Model(必要时)进行更新,以反映对Model所做的更改通过用户界面。

    综上所述,View Models在IOS 中没有意义,因为IOS在名为View Controllers的设计方法中使用MVC(模型 - 视图 - 控制器)