横向滚动UICollectionView时,如何确定对应的UITableView Cell行?
解决UITableView嵌套UICollectionView时获取对应行号的问题
你遇到的问题根源很明确:用全局变量nowRow记录UITableView行号的方式行不通——UITableView的Cell是会复用的,当你滚动列表时,后续创建的Cell会覆盖nowRow的值,导致之前Cell里的UICollectionView调用数据源方法时,拿到的nowRow已经不是它对应的行号了,自然会在访问posts[nowRow].imageUrls时出现数组越界或取错数据的报错。
下面给你两种实用的解决方案,推荐第二种,更符合代码解耦的设计思路:
方案一:通过UICollectionView的Tag传递行号
这种方式改动小,能快速修复问题:
- 在UITableView的
cellForRowAt方法里,把当前行号赋值给UICollectionView的tag:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "DetailMutiTableViewCell", for: indexPath) as! DetailMutiTableViewCell // 将当前行号设为collectionView的tag cell.collectionView1.tag = indexPath.row cell.post = posts[indexPath.row] cell.collectionView1.delegate = self cell.collectionView1.dataSource = self cell.collectionView1.reloadData() return cell }
- 在UICollectionView的数据源方法里,通过
collectionView.tag拿到对应的行号:
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { let row = collectionView.tag guard row < posts.count else { return 0 } // 增加安全判断,避免数组越界 return posts[row].imageUrls.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MultiImagesCollectionViewCell", for: indexPath) as! MultiImagesCollectionViewCell let row = collectionView.tag guard row < posts.count, indexPath.item < posts[row].imageUrls.count else { // 安全处理,防止报错 cell.imageView1.image = nil return cell } let photoUrlString = posts[row].imageUrls[indexPath.item] let photoUrl = URL(string: photoUrlString) cell.imageView1.sd_setImage(with: photoUrl) return cell }
这种方法能快速解决问题,但缺点是ViewController需要同时处理两个组件的数据源和代理,耦合度较高。
方案二:让UITableViewCell独立管理UICollectionView(推荐)
把UICollectionView的数据源和代理逻辑放到对应的UITableViewCell内部,让每个Cell自己负责内部CollectionView的展示,这样ViewController的逻辑会更清晰,也彻底避免了行号混乱的问题:
- 修改
DetailMutiTableViewCell,让它遵守UICollectionView的数据源和代理协议,独立持有自己的post数据:
class DetailMutiTableViewCell: UITableViewCell, UICollectionViewDataSource, UICollectionViewDelegate { @IBOutlet weak var collectionView1: UICollectionView! var post: Post? { didSet { // 当post数据更新时,自动刷新collectionView collectionView1.reloadData() } } override func awakeFromNib() { super.awakeFromNib() // 初始化collectionView的数据源和代理为当前Cell collectionView1.dataSource = self collectionView1.delegate = self // 提前注册CollectionViewCell(如果没在xib里配置的话) collectionView1.register(UINib(nibName: "MultiImagesCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "MultiImagesCollectionViewCell") } // MARK: - UICollectionView 数据源与代理 func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return post?.imageUrls.count ?? 0 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MultiImagesCollectionViewCell", for: indexPath) as! MultiImagesCollectionViewCell guard let post = post, indexPath.item < post.imageUrls.count else { cell.imageView1.image = nil return cell } let photoUrlString = post.imageUrls[indexPath.item] let photoUrl = URL(string: photoUrlString) cell.imageView1.sd_setImage(with: photoUrl) return cell } // 如果需要监听CollectionView的点击事件,可以通过闭包回调给ViewController var didSelectImage: ((Int) -> Void)? func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { didSelectImage?(indexPath.item) } }
- 修改Home2ViewController里的
cellForRowAt方法,只需给Cell设置数据即可,无需再处理CollectionView的代理:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "DetailMutiTableViewCell", for: indexPath) as! DetailMutiTableViewCell cell.post = posts[indexPath.row] // 可选:监听CollectionView的点击事件 cell.didSelectImage = { [weak self] imageIndex in guard let self = self else { return } let targetImageUrl = self.posts[indexPath.row].imageUrls[imageIndex] // 这里处理图片点击后的逻辑,比如跳转预览页 } return cell }
这样每个UITableViewCell都独立管理自己的UICollectionView,完全不受Cell复用的影响,代码结构更清晰,后期维护也更方便。
内容的提问来源于stack exchange,提问作者kou
相关产品推荐
相关产品推荐

