You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在MVVM架构下处理UITableViewCell中的UICollectionView

MVVM架构下嵌套TableView与CollectionView的处理方案

我来帮你梳理下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需要展示的所有数据和逻辑(比如格式化书名、处理封面加载状态等)

这样做的好处:

  1. 职责单一:每个ViewModel只负责对应层级的UI数据和逻辑,不会出现数据混乱
  2. 可复用性:BookItemViewModel可以在其他需要展示相同item的地方复用
  3. 易于测试:每个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 06:27:32