为每个 UITableViewCell 创建视图模型
Posted
技术标签:
【中文标题】为每个 UITableViewCell 创建视图模型【英文标题】:Creating view-model for each UITableViewCell 【发布时间】:2016-10-28 06:22:27 【问题描述】:我坚持为表格视图的单元格创建视图模型的设计决策。每个单元格的数据由数据源类提供(有一个Contacts
数组)。在MVVM
只有视图模型可以与模型对话,但是将数据源放在视图模型中是没有意义的,因为它可以访问所有单元格的数据,而且将数据源放在视图控制器中也是错误的因为它不能引用数据。还有其他一些关键时刻:
cellForRowAtindexPath
不能放在视图模型中,因为
它不应包含任何 UI 引用
View/ViewController 的视图模型不应与单元格的视图模型交互
在MVVM
的关系中为单元格“插入”数据源的正确方法是什么?谢谢。
【问题讨论】:
除非您使用额外的框架,例如 React,否则 MVVM 在 ios 中并不能真正工作,因为 iOS 控件没有数据绑定。 @Paulw11 我使用的是委托模式而不是 React,它的代码更多,但更清晰,调试也不痛苦 模型必须归某物所有...所以,是的,对于 iOS 来说,这可能很棘手。但是当提供一个视图模型对象时,从它们模型中提供最多的计算后/获取后的值,并在初始化视图模型 obj 后尝试不在视图模型中引用模型。您应该在初始化时将模型中所需的值/引用注入视图模型(即依赖注入)。 @Jorge Ortiz 有一个很好的答案。 【参考方案1】:除非您有通过 Model-View-ViewModel
解决的特定问题,否则尝试仅将其用于“最佳实践”最终会引入许多不必要的复杂性。
您的数据源负责填充您的表格。除了您的数据源之外,没有其他东西需要对 contacts
的引用,因为它会使用这些数据更新您的表。
View Models
仅在您需要进行复杂的 UI 交互和更新时发挥作用。 VM 负责封装视图的状态,例如...
-
文本字段的值
选择了哪些复选框/单选按钮
元素的颜色
动画逻辑
UI 元素之间的依赖关系
当您的视图发生更改时,您的 View Model
负责更新您的 Model
(必要时),以便通过 UI 反映对该 Model
所做的更改。
话虽如此,视图模型在 IOS 中没有意义,这是因为 IOS 在称为 MVC
(模型-视图-控制器)的设计方法中使用了 View Controllers
p>
【讨论】:
首先,我使用 MVVM 不是因为它花哨,而是因为它可以更轻松地编写测试,它将表示逻辑和 MVC 没有的业务逻辑分开(因为视图控制器设计)。其次,MVVM 在 iOS 上很好,它只是 MVC 的升级版本。第三,因为 ViewController 和 View 如此紧密耦合,所以这种相等性几乎 100% 正确:ViewController = View,Apple 版本的 MVC 看起来更像这样:View/ViewController -> Model。所以 View/ViewController 可以与 Model 对话,而 Model 只在必须执行更新时通知 View/ViewController。 声明 MVVM 是“MVC 的升级版本”并不成立,因为事实并非如此。它们是针对同一问题(视图及其状态)的完全不同的方法。在 iOS 中,视图不需要 VM,因为它们已经拥有自己的状态,由 ViewController 更新。【参考方案2】:让我从一些理论开始。 MVVM 是针对 Microsoft Silverlight 和 WPF 的 Presentation Model(或应用程序模型)的专门化。 这种 UI 架构模式背后的主要思想是:
视图部分是唯一依赖于 GUI 框架的部分。这意味着对于 iOS,视图控制器是视图的一部分。 视图只能与视图模型对话。 从不模特。 视图模型保存视图的状态。此状态通过视图模型属性提供给视图。这些属性不仅包含标签的值,还包含其他与视图相关的信息,例如是否启用了保存按钮或评级视图的颜色。但是状态的信息必须是独立于 UI 框架的。因此,在 iOS 的情况下,颜色的属性应该是一个枚举,例如,而不是 UIColor。 视图模型还提供了处理 UI 操作的方法。此操作将与模型对话,但它们永远不会更改与数据直接相关的视图的状态。相反,它会与模型对话并要求进行所需的更改。 模型应该是自治的,即您应该能够为命令行应用程序和 UI 界面的模型使用相同的代码。它将处理所有业务逻辑。 模型不知道视图模型。因此,对视图模型的更改是通过观察机制传播的。对于 iOS 和具有普通 NSObject 子类甚至 Core Data 的模型,可以使用 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
数组)对话的视图模型,并且视图仅与视图模型对话。
希望这会有所帮助。
【讨论】:
@jorge 单元格的子视图是否也应该通过某种观察机制绑定到单元格视图模型?或者你只是在单元格中设置视图模型,然后立即调用configure()
?
@jorge 根据你的说法,TableviewController 会有一个 viewModel,TableviewCell 会有另一个 viewModel 对吗?这是一个好方法吗?
@AP_ 我认为这正是他的意思
@jorge 如何在 mvvm 的 uitableview 单元格中为按钮添加操作?
虚拟机应该保持视图的状态,所以每次都生成一个新的虚拟机不是一个好主意。相反,为 VC 的 VM 中的单元创建一个 VM 列表,并将这些 VM 传递给 VC 以获得不同的索引路径。以上是关于为每个 UITableViewCell 创建视图模型的主要内容,如果未能解决你的问题,请参考以下文章
将 xib 文件中的视图添加到 UITableViewCell 添加约束
UITableViewCell 内的 UICollectionView:每个的“reloadData”顺序?
根据子视图高度调整 UITableViewCell(从 xib 创建)高度