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

SwiftUI Horizontal ScrollView实现单元格滑动吸附至HStack前缘的方法

实现Horizontal ScrollView的单元格滑动吸附切换功能

需求:我有一个包含固定尺寸内容单元格的Horizontal ScrollView,想要实现滑动切换上一个/下一个单元格的功能,类似Tinder的滑动交互,但所有单元格保留在ScrollView内,且单元格能「吸附」到HStack的前缘。

原代码:

struct ContentView: View {
    let content = ["Hello", "World", "How", "Are", "You"]
    
    var body: some View {
        ScrollView(.horizontal) {
            HStack {
                ForEach(content, id: \.self) { str in
                    Text(str)
                        .frame(width: 200, height: 200, alignment: .center)
                        .background(Color.green)
                }
            }
        }
    }
}

解决方案

方案1:iOS 17+ 原生分页吸附(简洁实现)

利用SwiftUI 4.0新增的滚动目标API,直接实现单元格吸附和分页滑动:

struct ContentView: View {
    let content = ["Hello", "World", "How", "Are", "You"]
    @State private var currentIndex = 0
    
    var body: some View {
        ScrollView(.horizontal) {
            HStack(spacing: 0) {
                ForEach(content.indices, id: \.self) { index in
                    Text(content[index])
                        .frame(width: 200, height: 200, alignment: .center)
                        .background(currentIndex == index ? Color.green : Color.gray)
                        .id(index)
                }
            }
            .scrollTargetLayout()
        }
        .scrollTargetBehavior(.paging)
        .gesture(
            DragGesture()
                .onEnded { value in
                    let threshold: CGFloat = 50
                    if value.translation.width < -threshold {
                        currentIndex = min(currentIndex + 1, content.count - 1)
                    } else if value.translation.width > threshold {
                        currentIndex = max(currentIndex - 1, 0)
                    }
                }
        )
    }
}

方案2:兼容iOS 13+ 的ScrollViewReader实现

如果需要支持更低版本系统,使用ScrollViewReader手动控制滚动位置,结合拖拽手势计算目标索引:

struct ContentView: View {
    let content = ["Hello", "World", "How", "Are", "You"]
    @State private var currentIndex = 0
    let cellWidth: CGFloat = 200
    
    var body: some View {
        ScrollView(.horizontal) {
            ScrollViewReader { proxy in
                HStack(spacing: 0) {
                    ForEach(content.indices, id: \.self) { index in
                        Text(content[index])
                            .frame(width: cellWidth, height: 200, alignment: .center)
                            .background(currentIndex == index ? Color.green : Color.gray)
                            .id(index)
                    }
                }
                .onAppear {
                    proxy.scrollTo(0, anchor: .leading)
                }
            }
        }
        .gesture(
            DragGesture()
                .onEnded { value in
                    let translation = value.translation.width
                    if translation < -50 {
                        currentIndex = min(currentIndex + 1, content.count - 1)
                    } else if translation > 50 {
                        currentIndex = max(currentIndex - 1, 0)
                    }
                    withAnimation(.easeInOut) {
                        proxy.scrollTo(currentIndex, anchor: .leading)
                    }
                }
        )
    }
}

关键说明

  • 单元格吸附:iOS17+用scrollTargetBehavior(.paging)自动实现吸附;低版本通过ScrollViewReader的scrollTo(_:anchor:)方法,强制滚动到单元格前缘。
  • 拖拽切换:监听DragGesture的拖拽方向和距离,设置阈值判断是否触发切换,避免误操作。
  • 状态管理:用@State变量currentIndex记录当前显示的单元格索引,可用于高亮当前单元格或执行其他业务逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 08:08:21