如何在UITableView对应Section中分别放置不同UICollectionView并解决渲染异常问题
你现在的核心问题是混淆了UITableView的Section和它内部UICollectionView的Section——每个TableViewCell里的CollectionView其实只有1个Section,但你在collectionView(_:cellForItemAt:)里判断的是CollectionView自身的section,这完全不对。另外,你的TableViewCell没有区分当前要展示的是普通CollectionViewCell模型还是按钮模型,导致不管哪个TableView Section,都走了CollectionView的Section 0逻辑。
1. 给TableViewCell添加类型标识,区分要加载的CollectionViewCell类型
首先在TableViewCell.swift里加个属性,用来标记当前这个TableViewCell需要展示哪种CollectionViewCell:
// 在TableViewCell类内部添加 enum CollectionCellType { case normal case button } var cellType: CollectionCellType = .normal
2. 修改updateCellWith方法,同时传递模型和类型
原来的方法只传了模型数组,现在要把类型也传进去,让TableViewCell知道该加载哪种cell:
func updateCellWith(row: [CollectionViewModel], type: CollectionCellType) { self.rowWithColors = row self.cellType = type self.collectionView.reloadData() }
3. 修复UITableView的cellForRowAt方法
这里有两个关键问题要补:一是Section 1分支里忘记return cell了,会导致返回默认空cell;二是调用updateCellWith时要传对应类型:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.section == 0{ if let cell = tableView.dequeueReusableCell(withIdentifier: "tableviewcellid", for: indexPath) as? TableViewCell { // 保留你原有的模型赋值、调试代码 cell.updateCellWith(row: rowArray, type: .normal) cell.selectionStyle = .none return cell } } else if indexPath.section == 1{ if let cell = tableView.dequeueReusableCell(withIdentifier: "tableviewcellid", for: indexPath) as? TableViewCell { // 保留你原有的模型赋值、调试代码 cell.updateCellWith(row: rowArray, type: .button) cell.selectionStyle = .none // 这里之前漏掉了return,必须补上! return cell } } // 不建议返回空UITableViewCell,这里直接抛出错误提醒问题 fatalError("Unexpected section or cell dequeue failure") }
4. 彻底修复CollectionView的cellForItemAt方法
现在不要再判断CollectionView的section了(因为每个CollectionView只有一个section),而是根据cellType来决定加载哪种cell:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let model = rowWithColors?[indexPath.item] else { fatalError("No data model available for collection view item") } switch cellType { case .normal: guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionviewcellid", for: indexPath) as? CollectionViewCell else { fatalError("Failed to dequeue CollectionViewCell") } let normalModel = model as! CollectionViewCellModel cell.imageView.image = normalModel.imageView cell.dicountAmountLabel.text = normalModel.dicountAmountLabel cell.dicountLabel.text = normalModel.dicountLabel cell.customerTypeLabel.text = normalModel.customerTypeLabel cell.dicountAmountLabel.textColor = .white cell.dicountLabel.textColor = .white cell.customerTypeLabel.textColor = .white return cell case .button: guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionviewcellButtonid", for: indexPath) as? CollectionViewCellButton else { fatalError("Failed to dequeue CollectionViewCellButton") } let buttonModel = model as! CollectionViewCellModelButton // 替换成你模型里的按钮标题字段,不要硬编码"Hi" cell.collectionButton.setTitle(buttonModel.buttonTitle, for: .normal) return cell } }
小提示:如果怕强制类型转换(
as!)崩溃,可以改成guard let的安全转换,比如guard let normalModel = model as? CollectionViewCellModel else { ... },更稳妥。
5. 可选优化:模型层抽象(让代码更优雅)
如果想彻底摆脱类型转换的麻烦,可以让两个模型遵守同一个配置协议:
protocol CollectionCellConfigurable { func configure(cell: UICollectionViewCell) } extension CollectionViewCellModel: CollectionCellConfigurable { func configure(cell: UICollectionViewCell) { guard let normalCell = cell as? CollectionViewCell else { return } normalCell.imageView.image = self.imageView normalCell.dicountAmountLabel.text = self.dicountAmountLabel // 其他配置项... } } extension CollectionViewCellModelButton: CollectionCellConfigurable { func configure(cell: UICollectionViewCell) { guard let buttonCell = cell as? CollectionViewCellButton else { return } buttonCell.collectionButton.setTitle(self.buttonTitle, for: .normal) } }
然后CollectionView的cellForItemAt可以简化成:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let model = rowWithColors?[indexPath.item] as? CollectionCellConfigurable else { fatalError("Model does not conform to CollectionCellConfigurable") } let reuseID = cellType == .normal ? "collectionviewcellid" : "collectionviewcellButtonid" let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseID, for: indexPath) model.configure(cell: cell) return cell }
做完这些修改后再运行代码:
- UITableView的Section 0会加载普通的
CollectionViewCell; - UITableView的Section 1会加载带按钮的
CollectionViewCellButton; - 不会再出现错误的分支跳转,运行时崩溃也会消失。
内容的提问来源于stack exchange,提问作者getc

