Search code examples
swiftswiftuiscenekit

How to access SCNSceneRendererDelegate methods when using SceneKit with SwiftUI?


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 ?


Solution

  • 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) { }
    }