iOS Swift:UIScrollView嵌套横竖UICollectionView滚动卡顿求助
解决嵌套UICollectionView在UIScrollView中的滚动卡顿与加载异常问题
这个坑我之前帮不少开发者踩过,核心问题是UIScrollView和嵌套的垂直UICollectionView之间的滚动事件冲突,再加上willDisplayCell的加载逻辑没处理好,导致滚动不流畅甚至触发重复API请求。下面给你几个针对性的解决方案,按优先级排序:
1. 优先用Compositional Layout替代嵌套结构(最推荐)
其实完全不需要用UIScrollView嵌套两个CollectionView,iOS 13+的UICollectionView Compositional Layout可以轻松实现“顶部水平滚动+底部垂直滚动”的布局,从根源上避免滚动冲突。
示例代码如下:
// 构建水平滚动的section let horizontalItem = NSCollectionLayoutItem( layoutSize: NSCollectionLayoutSize( widthDimension: .absolute(120), heightDimension: .absolute(120) ) ) horizontalItem.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8) let horizontalGroup = NSCollectionLayoutGroup.horizontal( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(136) ), subitems: [horizontalItem] ) let horizontalSection = NSCollectionLayoutSection(group: horizontalGroup) horizontalSection.orthogonalScrollingBehavior = .continuous // 开启水平滚动 // 构建垂直滚动的section let verticalItem = NSCollectionLayoutItem( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(80) ) ) verticalItem.contentInsets = NSDirectionalEdgeInsets(top: 4, leading: 8, bottom: 4, trailing: 8) let verticalGroup = NSCollectionLayoutGroup.vertical( layoutSize: NSCollectionLayoutSize( widthDimension: .fractionalWidth(1.0), heightDimension: .absolute(88) ), subitems: [verticalItem] ) let verticalSection = NSCollectionLayoutSection(group: verticalGroup) // 组合两个section let layout = UICollectionViewCompositionalLayout { sectionIndex, _ in return sectionIndex == 0 ? horizontalSection : verticalSection } // 初始化CollectionView let mainCollectionView = UICollectionView(frame: view.bounds, collectionViewLayout: layout) mainCollectionView.dataSource = self // 注册cell... view.addSubview(mainCollectionView)
这种方式用一个CollectionView就能实现需求,没有滚动冲突,性能也更优,还能利用系统的预加载优化。
2. 解决滚动事件冲突(如果必须保留嵌套结构)
如果因为兼容性或其他原因必须用UIScrollView嵌套,那需要让垂直UICollectionView优先响应垂直滚动事件,不让父UIScrollView抢。可以自定义垂直CollectionView:
class PriorityVerticalCollectionView: UICollectionView { override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { // 判断是否是垂直拖动手势 if let panGesture = gestureRecognizer as? UIPanGestureRecognizer { let translation = panGesture.translation(in: superview) // 垂直拖动距离大于水平时,自己响应手势,阻止父ScrollView处理 return abs(translation.y) > abs(translation.x) } return super.gestureRecognizerShouldBegin(gestureRecognizer) } }
把你的垂直UICollectionView替换成这个自定义类,就能解决滚动争抢的问题。
3. 优化willDisplayCell的加载逻辑,避免重复触发
滚动卡顿的另一个原因可能是频繁触发API请求,所以必须加一个加载状态锁,并且精准判断触发时机:
// 全局变量标记是否正在加载 private var isLoadingMore = false private let loadingThreshold = 2 // 提前倒数第2个cell时触发加载,避免用户等待 func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { guard !isLoadingMore else { return } let totalItemsInSection = collectionView.numberOfItems(inSection: indexPath.section) // 确保是当前section的倒数第N个cell才触发 if indexPath.item == totalItemsInSection - loadingThreshold { isLoadingMore = true // 调用API加载更多,注意异步处理 fetchMoreData { [weak self] newData in DispatchQueue.main.async { self?.dataArray.append(contentsOf: newData) self?.collectionView.reloadData() self?.isLoadingMore = false } } } }
另外,建议给垂直CollectionView添加一个加载footer,这样用户能直观看到加载状态,也能避免误判滚动到底部。
4. 额外性能优化
- 确保所有cell的布局约束是高效的,避免在
cellForItemAt里做耗时操作; - 开启CollectionView的
prefetchingEnabled(默认开启),让系统提前预加载cell; - API请求要做好缓存和去重,避免重复请求相同数据;
- 检查UIScrollView的
contentSize是否正确,不要让子视图的布局超出ScrollView的范围导致额外的滚动计算。
内容的提问来源于stack exchange,提问作者K. Mitra
相关产品推荐
相关产品推荐

