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

SwiftUI滚动视图中Gesture的onEnded未触发问题解决

解决SwiftUI滚动视图与卡片拖拽手势冲突问题

你的问题核心是ScrollView的垂直滚动手势与卡片的拖拽手势发生了冲突——当用户垂直滚动时,卡片的DragGesture被误触发,而由于垂直位移不满足卡片切换的水平阈值,onEnded里没有重置offset,导致卡片停留在偏移位置。

可以通过以下两个关键修改解决:

1. 区分拖拽方向,只响应水平拖拽

在DragGesture的onChanged回调中,先判断拖拽的主要方向:如果垂直位移的绝对值大于水平位移,说明用户是想滚动ScrollView,此时不更新卡片的offset,直接让ScrollView处理手势。

2. 强制手势结束时重置偏移

无论拖拽是否触发卡片切换,只要手势结束,都要确保offset被重置为.zero,避免卡片停留在异常位置。

修改后的完整代码

import SwiftUI

struct StackedCardsScrollView: View {
    @State var cards: [String] = ["1", "2", "3", "4", "5"]
    @State var offset: CGSize = .zero
    
    var body: some View {
        ScrollView {
            Color.blue.frame(height: 200)
            LazyVStack {
                ZStack {
                    ForEach(cards.indices, id: \.self) { index in
                        Color.red.frame(width: 120, height: 90)
                            .cornerRadius(15)
                            .overlay(
                                RoundedRectangle(cornerRadius: 15)
                                    .stroke(.black, lineWidth: 1.0)
                            )
                            .overlay(Text("\(index)"))
                            .onTapGesture {
                                UIImpactFeedbackGenerator(style: .light).impactOccurred()
                            }
                            .offset(x: Double(((cards.count - 1) - index) * 3))
                            .offset(x: index == (cards.count - 1) ? offset.width : 0.0, y: index == (cards.count - 1) ? offset.height : 0.0)
                            .highPriorityGesture(
                                DragGesture()
                                    .onChanged { value in
                                        guard cards.count > 1 else { return }
                                        // 只响应水平方向的拖拽
                                        if abs(value.translation.width) > abs(value.translation.height) {
                                            offset = value.translation
                                        } else {
                                            // 垂直拖拽时不更新offset,交给ScrollView处理
                                            offset = .zero
                                        }
                                    }
                                    .onEnded { value in
                                        guard cards.count > 1 else { return }
                                        withAnimation(.bouncy(duration: 0.3)) {
                                            if abs(value.translation.width) > 45.0 {
                                                ended()
                                            } else {
                                                offset = .zero
                                            }
                                        }
                                    }
                            )
                    }
                }
            }
            Color.orange.frame(height: 1000).padding(.top, 100)
            Color.green.frame(height: 1000)
            Color.gray.frame(height: 1000)
        }
    }
    
    func ended() {
        withAnimation(.bouncy(duration: 0.3)) {
            let element = cards.removeLast()
            cards.insert(element, at: 0)
            offset = .zero
        }
    }
}

#Preview {
    StackedCardsScrollView()
}

关键改动说明

  • 在onChanged中增加了方向判断:通过比较拖拽的水平/垂直位移绝对值,只在水平拖拽时更新卡片offset,垂直拖拽时直接重置offset,避免干扰ScrollView滚动。
  • 统一在onEnded中用withAnimation包裹offset重置逻辑,确保手势结束后卡片总能回到原位(除非触发了切换)。
  • 优化了视图命名(将tetfqwe改为StackedCardsScrollView),提升代码可读性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 08:22:47