如何在MVVM架构下处理UITableViewCell中的UICollectionView
我来帮你梳理下MVVM架构里处理这种嵌套TableView+CollectionView的思路,结合你给出的场景和代码来拆解:
首先明确你的视图层级:ViewController → UITableView → CustomTableViewCell → UICollectionView → CustomCollectionViewCell
你已经实现的主ViewModel代码如下:
class ViewModel { //MARK: Private Properties private var libraries = [Library](){ didSet{ self.reloadTableViewClosure?() } } //MARK: Internal Properties var reloadTableViewClosure: (()->())? var numberOfLibraries: Int{ return self.libraries.count } //MARK: Internal Methods func getLibrary(at indexPath: IndexPath) -> Library { return self.libraries[indexPath.row] } //MARK: Initializer init() { self.fetchLibraryList() } //MARK: Private Methods private func fetchLibraryList() { if let path = Bundle.main.path(forResource: "LibraryList", ofType: "json") { if let libraryList = try? JSONDecoder().decode([Library].self, from: Data(contentsOf: URL(fileURLWithPath: path))) { self.libraries = libraryList } } } }
针对你的两个疑问,逐一解答:
问题1:是否应让主ViewController同时作为UITableView和所有UICollectionView的delegate与dataSource?
绝对不建议这么做!主ViewController本来就负责TableView的逻辑,如果再接管所有CollectionView的代理,会导致ViewController的代码量急剧膨胀,完全违背MVVM“瘦控制器”的核心原则,后期维护起来会非常头疼。
正确的做法是让CustomTableViewCell自己负责内部CollectionView的delegate和dataSource:
- 给CustomTableViewCell单独设置一个ViewModel(比如叫
LibraryCellViewModel),这个ViewModel负责提供当前Cell内CollectionView需要的所有数据(比如item数量、每个item的模型) - 在CustomTableViewCell内部,把CollectionView的delegate和dataSource指向自己,然后通过ViewModel获取数据填充UI
- 主ViewController只需要处理TableView的逻辑,把对应的
LibraryCellViewModel传给CustomTableViewCell就行
问题2:CustomCollectionViewCell的模型对象应存放在当前ViewModel中,还是需要单独创建新的ViewModel?
建议为CollectionView的每个item创建对应的ViewModel(比如BookItemViewModel),然后让LibraryCellViewModel持有这些子ViewModel的数组。
具体的层级应该是:
- 主
ViewModel→ 持有多个LibraryCellViewModel(对应TableView的每一行) LibraryCellViewModel→ 持有多个BookItemViewModel(对应CollectionView的每个item)BookItemViewModel→ 封装CustomCollectionViewCell需要展示的所有数据和逻辑(比如格式化书名、处理封面加载状态等)
这样做的好处:
- 职责单一:每个ViewModel只负责对应层级的UI数据和逻辑,不会出现数据混乱
- 可复用性:
BookItemViewModel可以在其他需要展示相同item的地方复用 - 易于测试:每个ViewModel的逻辑都可以单独写单元测试,不用依赖UI层
举个简单的代码示例:
首先定义LibraryCellViewModel:
class LibraryCellViewModel { private let books: [Book] var numberOfBooks: Int { books.count } init(books: [Book]) { self.books = books } func bookItemViewModel(at index: Int) -> BookItemViewModel { return BookItemViewModel(book: books[index]) } }
然后是BookItemViewModel:
class BookItemViewModel { let bookTitle: String let bookCoverURL: URL? init(book: Book) { self.bookTitle = book.name // 这里可以做格式化,比如添加前缀、处理空值 self.bookCoverURL = URL(string: book.coverUrl) } }
然后在主ViewModel里,把Library转换成LibraryCellViewModel:
// 主ViewModel里修改相关方法 private var libraryCellViewModels = [LibraryCellViewModel]() { didSet { self.reloadTableViewClosure?() } } var numberOfLibraries: Int { libraryCellViewModels.count } func getLibraryCellViewModel(at indexPath: IndexPath) -> LibraryCellViewModel { libraryCellViewModels[indexPath.row] } private func fetchLibraryList() { if let path = Bundle.main.path(forResource: "LibraryList", ofType: "json") { if let libraryList = try? JSONDecoder().decode([Library].self, from: Data(contentsOf: URL(fileURLWithPath: path))) { // 把Library转换成对应的CellViewModel self.libraryCellViewModels = libraryList.map { LibraryCellViewModel(books: $0.books) } } } }
最后在CustomTableViewCell里:
class CustomTableViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate { @IBOutlet weak var collectionView: UICollectionView! var viewModel: LibraryCellViewModel? { didSet { collectionView.reloadData() } } override func awakeFromNib() { super.awakeFromNib() collectionView.dataSource = self collectionView.delegate = self // 注册cell等操作 } // MARK: UICollectionView DataSource func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { viewModel?.numberOfBooks ?? 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionViewCell", for: indexPath) as! CustomCollectionViewCell if let itemVM = viewModel?.bookItemViewModel(at: indexPath.item) { cell.configure(with: itemVM) } return cell } }
这样整个架构就非常清晰,每个组件各司其职,ViewController只需要处理TableView的绑定和基本逻辑,完全不用关心CollectionView的细节。
内容的提问来源于stack exchange,提问作者PGDev

