如何禁用UICollectionView但保留其Header视图的正常交互
问题说明
需求为:在纵向滚动的外层UICollectionView中禁用cell选中能力,同时保留Section Header中嵌套的横向滚动UICollectionView的正常交互。
尝试的两种错误实现均会阻断Header交互:
- 全局关闭外层CollectionView用户交互:
CollectionView.isUserInteractionEnabled = false
- 仅关闭选中属性:
CollectionView.allowsSelection = false
使用的Header自定义代码如下:
final class HeaderCollectionReusableView: UICollectionReusableView { override init(frame: CGRect) { super.init(frame: .zero) addSubView() setupLayout() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } static let headerIdentifier = "HeaderCollectionReusableView" private func addSubView() { addSubview(collectionView) } public lazy var collectionView: UICollectionView = { var flowLayout = UICollectionViewFlowLayout() flowLayout.scrollDirection = .horizontal flowLayout.itemSize = CGSize(width: UIScreen.main.bounds.size.width / 3.5, height: UIScreen.main.bounds.size.width / 3) flowLayout.minimumLineSpacing = 12 flowLayout.sectionInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) var view = UICollectionView(frame: .zero, collectionViewLayout: flowLayout) view.register(cell: CollectionViewCell.self) view.backgroundColor = .clear view.showsHorizontalScrollIndicator = false view.showsVerticalScrollIndicator = false view.contentInsetAdjustmentBehavior = .automatic view.translatesAutoresizingMaskIntoConstraints = false return view }() private func setupLayout() { NSLayoutConstraint.activate([ collectionView.topAnchor.constraint(equalTo: topAnchor), collectionView.leadingAnchor.constraint(equalTo: leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: trailingAnchor), collectionView.heightAnchor.constraint(equalToConstant: UIScreen.main.bounds.size.width / 3), ]) } }
问题根因
- 直接设置
isUserInteractionEnabled = false会让当前视图和其所有子视图统一禁用触摸响应,Header作为外层CollectionView的子视图,自然无法接收交互事件,该方案原理上就不符合需求。 - 仅设置
allowsSelection = false只会禁止cell的选中回调,本身不会影响SupplementaryView(Header/Footer)的交互,出现交互阻断的核心原因是外层纵向CollectionView的滚动手势和Header内横向CollectionView的滚动手势存在冲突,外层手势抢占了事件。
正确实现方案
- 不要修改外层CollectionView的
isUserInteractionEnabled属性,保持默认值true。 - 配置外层CollectionView的选中相关属性,禁用cell选中能力:
// 禁用单选中 outerCollectionView.allowsSelection = false // 若开启过多选,同步关闭 outerCollectionView.allowsMultipleSelection = false
- 如果需要彻底屏蔽外层所有cell的点击响应(包括cell上自定义控件的点击),实现
UICollectionViewDelegate的以下方法,直接禁止cell高亮,事件不会被cell拦截,也不会影响Header:
func collectionView(_ collectionView: UICollectionView, shouldHighlightItemAt indexPath: IndexPath) -> Bool { return false }
- 处理嵌套滚动的手势冲突,让外层纵向滚动手势在触摸区域位于Header内横向CollectionView上时,不抢占事件。首先让持有外层CollectionView的类实现
UIGestureRecognizerDelegate,然后添加以下代码:
// 给外层CollectionView的滚动手势设置代理 outerCollectionView.panGestureRecognizer.delegate = self func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { // 获取当前触摸点在外层CollectionView上的位置 let touchPoint = gestureRecognizer.location(in: outerCollectionView) // 判断触摸点是否在Header区域 guard let headerIndexPath = outerCollectionView.indexPathForSupplementaryElement( ofKind: UICollectionView.elementKindSectionHeader, at: touchPoint ), let headerView = outerCollectionView.supplementaryView( forElementKind: UICollectionView.elementKindSectionHeader, at: headerIndexPath ) as? HeaderCollectionReusableView else { return false } // 判断触摸点是否在Header内的横向CollectionView上 let pointInHeader = gestureRecognizer.location(in: headerView) return headerView.collectionView.point(inside: pointInHeader, with: nil) }
- 如果业务不需要外层CollectionView的纵向滚动能力,可额外添加
outerCollectionView.isScrollEnabled = false,该属性不会影响子视图的交互。
内容的提问来源于stack exchange,提问作者Dmitry
相关产品推荐
相关产品推荐

