Swift 4 中如何在TableViewCell内通过代码创建CollectionView
嘿,这个需求我刚好做过,纯代码实现完全没问题,我一步步给你拆解,保证每个TableViewCell里的CollectionView数量能根据数据动态变化,完全不用碰Storyboard:
第一步:改造你的自定义TableViewCell
首先在你的CustomTableViewCell里,我们需要用数组来管理多个CollectionView,同时处理它们的布局、代理和数据源:
import UIKit class CustomTableViewCell: UITableViewCell { // 存放当前cell内的所有CollectionView private var collectionViews: [UICollectionView] = [] // 二维数组:外层对应每个CollectionView,内层是该CollectionView的数据源 var cellData: [[String]] = [] { didSet { // 数据更新时自动刷新所有CollectionView collectionViews.forEach { $0.reloadData() } } } // 自定义闭包,用来把CollectionView的点击事件转发给TableViewController(可选) var onCollectionItemSelected: ((_ collectionTag: Int, _ itemIndex: Int) -> Void)? override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { super.init(style: style, reuseIdentifier: reuseIdentifier) setupContentViewConstraints() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // 先给contentView设置基础约束,避免后续布局混乱 private func setupContentViewConstraints() { contentView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ contentView.topAnchor.constraint(equalTo: topAnchor), contentView.leadingAnchor.constraint(equalTo: leadingAnchor), contentView.trailingAnchor.constraint(equalTo: trailingAnchor), contentView.bottomAnchor.constraint(equalTo: bottomAnchor) ]) } // 核心方法:根据数据数量动态创建/刷新CollectionView func configureDynamicCollectionViews() { // 先清理旧的CollectionView,避免cell复用时重复添加 collectionViews.forEach { $0.removeFromSuperview() } collectionViews.removeAll() // 遍历数据源,有多少组数据就创建多少个CollectionView for (index, _) in cellData.enumerated() { // 创建流式布局,可根据需求调整item大小、滚动方向 let layout = UICollectionViewFlowLayout() layout.itemSize = CGSize(width: 80, height: 80) layout.minimumInteritemSpacing = 10 layout.minimumLineSpacing = 10 layout.scrollDirection = .horizontal // 改成.vertical就是垂直滚动 // 创建CollectionView let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.backgroundColor = .lightGray.withAlphaComponent(0.2) // 注册你的自定义CollectionViewCell collectionView.register(CustomCollectionViewCell.self, forCellWithReuseIdentifier: "CustomCollectionCell") // 设置代理和数据源,直接让当前cell承担 collectionView.delegate = self collectionView.dataSource = self // 给每个CollectionView打tag,方便区分是哪个 collectionView.tag = index contentView.addSubview(collectionView) collectionViews.append(collectionView) // 设置CollectionView的约束:垂直排列,左右边距10,每个高度80,间距10 NSLayoutConstraint.activate([ collectionView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10), collectionView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10), collectionView.heightAnchor.constraint(equalToConstant: 80), // 第一个CollectionView贴contentView顶部,后续的贴前一个的底部 collectionView.topAnchor.constraint(equalTo: index == 0 ? contentView.topAnchor : collectionViews[index-1].bottomAnchor, constant: 10) ]) // 最后一个CollectionView贴contentView底部 if index == cellData.count - 1 { NSLayoutConstraint.activate([ collectionView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10) ]) } } } } // 让TableViewCell遵守CollectionView的代理和数据源协议 extension CustomTableViewCell: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { // 根据tag获取对应CollectionView的数据源数量 return cellData[collectionView.tag].count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CustomCollectionCell", for: indexPath) as! CustomCollectionViewCell // 给CollectionViewCell设置内容(这里用文本举例,你可以换成自己的UI) cell.titleLabel.text = cellData[collectionView.tag][indexPath.item] cell.backgroundColor = .white return cell } // 处理CollectionView的点击事件,转发给TableViewController func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { onCollectionItemSelected?(collectionView.tag, indexPath.item) } }
第二步:完善你的自定义CollectionViewCell
这里是纯代码实现的CustomCollectionViewCell,你可以根据自己的需求调整UI:
class CustomCollectionViewCell: UICollectionViewCell { // 自定义标签,用来显示内容 let titleLabel: UILabel = { let label = UILabel() label.translatesAutoresizingMaskIntoConstraints = false label.textAlignment = .center label.font = UIFont.systemFont(ofSize: 14) label.textColor = .black return label }() override init(frame: CGRect) { super.init(frame: frame) setupUI() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupUI() { addSubview(titleLabel) NSLayoutConstraint.activate([ titleLabel.centerXAnchor.constraint(equalTo: centerXAnchor), titleLabel.centerYAnchor.constraint(equalTo: centerYAnchor), titleLabel.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor, constant: 5), titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor, constant: -5) ]) // 给cell加个圆角,看起来更美观 layer.cornerRadius = 8 layer.masksToBounds = true } }
第三步:在TableViewController里配置数据源和复用逻辑
最后在你的CustomTableViewController里,给每个TableViewCell传对应的数据,并处理复用:
import UIKit class CustomTableViewController: UITableViewController { // 模拟数据源:外层是每个TableViewCell的数据,内层是该cell内多个CollectionView的数据源 private let tableViewDataSource: [[[String]]] = [ // 第一个TableViewCell包含2个CollectionView [["苹果", "香蕉", "橙子"], ["可乐", "雪碧"]], // 第二个TableViewCell包含3个CollectionView [["红色"], ["正方形", "圆形", "三角形", "五角星"], ["周一", "周二"]], // 第三个TableViewCell包含1个CollectionView [["篮球", "足球", "排球", "羽毛球", "乒乓球"]] ] override func viewDidLoad() { super.viewDidLoad() // 注册自定义TableViewCell tableView.register(CustomTableViewCell.self, forCellReuseIdentifier: "CustomTableCell") // 开启自动计算cell高度,避免手动计算高度的麻烦 tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 250 // 给个预估高度,防止界面跳动 tableView.separatorStyle = .singleLine } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return tableViewDataSource.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableCell", for: indexPath) as! CustomTableViewCell // 给cell传对应的数据 cell.cellData = tableViewDataSource[indexPath.row] // 调用配置CollectionView的方法 cell.configureDynamicCollectionViews() // 设置点击事件的回调(可选) cell.onCollectionItemSelected = { [weak self] collectionTag, itemIndex in guard let self = self else { return } let selectedData = self.tableViewDataSource[indexPath.row][collectionTag][itemIndex] print("你点击了第\(indexPath.row+1)个TableViewCell里的第\(collectionTag+1)个CollectionView,第\(itemIndex+1)个item:\(selectedData)") // 这里可以添加你的业务逻辑,比如跳转页面、弹出弹窗等 } return cell } }
关键注意点
- Cell复用问题:每次调用
configureDynamicCollectionViews时,一定要先移除旧的CollectionView,否则复用cell时会重复添加,导致布局混乱。 - 自动高度:TableView开启
automaticDimension后,会根据cell内的CollectionView自动计算高度,不需要手动设置。 - 代理转发:如果需要在TableViewController处理CollectionView的交互,用闭包是最简洁的方式,避免复杂的代理嵌套。
这样整个流程就完成了,完全纯代码实现,每个TableViewCell里的CollectionView数量会根据你传入的cellData自动变化,完美满足你的需求!
内容的提问来源于stack exchange,提问作者Sam H
相关产品推荐
相关产品推荐

