如何实现Airbnb式横向集合视图(滑动时自动选中索引)
问题修复:仿Airbnb滚动联动效果异常
我参考Airbnb的滚动联动逻辑,在func scrollViewDidScroll(_ scrollView: UIScrollView)中实现了代码,但运行效果不符合预期。
原代码:
func scrollViewDidScroll(_ scrollView: UIScrollView) { let principal = scrollView.contentSize.width - scrollView.frame.width let progress = scrollView.contentOffset.x / principal var progresss = max(0, min(1, progress)) progressBar.setProgress(Float(progresss), animated: true) new = Int((CGFloat(arrayTagBasedFilter.count)) * progresss) new = Int(max(0, min(arrayTagBasedFilter.count-1, new))) let newIndex = IndexPath(item: new, section: 0) if let selectedIndexForFilter = selectedIndexForFilter { if let cell = collectionViewFilter.cellForItem(at: selectedIndexForFilter) as? BoostFilterCVCell { cell.lblTitle.textColor = .black.withAlphaComponent(0.5) } } if let cell = collectionViewFilter.cellForItem(at: newIndex) as? BoostFilterCVCell { selectedIndexForFilter = newIndex cell.lblTitle.textColor = .black progressView.frame = CGRect(origin: CGPoint(x: cell.frame.origin.x, y: collectionViewFilter.frame.origin.y - 50), size: CGSize(width: cell.frame.width, height: 1)) } }
问题分析与修复方案
1. 修复除以0崩溃问题
当主滚动视图内容宽度小于等于自身宽度时,principal会为0,直接计算progress会触发崩溃,先加判断:
guard scrollView.contentSize.width > scrollView.frame.width else { progressBar.setProgress(0, animated: false) return } let principal = scrollView.contentSize.width - scrollView.frame.width
2. 精准匹配滚动对应的Filter项
原代码用整体进度乘以数组长度计算选中项,忽略了Filter项宽度可能不一致的情况,改成根据滚动偏移量匹配页面索引:
// 主视图为等宽分页滚动的情况 let pageWidth = scrollView.frame.width let currentPage = Int(round(scrollView.contentOffset.x / pageWidth)) let new = max(0, min(arrayTagBasedFilter.count - 1, currentPage)) let newIndex = IndexPath(item: new, section: 0)
如果是非等宽页面,需要提前计算每个页面的累计宽度区间,再判断contentOffset.x落在哪个区间来确定索引。
3. 优化选中状态更新逻辑
直接在scrollViewDidScroll中调用cellForItem会因为cell不可见返回nil,导致状态更新失效。改成通过刷新collectionView项来更新:
// 记录旧选中项,更新新选中项 let oldSelectedIndex = selectedIndexForFilter selectedIndexForFilter = newIndex // 刷新旧项和新项的状态 if let oldIndex = oldSelectedIndex { collectionViewFilter.reloadItems(at: [oldIndex]) } collectionViewFilter.reloadItems(at: [newIndex])
然后在cellForItemAt方法中统一处理文字颜色:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "BoostFilterCVCell", for: indexPath) as! BoostFilterCVCell let isSelected = indexPath == selectedIndexForFilter cell.lblTitle.textColor = isSelected ? .black : .black.withAlphaComponent(0.5) return cell }
4. 修正进度条位置计算
原代码未考虑Filter列表自身滚动的情况,改成将cell坐标转换到父视图坐标系:
if let cell = collectionViewFilter.cellForItem(at: newIndex) as? BoostFilterCVCell { let cellFrameInSuperview = collectionViewFilter.convert(cell.frame, to: collectionViewFilter.superview) progressView.frame = CGRect( x: cellFrameInSuperview.origin.x, y: collectionViewFilter.frame.origin.y - 50, width: cellFrameInSuperview.width, height: 1 ) }
5. 优化滚动性能
scrollViewDidScroll会被高频调用,关闭进度条动画减少开销:
progressBar.setProgress(Float(progresss), animated: false)
内容的提问来源于stack exchange,提问作者Patel
相关产品推荐
相关产品推荐

