【问题标题】:Canvas doesn't get redrawn in SwiftUI画布不会在 SwiftUI 中重绘
【发布时间】:2022-12-19 02:03:35
【问题描述】:

我在 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 没有绘制任何东西。

我的问题在哪里,我该如何解决这个问题?

提前致谢,
乔纳斯

【问题讨论】:

  • 嘿,你能解决这个问题吗?如果你这样做了,你介意分享它作为答案吗?谢谢

标签: swift macos canvas swiftui


【解决方案1】:

我遇到过同样的问题。 转到您的项目设置,选择“构建设置”选项卡,并确保“严格并发检查”处于开启状态。 它会给你关于 GraphicsContext 不是线程安全的提示,因为它不符合 Sendable 协议。

我不是该主题的专家,但我相信这意味着 GraphicsContext 不打算在异步上下文中使用,您不打算保存它的参考以供将来使用。您将获得一个 GraphicsContext 实例,只要渲染器闭包的执行持续,您就可以使用它(当然,此执行在您的异步回调可以使用 GraphicsContext 实例之前结束)。

你真正想要的是一个 TimelineView:

    import SwiftUI

private func nanosValue(for date: Date) -> Int { return Calendar.current.component(.nanosecond, from: date)
   }

struct ContentView: View {
    var body: some View {
        TimelineView(.periodic(from: Date(), by: 0.000001)) { timeContext in
            Canvas { context, size in
                let value = nanosValue(for: timeContext.date) % 2
                
                let rect = CGRect(origin: .zero, size: size).insetBy(dx: 25, dy: 25)

                           // Path
                           let path = Path(roundedRect: rect, cornerRadius: 35.0)

                           // Gradient
                let gradient = Gradient(colors: [.green, value == 1 ? .blue : .red])
                           let from = rect.origin
                           let to = CGPoint(x: rect.width + from.x, y: rect.height + from.y)
                           
                           // Stroke path
                           context.stroke(path, with: .color(.blue), lineWidth: 25)
                           
                           // Fill path
                           context.fill(path, with: .linearGradient(gradient,
                                                                    startPoint: from,
                                                                    endPoint: to))
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

这将每纳秒改变绘制矩形的渐变颜色。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-30
    • 2013-04-26
    • 1970-01-01
    相关资源
    最近更新 更多