Canvas 不会在 SwiftUI 中重绘
我在 macOS 上的 SwiftUI 中有一个项目,我每秒在画布上绘制两次。
这是我的 ContentView:
struct ContentView: view {
@State var score: Int = 0
var body: some View {
VStack {
Text("Score: \(self.score)")
.fixedSize(horizontal: true, vertical: true)
Canvas(renderer: { gc, size in
start(
gc: &gc,
size: size
onPoint: { newScore in
self.score = newScore
}
)
)
}
}
}
start
函数:
var renderer: Renderer
func start(
gc: inout GraphicsContext,
size: size,
onPoint: @escaping (Int) -> ()
) {
if renderer != nil {
renderer!.set(gc: &gc)
} else {
renderer = Renderer(
context: &gc,
canvasSize: size,
onPoint: onPoint
)
startGameLoop(renderer: renderer!)
}
renderer!.drawFrame()
}
var timer: Timer
func startGameLoop(renderer: Renderer) {
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: {
renderer!.handleNextFrame()
}
}
渲染器大致如下所示:
class Renderer {
var gc: GraphicsContext
var size: CGSize
var cellSize: CGFloat
let pointCallback: (Int) -> ()
var player: (Int, Int) = (0,0)
init(
context: inout GraphicsContext,
canvasSize: CGSize,
onPoint: (Int) -> ()
) {
self.gc = gc
self.size = canvasSize
self.pointCallback = onPoint
self.cellSize = min(self.size.width, self.size.height)
}
}
extension Renderer {
func handleNextFrame() {
self.player = (self.player.0 + 1, self.player.1 + 1)
self.drawFrame
}
func drawFrame() {
self.gc.fill(
Path(
x: CGFloat(self.player.0) * self.cellSize,
y: CGFloat(self.player.1) * self.cellSize,
width: self.cellSize,
height: self.cellSize
)
)
}
}
因此,每秒调用 handleNextFrame
方法两次,该方法调用drawFrame
方法,将player
的位置绘制到画布上。
然而,画布上没有绘制任何东西。
仅绘制第一帧,它来自 start
中的 renderer!.drawFrame()
。当得分时,画布也会重新绘制,因为 start
函数会再次被调用。
问题是,当从 handleNextFrame
调用 drawFrame
时,Canvas 上没有绘制任何内容。
我的问题出在哪里,我该如何解决这个问题?
预先感谢,
乔纳斯
I have a project in SwiftUI on macOS where I draw to a canvas twice per second.
This is my ContentView
:
struct ContentView: view {
@State var score: Int = 0
var body: some View {
VStack {
Text("Score: \(self.score)")
.fixedSize(horizontal: true, vertical: true)
Canvas(renderer: { gc, size in
start(
gc: &gc,
size: size
onPoint: { newScore in
self.score = newScore
}
)
)
}
}
}
The start
function:
var renderer: Renderer
func start(
gc: inout GraphicsContext,
size: size,
onPoint: @escaping (Int) -> ()
) {
if renderer != nil {
renderer!.set(gc: &gc)
} else {
renderer = Renderer(
context: &gc,
canvasSize: size,
onPoint: onPoint
)
startGameLoop(renderer: renderer!)
}
renderer!.drawFrame()
}
var timer: Timer
func startGameLoop(renderer: Renderer) {
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true, block: {
renderer!.handleNextFrame()
}
}
And the renderer roughly looks like this:
class Renderer {
var gc: GraphicsContext
var size: CGSize
var cellSize: CGFloat
let pointCallback: (Int) -> ()
var player: (Int, Int) = (0,0)
init(
context: inout GraphicsContext,
canvasSize: CGSize,
onPoint: (Int) -> ()
) {
self.gc = gc
self.size = canvasSize
self.pointCallback = onPoint
self.cellSize = min(self.size.width, self.size.height)
}
}
extension Renderer {
func handleNextFrame() {
self.player = (self.player.0 + 1, self.player.1 + 1)
self.drawFrame
}
func drawFrame() {
self.gc.fill(
Path(
x: CGFloat(self.player.0) * self.cellSize,
y: CGFloat(self.player.1) * self.cellSize,
width: self.cellSize,
height: self.cellSize
)
)
}
}
So the handleNextFrame
method is called twice per second, which calls the drawFrame
method, drawing the position of the player
to the canvas.
However, there is nothing being drawn to the canvas.
Only the first frame is drawn, which comes from the renderer!.drawFrame()
in start
. When a point is scored, the canvas is also redrawn, because the start
function gets called again.
The problem is that there is nothing being drawn to the Canvas when the drawFrame
is called from handleNextFrame
.
Where lies my problem, and how can I fix this issue?
Thanks in advance,
Jonas
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
我有同样的问题。
转到项目设置,选择“构建设置”选项卡,并确保“严格并发检查”处于打开状态。
它会提示您 GraphicsContext 不是线程安全的,因为它不符合 Sendable 协议。
我不是该主题的专家,但我相信这意味着 GraphicsContext 不适合在异步上下文中使用,您不适合保存其引用以供将来使用。您将获得一个 GraphicsContext 实例,并且只要渲染器闭包的执行持续,您就应该使用它(当然,此执行会在异步回调可以使用 GraphicsContext 实例之前结束)。
您真正想要的是 TimelineView:
这将每纳秒更改绘制矩形的渐变颜色。
有趣的是,我在一些博客上听说,你需要一个完全不同的心态来使用 SwiftUI,哈哈。它只是不能以传统方式工作。
I had the same issue.
Go to your project settings, select 'Build Settings' tab, and make sure 'Strict Concurrency Checking' is ON.
It will give you hints about GraphicsContext not being thread-safe, because it does not conform to Sendable protocol.
I am no expert on the topic, but I believe this means that GraphicsContext is not meant to be used in asynchronous context, you are not meant to save its reference for future use. You are given a GraphicsContext instance and you are meant to use it so long as the renderer closure's execution lasts (and this execution, of course, ends before your asynchronous callbacks could use the GraphicsContext instance).
What you really want is a TimelineView:
This will change the gradient colour of the drawn rectangle every nanosecond.
Funny thing, I have heard on some blog, you need a completely different mindset to use SwiftUI, haha. It just does not work the traditional way.