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

如何实现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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 16:25:09