blob: 87ee9e4761123e848b68a7d06d676a2a4f50c697 [file] [edit]
import SwiftUI
import QuartzCore
import Metal
final class MetalHostView: UIView {
override class var layerClass: AnyClass { CAMetalLayer.self }
var metalLayer: CAMetalLayer { layer as! CAMetalLayer }
private var didStart = false
override func layoutSubviews() {
super.layoutSubviews()
// Make sure we actually have size
guard bounds.width > 0, bounds.height > 0 else { return }
metalLayer.frame = bounds
metalLayer.contentsScale = 1.0
metalLayer.drawableSize = CGSize(
width: max(1, bounds.width),
height: max(1, bounds.height)
)
if !didStart { // start only once, with real size
didStart = true
let opaque = Unmanaged.passUnretained(metalLayer).toOpaque()
cube_runner_start(opaque)
} else {
cube_runner_resize() // keep resizing after start
}
}
}
struct ContentView: View {
var body: some View {
CubeHostView()
.ignoresSafeArea()
}
}
struct CubeHostView: UIViewRepresentable {
final class Coordinator: NSObject {
var displayLink: CADisplayLink?
@objc func tick() { cube_runner_draw() }
}
func makeCoordinator() -> Coordinator { Coordinator() }
func makeUIView(context: Context) -> MetalHostView {
let view = MetalHostView()
// Configure the CAMetalLayer
view.metalLayer.device = MTLCreateSystemDefaultDevice()
view.metalLayer.pixelFormat = .bgra8Unorm
view.metalLayer.isOpaque = true
view.metalLayer.framebufferOnly = true
view.metalLayer.presentsWithTransaction = false
// Drive rendering
let cADisplayLink = CADisplayLink(target: context.coordinator, selector: #selector(Coordinator.tick))
cADisplayLink.preferredFrameRateRange = CAFrameRateRange(minimum: 30, maximum: 60, preferred: 60)
cADisplayLink.add(to: .main, forMode: .default)
context.coordinator.displayLink = cADisplayLink
return view
}
func updateUIView(_ uiView: MetalHostView, context: Context) {
uiView.setNeedsLayout() // layoutSubviews will size & start/resize
}
static func dismantleUIView(_ uiView: MetalHostView, coordinator: Coordinator) {
coordinator.displayLink?.invalidate()
cube_runner_stop()
}
}