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

UICollectionViewCell嵌套UITableView:滚动保留+非可选行触摸透传需求

可行实现方案

核心思路是区分处理滚动与点击事件:让UITableView保留滚动能力,同时在点击不可选行时,不拦截事件,让其向上透传给外层UICollectionView。具体实现如下:

1. 自定义UITableView拦截点击手势

自定义一个继承自UITableView的子类,通过重写gestureRecognizerShouldBegin(_:)方法,对点击手势(UITapGestureRecognizer)做针对性拦截:

class NestedTableView: UITableView {
    // 闭包:传入indexPath,返回对应行是否可选
    var isRowSelectable: ((IndexPath) -> Bool)?
    
    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) {
        // 仅处理点击手势,滚动手势(如UIPan)正常放行
        guard let tapGesture = gestureRecognizer as? UITapGestureRecognizer else {
            return super.gestureRecognizerShouldBegin(gestureRecognizer)
        }
        
        // 获取点击位置对应的行
        let tapPos = tapGesture.location(in: self)
        if let indexPath = indexPathForRow(at: tapPos) {
            // 如果该行不可选,阻止点击手势生效,事件会向上传递给外层CollectionView
            if let selectable = isRowSelectable?(indexPath), !selectable {
                return false
            }
        }
        
        return super.gestureRecognizerShouldBegin(gestureRecognizer)
    }
}

2. 在CollectionViewCell中配置自定义TableView

将CollectionViewCell中的普通UITableView替换为上面的自定义类,并绑定数据与可选性判断逻辑:

class CustomCollectionCell: UICollectionViewCell {
    private let tableView = NestedTableView()
    var dataList: [RowDataModel] = [] {
        didSet { tableView.reloadData() }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setupTableView()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        setupTableView()
    }
    
    private func setupTableView() {
        tableView.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(tableView)
        
        NSLayoutConstraint.activate([
            tableView.topAnchor.constraint(equalTo: contentView.topAnchor),
            tableView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            tableView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            tableView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)
        ])
        
        tableView.dataSource = self
        tableView.delegate = self
        
        // 绑定行可选性判断逻辑
        tableView.isRowSelectable = { [weak self] indexPath in
            guard let self = self else { return false }
            return self.dataList[indexPath.row].isSelectable
        }
    }
}

// UITableView数据源与代理实现
extension CustomCollectionCell: UITableViewDataSource, UITableViewDelegate {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return dataList.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "RowCell", for: indexPath)
        cell.textLabel?.text = dataList[indexPath.row].title
        return cell
    }
    
    // 双重保险:确保不可选行不会被选中
    func tableView(_ tableView: UITableView, willSelectRowAt indexPath: IndexPath) -> IndexPath? {
        return dataList[indexPath.row].isSelectable ? indexPath : nil
    }
}

方案说明

  • 滚动能力保留:仅拦截点击手势,滚动相关的UIPanGestureRecognizer不受影响,TableView始终可以垂直滚动。
  • 行可选性配置:通过isRowSelectable闭包,可根据数据模型动态控制每一行是否可选。
  • 事件透传实现:当点击不可选行时,TableView放弃处理点击手势,事件会沿着响应链向上传递,最终被外层UICollectionView捕获,触发其代理方法(如collectionView(_:didSelectItemAt:))。

注意:确保UICollectionViewCell和NestedTableView的isUserInteractionEnabled属性保持默认的true,否则会影响事件传递。

内容的提问来源于stack exchange,提问作者D. Mika

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 05:17:02