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

iOS15/16下SwiftUI实现支持滚动吸附分页的自定义轮播

兼容iOS15/16的SwiftUI自定义轮播实现(无UIKit依赖)

可以实现,无需依赖UIKit或CollectionView,直接基于SwiftUI原生组件ScrollView+ScrollViewReader+GeometryReader组合,就能做到连续滚动+分页吸附,同时支持自定义元素间距,完美适配iOS15和16。

完整实现代码

import SwiftUI

struct CarouselView<Content: View>: View {
    let items: [Content]
    let itemSpacing: CGFloat
    let itemWidth: CGFloat
    @State private var currentIndex: Int = 0
    @State private var isScrolling: Bool = false
    
    init(
        items: [Content],
        itemSpacing: CGFloat = 16,
        itemWidth: CGFloat = UIScreen.main.bounds.width - 32
    ) {
        self.items = items
        self.itemSpacing = itemSpacing
        self.itemWidth = itemWidth
    }
    
    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            ScrollViewReader { proxy in
                HStack(spacing: itemSpacing) {
                    ForEach(0..<items.count, id: \.self) { index in
                        items[index]
                            .frame(width: itemWidth)
                            .cornerRadius(12)
                            .id(index)
                            .overlay(
                                GeometryReader { geo in
                                    Color.clear
                                        .onChange(of: geo.frame(in: .global).minX) { _ in
                                            guard isScrolling else { return }
                                            // 计算元素中心与屏幕中心的偏移,判断是否触发吸附
                                            let screenCenter = UIScreen.main.bounds.width / 2
                                            let itemCenter = geo.frame(in: .global).midX
                                            let offsetDiff = abs(itemCenter - screenCenter)
                                            
                                            if offsetDiff < itemWidth / 4 {
                                                currentIndex = index
                                            }
                                        }
                                }
                            )
                    }
                }
                .padding(.horizontal, (UIScreen.main.bounds.width - itemWidth) / 2)
                .onAppear {
                    proxy.scrollTo(0, anchor: .center)
                }
                .onChange(of: currentIndex) { newIndex in
                    withAnimation(.easeInOut(duration: 0.3)) {
                        proxy.scrollTo(newIndex, anchor: .center)
                    }
                }
                .gesture(
                    DragGesture()
                        .onChanged { _ in isScrolling = true }
                        .onEnded { _ in
                            isScrolling = false
                            withAnimation(.easeInOut(duration: 0.3)) {
                                proxy.scrollTo(currentIndex, anchor: .center)
                            }
                        }
                )
            }
        }
    }
}

// 使用示例
struct CarouselDemoView: View {
    let demoItems: [some View] = (1...5).map { index in
        Text("轮播项 \(index)")
            .font(.title)
            .frame(height: 200)
            .background(Color.blue.opacity(0.3))
            .cornerRadius(12)
    }
    
    var body: some View {
        VStack(spacing: 20) {
            Text("自定义轮播(兼容iOS15+)")
                .font(.headline)
            
            CarouselView(
                items: demoItems,
                itemSpacing: 12,
                itemWidth: UIScreen.main.bounds.width - 40
            )
            .frame(height: 220)
        }
        .padding()
    }
}

struct CarouselView_Previews: PreviewProvider {
    static var previews: some View {
        CarouselDemoView()
    }
}

核心实现逻辑

  • 自定义布局控制:通过HStack的spacing参数直接设置元素间距,配合左右padding实现元素居中显示,解决TabView只能单视图填充的局限。
  • 滚动状态管理:用DragGesture标记滚动状态,避免滚动过程中频繁触发吸附逻辑,保证滚动流畅性。
  • 分页吸附判断:利用GeometryReader获取元素的全局位置,计算元素中心与屏幕中心的偏移量,当偏移小于阈值时锁定当前索引,再通过ScrollViewReader.scrollTo()实现平滑吸附。
  • iOS版本兼容:ScrollViewReader支持iOS13+,onChange支持iOS14+,完全覆盖iOS15/16需求;若需适配iOS14,可将onChange替换为onReceive监听位置变化。

可扩展优化点

  • 自动轮播:添加Timer.publish定时更新currentIndex,视图消失时记得取消定时器。
  • 无限滚动:在HStack首尾添加重复元素,滚动到边界时切换索引,实现无缝循环效果。
  • 自定义吸附动画:调整withAnimation的时长参数,适配不同场景的流畅度需求。

内容的提问来源于stack exchange,提问作者Shawn Frank

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 23:52:45