SwiftUI onDrag捕获原始值问题
SwiftUI onDrag捕获原始值问题

SwiftUI onDrag捕获原始值问题

问题描述

在开发《方方块》游戏的过程中,发现在第一次拖动时,并不会显示方块阴影。但是第二次拖动时,会显示方块阴影。

经过代码排查,发现在第一次放置时,发现有一个棋盘的原点是 (0.0, 0.0)。

gridOrigin:(0.0, 0.0)

通过排查发现,棋盘的原点实际是可以正常获取的:

GameGridView(grid: grid, shadowPosition: shadowPosition, shadowBlock: shadowBlock)
    .background {
        GeometryReader { gridGeo in
            Color.clear
                .onAppear {
                    DispatchQueue.main.async {
                        let gridPosition = gridGeo.frame(in: .global).origin
                    gridOrigin = gridPosition
                    print("使用延迟获取的网格原点 gridOrigin: \(gridOrigin)")
                }
            }
        }
}

Xcode输出的代码为:

使用延迟获取的网格原点 gridOrigin: (7.5, 212.0)

但是,在拖动方块的过程中,计算阴影的方法获取的gridOrigin是(0, 0)。

进入shadowBlock方法
start:(64.66666158040366, 64.66665649414062)
end:(22.0, -9.0)
origins:(127.5, 602.0)
gridOrigin:(0.0, 0.0)

经过一系列排查的工作,发现问题在于onDrag方法中的gridOrigin捕获的是原始值。

DraggableBlockView(block: block, 
    GestureOffset: GestureOffset,
     onDrag :{ start, end, geo  in
        shadowBlock(block, start, end, geo, item)
    },
    onDrop: {start, end, geo  in
      placeBlock(block, start, end, geo, item)
})

在DraggableBlockView视图中,shadowBlock和placeBlock的参数是一致的,这两个方法都是从外部获取的gridOrigin:

// 显示放置方块的阴影
func shadowBlock(_ block: Block, _ start: CGPoint, _ end: CGSize, _ origins: CGPoint, _ indices : Int) {
    let CalculateY = touchOffsetY - gridOrigin.y
    // ...
}

// 放置方块
func placeBlock(_ block: Block, _ start: CGPoint, _ end: CGSize, _ origins: CGPoint, _ indices : Int) {
    let CalculateY = touchOffsetY - gridOrigin.y 
    // ...
}

但是只有在调用onDrop方法时,才能获取到真实的gridOrigin值。

经过查询了解到,在SwiftUI的onDrag中,gridOrigin可能被捕获为原始值(0,0),而 SwiftUI 的状态更新是异步的,所以当 onDrag 触发时,它可能还没有拿到最新的 gridOrigin。

.onDrag { _ in
    print("移动中,gridOrigin: \(gridOrigin)") // 可能仍然是 (0,0)
}

这里 onDrag 捕获的是当时的 gridOrigin 值,而不是一个实时更新的引用。

解决方案

SwiftUI 异步更新 UI,onDrag 可能发生在 gridOrigin 更新前。

DispatchQueue.main.async 推迟执行代码,确保 gridOrigin 已经正确更新。

DraggableBlockView(block: block, 
    GestureOffset: GestureOffset,
     onDrag :{ start, end, geo  in
      DispatchQueue.main.async {
          shadowBlock(block, start, end, geo, item)
      }
  },
    onDrop: {start, end, geo  in
      placeBlock(block, start, end, geo, item)
  })

使用DispatchQueue.main.async 把代码推到下一次主线程循环中执行,这就保证了 gridOrigin 已经更新,获取到的是最新值。

总结

onDrag 捕获的是旧的 gridOrigin,所以直接 print(gridOrigin) 可能仍然是 (0,0)。

DispatchQueue.main.async 让代码在 UI 更新后执行,因此 gridOrigin 是最新的。

shadowBlock 可能在 UI 更新后才调用,所以能获取到最新的 gridOrigin。

最后,通过DispatchQueue.main.async实现onDrag捕获最新值。

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注