问题描述

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

经过代码排查,发现在第一次放置时,发现有一个棋盘的原点是 (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捕获最新值。