Reputation: 21025
I am using SceneKit with SwiftUI by following solution provided by Mehdi to this question:
SwiftUI - how to add a Scenekit Scene
Normally, when one creates a SceneKit project, implementing the renderer methods is as easy as just adding the the following extension in the GameViewController file and implementing each of the renderer methods:
extension GameViewController: SCNSceneRendererDelegate {
// 2
func renderer(renderer: SCNSceneRenderer, updateAtTime time: NSTimeInterval) {
// 3
doWhatever()
}
}
But when using SwiftUI, we use a struct instead of a class (see above linked question), so we cannot simply add the extension, because Xcode complains:
Non-class type 'ScenekitView" cannot conform to class protocol 'NSObjectProtocol'
Non-class type 'ScenekitView' cannot conform to class protocol 'SCNSceneRendererDelegate'
What is the solution to this ?
Upvotes: 2
Views: 1287
Reputation: 61
If you are brave and don't want to import anything but SwiftUI:
Create helper class SceneRendererDelegate
and implement anything you want:
import SceneKit
// the hackiest hack ever. And the stupidest one!
// For handling touches in SceneView we need SCNSceneRenderer,
// but SwiftUI's SceneView does not provide it.
class SceneRendererDelegate: NSObject, SCNSceneRendererDelegate {
var renderer: SCNSceneRenderer?
var onEachFrame: (() -> ())? = nil
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
if self.renderer == nil {
self.renderer = renderer
let type = type(of: renderer)
print("We got SceneRenderer: \(type)")
}
onEachFrame?()
}
}
Add it to your GameView
:
import SwiftUI
import SceneKit
struct GameView: View {
var sceneRendererDelegate = SceneRendererDelegate()
var body: some View {
ZStack {
SceneView(
scene: yourScene,
options: [
.temporalAntialiasingEnabled
],
delegate: sceneRendererDelegate)
}
.onDisappear {
// Make sure your code disabled in background
sceneRendererDelegate.onEachFrame = nil
}
.onAppear {
sceneRendererDelegate.onEachFrame = {
// Your code on every frame
}
}
}
}
You can check and run live demos from my old SwiftUI-Games
Upvotes: 2
Reputation: 21025
Found the solution in this answer:
SwiftUI – Passing data from SwiftUIView to SceneKit
At the lower half of Andy's question he describes how to use a coordinator to implement the delegate methods. Reproducing here for convenience:
struct ScenekitView: NSViewRepresentable {
@Binding var showStats: Bool
let sceneView = SCNView(frame: .zero)
let scene = SCNScene(named: "art.scnassets/ship.scn")!
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, SCNSceneRendererDelegate {
var control: ScenekitView
init(_ control: ScenekitView) {
self.control = control
}
func renderer(_ renderer: SCNSceneRenderer,
updateAtTime time: TimeInterval) {
control.sceneView.showsStatistics = control.showStats
for i in 0...255 {
control.sceneView.backgroundColor = NSColor(
red: CGFloat(arc4random_uniform(UInt32(i))),
green: CGFloat(arc4random_uniform(UInt32(i))),
blue: CGFloat(arc4random_uniform(UInt32(i))),
alpha: 1.0)
}
}
}
func scnScene(stat: Bool, context: Context) -> SCNView {
sceneView.scene = scene
sceneView.showsStatistics = stat
sceneView.delegate = context.coordinator
return sceneView
}
func makeNSView(context: Context) -> SCNView {
scnScene(stat: true, context: context)
}
func updateNSView(_ uiView: SCNView, context: Context) { }
}
Upvotes: 5