如何为代码注册的自定义UICollectionViewCell填充API数据(含嵌套CollectionView)
嘿,这个场景太常见了——当你想把Cell的逻辑从ViewController里解耦出来时,总会遇到这种困惑。我来一步步给你拆解怎么搞定:
核心思路:给每个Cell定义专属数据模型+配置方法
核心原则是:ViewController只负责数据请求、模型转换和分发,Cell自己管理内部的UI更新(包括嵌套的子CollectionView)。
首先给不同类型的Cell定义对应的数据模型,确保每个Cell拿到的数据都是它直接能用的:
// 普通Cell的数据模型 struct NormalItemModel { let title: String let imageURL: String } // 带嵌套CollectionView的Cell的数据模型(包含子列表的数据源) struct NestedSectionModel { let sectionTitle: String let subItems: [SubItemModel] } // 子CollectionView的Item模型 struct SubItemModel { let name: String let icon: String }
接下来给每个Cell添加配置方法,把数据传进去,同时让Cell自己处理内部逻辑:
// 普通Cell class NormalItemCell: UICollectionViewCell { @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var iconImageView: UIImageView! func configure(with model: NormalItemModel) { titleLabel.text = model.title // 这里可以用图片加载库异步加载图片,比如Kingfisher/SDWebImage iconImageView.kf.setImage(with: URL(string: model.imageURL)) } } // 带嵌套CollectionView的Cell class NestedCollectionCell: UICollectionViewCell { @IBOutlet weak var sectionTitleLabel: UILabel! @IBOutlet weak var subCollectionView: UICollectionView! private var subItems: [SubItemModel] = [] // 可选:添加闭包回调,把子Item的点击事件传递回VC var subItemTapHandler: ((SubItemModel) -> Void)? override func awakeFromNib() { super.awakeFromNib() // 提前注册子CollectionView的Cell subCollectionView.register(SubItemCell.self, forCellWithReuseIdentifier: "SubItemCell") subCollectionView.dataSource = self subCollectionView.delegate = self } func configure(with model: NestedSectionModel) { sectionTitleLabel.text = model.sectionTitle self.subItems = model.subItems subCollectionView.reloadData() // 数据更新后刷新子列表 } } // 嵌套Cell的子CollectionView数据源/代理扩展(完全放在Cell内部) extension NestedCollectionCell: UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return subItems.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "SubItemCell", for: indexPath) as! SubItemCell cell.configure(with: subItems[indexPath.item]) return cell } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { // 触发回调,把点击事件传给VC subItemTapHandler?(subItems[indexPath.item]) } // 自定义子Item的布局 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: 80, height: 100) } }
在ViewController中统一处理数据分发
VC里只需要做三件事:请求数据、把原始数据转换成Cell需要的模型、在cellForItemAt里给对应Cell传数据:
class MainViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { @IBOutlet weak var mainCollectionView: UICollectionView! // 用枚举统一管理Cell类型,避免Any类型的安全风险 enum CellModel { case normal(NormalItemModel) case nested(NestedSectionModel) } private var cellModels: [CellModel] = [] override func viewDidLoad() { super.viewDidLoad() // 注册所有自定义Cell mainCollectionView.register(NormalItemCell.self, forCellWithReuseIdentifier: "NormalItemCell") mainCollectionView.register(NestedCollectionCell.self, forCellWithReuseIdentifier: "NestedCollectionCell") mainCollectionView.dataSource = self mainCollectionView.delegate = self // 发起API请求 fetchData() } private func fetchData() { // 这里是你的API请求逻辑 APIManager.fetchMainData { [weak self] result in switch result { case .success(let rawData): self?.parseRawDataToCellModels(rawData: rawData) self?.mainCollectionView.reloadData() case .failure(let error): print("请求失败:\(error.localizedDescription)") } } } private func parseRawDataToCellModels(rawData: MainData) { // 把接口返回的原始数据转换成Cell需要的模型 for section in rawData.sections { if section.type == .normal { let normalModels = section.items.map { NormalItemModel(title: $0.title, imageURL: $0.imageURL) } normalModels.forEach { self.cellModels.append(.normal($0)) } } else if section.type == .nested { let subItems = section.subItems.map { SubItemModel(name: $0.name, icon: $0.iconURL) } let nestedModel = NestedSectionModel(sectionTitle: section.title, subItems: subItems) self.cellModels.append(.nested(nestedModel)) } } } // MARK: - UICollectionView 数据源 func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return cellModels.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { switch cellModels[indexPath.item] { case .normal(let model): let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NormalItemCell", for: indexPath) as! NormalItemCell cell.configure(with: model) return cell case .nested(let model): let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "NestedCollectionCell", for: indexPath) as! NestedCollectionCell cell.configure(with: model) // 设置子Item的点击回调 cell.subItemTapHandler = { [weak self] subModel in self?.handleSubItemTap(subModel: subModel) } return cell } } // MARK: - UICollectionView 布局 func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { switch cellModels[indexPath.item] { case .normal: return CGSize(width: collectionView.bounds.width, height: 100) case .nested: return CGSize(width: collectionView.bounds.width, height: 220) // 给嵌套列表留足够高度 } } // 处理子Item的点击事件 private func handleSubItemTap(subModel: SubItemModel) { print("点击了子Item:\(subModel.name)") // 这里可以做跳转详情等操作 } }
额外优化小建议
- 用
enum管理Cell类型比直接用Any更安全,避免类型转换错误; - 如果Cell有复杂的业务逻辑,可以把配置方法里的部分逻辑抽成私有方法,让代码更清晰;
- 嵌套CollectionView的高度如果需要动态计算,可以在
configure方法里根据子Item数量计算高度,再通过VC的布局代理返回。
内容的提问来源于stack exchange,提问作者kamenr
相关产品推荐
相关产品推荐

