SwiftUI DragGesture设置offset触发无限循环的问题如何解决
问题原因
你出现无限循环的核心原因是DragGesture的value.location是相对于当前触发手势的蓝色视图自身的坐标系的坐标,而不是相对于父容器的全局坐标:
- 你每次把
dragOffset设为value.location,会立刻改变Toolbar的offset位置 - Toolbar位置变了之后,蓝色视图的坐标系也跟着移动,导致系统重新计算出的
value.location立刻变回0附近 - 这又触发
onChanged回调修改offset,两个数值来回跳转就形成了无限循环
修复方案
修改DragGesture的处理逻辑,用拖拽位移增量来计算偏移,避免和视图位置形成循环依赖:
- 新增状态变量记录拖拽开始前的初始偏移量,避免拖拽起始位置跳变
- 使用
value.translation(拖拽的位移增量,不受当前视图位置影响)计算最终偏移 - 可选增加边界判断,限制视图不被拖出屏幕范围
修复后的完整代码如下:
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
相关产品推荐
相关产品推荐

