You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

iOS Swift实现CollectionView类似跑马灯的平滑连续滚动(需索引值)

嘿,这个需求我之前做过类似的,用UICollectionView实现底部跑马灯的自动平滑滚动+实时获取当前item的index完全可行,下面给你一步步拆解实现思路和代码:

一、基础UICollectionView配置

首先得把CollectionView的布局和基础属性设置好,适配跑马灯的需求:

  • UICollectionViewFlowLayout作为布局,设置滚动方向为水平,item的高度和CollectionView的高度一致,宽度根据你的内容调整,间距设为0,保证item无缝排列。
  • 关闭滚动指示器(showsHorizontalScrollIndicator = false),如果不需要用户手动滚动,可以设置isScrollEnabled = false(如果允许手动,后续要处理自动滚动的恢复逻辑)。
二、实现“无限滚动”的小技巧

跑马灯需要看起来无限循环,直接滚动到末尾再跳回开头会有生硬的跳转,所以我们可以用数据源复制的方式:

  • 假设你的原始数据数组是originalData: [String],在numberOfItemsInSection里返回originalData.count * 3(复制3份足够实现无限滚动的视觉效果)。
  • cellForItemAt里,用indexPath.item % originalData.count来取对应的原始数据,这样不管滚动到哪个位置,都能循环取到数据。
  • 初始化的时候,让CollectionView滚动到中间位置(比如contentOffset.x = CGFloat(originalData.count) * itemWidth),这样一开始滚动不会很快就到边界。
三、自动平滑滚动的两种实现方式

根据你想要的滚动效果,有两种常用方案:

1. 连续平滑滚动(类似新闻 ticker 那种不停滚动)

CADisplayLink来驱动每帧滚动,精度更高:

var displayLink: CADisplayLink?
let scrollSpeed: CGFloat = 1.0 // 每帧滚动1pt,可调整

func setupAutoScroll() {
    displayLink = CADisplayLink(target: self, selector: #selector(updateScrollPosition))
    displayLink?.add(to: .current, forMode: .common)
}

@objc func updateScrollPosition() {
    let currentOffset = collectionView.contentOffset
    let newOffset = CGPoint(x: currentOffset.x + scrollSpeed, y: currentOffset.y)
    
    // 滚动到一定位置后重置,避免contentOffset无限增大
    let maxOffset = CGFloat(originalData.count) * itemWidth
    if newOffset.x >= maxOffset * 2 {
        collectionView.contentOffset = CGPoint(x: maxOffset, y: 0)
    } else {
        collectionView.contentOffset = newOffset
    }
    
    // 实时获取当前index
    let currentIndex = Int(newOffset.x / itemWidth) % originalData.count
    print("当前显示的item index: \(currentIndex)")
}

记得在页面消失时暂停displayLink:

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)
    displayLink?.isPaused = true
}

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    displayLink?.isPaused = false
}

2. 逐Item平滑滚动(每个item停留几秒再滚动到下一个)

如果需要每个item停留一段时间再滚动,用UIView.animate配合定时器:

var scrollTimer: Timer?
let itemStayDuration: TimeInterval = 3.0 // 每个item停留3秒
let scrollAnimationDuration: TimeInterval = 0.5 // 滚动到下一个item的动画时间

func setupItemScrollTimer() {
    scrollTimer = Timer.scheduledTimer(timeInterval: itemStayDuration + scrollAnimationDuration, target: self, selector: #selector(scrollToNextItem), userInfo: nil, repeats: true)
}

@objc func scrollToNextItem() {
    let currentOffset = collectionView.contentOffset
    let nextOffsetX = currentOffset.x + itemWidth
    
    UIView.animate(withDuration: scrollAnimationDuration) {
        self.collectionView.contentOffset = CGPoint(x: nextOffsetX, y: 0)
    } completion: { [weak self] _ in
        guard let self = self else { return }
        // 重置偏移量,实现无限循环
        if nextOffsetX >= CGFloat(self.originalData.count * 2) * self.itemWidth {
            self.collectionView.contentOffset = CGPoint(x: CGFloat(self.originalData.count) * self.itemWidth, y: 0)
        }
        
        // 获取当前显示的index
        let currentIndex = Int(self.collectionView.contentOffset.x / self.itemWidth) % self.originalData.count
        print("当前显示的item index: \(currentIndex)")
    }
}

同样要在页面前后台切换时暂停/恢复定时器。

四、实时获取当前滚动项的Index

除了上面代码里在滚动时计算,你也可以通过UIScrollViewDelegate的方法来监听:

extension YourViewController: UIScrollViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let currentOffsetX = scrollView.contentOffset.x
        let currentIndex = Int(currentOffsetX / itemWidth) % originalData.count
        // 这里可以把currentIndex用闭包或者通知传递给需要的地方
        print("实时滚动中的index: \(currentIndex)")
    }
}

注意要给collectionView设置delegate = self

一些注意事项
  • 计算itemWidth时,如果是自适应内容的cell,要提前计算好每个item的宽度,或者用systemLayoutSizeFitting来获取cell的真实宽度。
  • 如果允许用户手动滚动,要在scrollViewWillBeginDragging里暂停自动滚动,scrollViewDidEndDragging里恢复。
  • 当数据源为空时,要停止自动滚动的定时器/displayLink,避免崩溃。

内容的提问来源于stack exchange,提问作者Chetan Pote

火山引擎 最新活动