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

SwiftUI DragGesture设置offset触发无限循环的问题如何解决

问题原因

你出现无限循环的核心原因是DragGesture的value.location是相对于当前触发手势的蓝色视图自身的坐标系的坐标,而不是相对于父容器的全局坐标:

  • 你每次把dragOffset设为value.location,会立刻改变Toolbar的offset位置
  • Toolbar位置变了之后,蓝色视图的坐标系也跟着移动,导致系统重新计算出的value.location立刻变回0附近
  • 这又触发onChanged回调修改offset,两个数值来回跳转就形成了无限循环
修复方案

修改DragGesture的处理逻辑,用拖拽位移增量来计算偏移,避免和视图位置形成循环依赖:

  1. 新增状态变量记录拖拽开始前的初始偏移量,避免拖拽起始位置跳变
  2. 使用value.translation(拖拽的位移增量,不受当前视图位置影响)计算最终偏移
  3. 可选增加边界判断,限制视图不被拖出屏幕范围

修复后的完整代码如下:

extension CGPoint {
    func toSize() -> CGSize { .init(width:x, height:y) }
}

struct ContentView: View {
    @State var offset: CGSize = .zero
    // 新增:记录拖拽开始时的初始偏移
    @State var startOffset: CGSize = .zero
    var body: some View {
        ZStack(alignment: .topLeading) {
            Toolbar(dragOffset: $offset, startOffset: $startOffset)
                .offset(offset)
            Text(verbatim: "Offset: \(offset)")
                .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
    }
}

struct Toolbar: View {
    @Binding var dragOffset: CGSize
    @Binding var startOffset: CGSize
    var body: some View {
        HStack {
            Color.blue.frame(width: 40, height: 40)
                .gesture(drag)
            Color.green.frame(width: 40, height: 40)
            Color.red.frame(width: 40, height: 40)
        }
    }
    var drag: some Gesture {
        DragGesture()
            .onBegin { _ in
                // 拖拽开始时先保存当前偏移作为基准值
                startOffset = dragOffset
            }
            .onChanged { value in
                // 基准偏移 + 拖拽位移 = 当前最终偏移
                dragOffset = CGSize(
                    width: startOffset.width + value.translation.width,
                    height: startOffset.height + value.translation.height
                )
                // 可选:开启后限制视图不会被拖出屏幕
                // let screenWidth = UIScreen.main.bounds.width
                // let screenHeight = UIScreen.main.bounds.height
                // dragOffset.width = max(0, min(screenWidth - 120, dragOffset.width))
                // dragOffset.height = max(0, min(screenHeight - 40, dragOffset.height))
                print("changed \(dragOffset)")
            }
            .onEnded { _ in
                print("ended")
            }
    }
}
补充说明

如果你确实需要通过坐标位置计算偏移,可以取value.location(in: .global)获取相对于整个屏幕的全局坐标,再减去初始点击位置的全局坐标计算位移,效果和上述方案一致,也不会出现循环问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.30 07:30:03