SwiftUI – 将数据从 SwiftUIView 传递到 SceneKit

Man*_*ero 1 xcode scenekit swift arkit swiftui

目标:使用 Scenekit 控制 SwiftUI 视图的 SwiftUI 切换按钮(UIViewRepresentable)

// 显示统计信息,例如 fps 和计时信息

我做了什么:这是 UIViewRepresentable ScenekitView

import SwiftUI
import SceneKit

struct ScenekitView : UIViewRepresentable {
    let scene = SCNScene(named: "art.scnassets/ship.scn")!

    func makeUIView(context: Context) -> SCNView {
        // create and add a camera to the scene
        let cameraNode = SCNNode()
        cameraNode.camera = SCNCamera()
        scene.rootNode.addChildNode(cameraNode)

        // place the camera
        cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)

        // create and add a light to the scene
        let lightNode = SCNNode()
        lightNode.light = SCNLight()
        lightNode.light!.type = .omni
        lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
        scene.rootNode.addChildNode(lightNode)

        // create and add an ambient light to the scene
        let ambientLightNode = SCNNode()
        ambientLightNode.light = SCNLight()
        ambientLightNode.light!.type = .ambient
        ambientLightNode.light!.color = UIColor.darkGray
        scene.rootNode.addChildNode(ambientLightNode)

        // retrieve the ship node
        let ship = scene.rootNode.childNode(withName: "ship", recursively: true)!

        // animate the 3d object
        ship.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 2, z: 0, duration: 1)))

        // retrieve the SCNView
        let scnView = SCNView()
        return scnView
    }

    func updateUIView(_ scnView: SCNView, context: Context) {
        scnView.scene = scene

        // allows the user to manipulate the camera
        scnView.allowsCameraControl = true

        // show statistics such as fps and timing information

        scnView.showsStatistics = true

        // configure the view
        scnView.backgroundColor = UIColor.black

 }

}
struct ScenekitView_Previews: PreviewProvider {
    static var previews: some View {
        ScenekitView()
    }
}
Run Code Online (Sandbox Code Playgroud)

这是控制Scenekit的菜单

import SwiftUI


struct Menu: View {

 @State var showstats = false


    var body: some View {

        HStack {

        Form {
            Toggle(isOn:) {
               Text("Stats")
            }
        }
        .frame(width: 200.0)

            Spacer()
    }
  }
 }
struct Menu_Previews: PreviewProvider {
    static var previews: some View {
        Menu()
    }
}
Run Code Online (Sandbox Code Playgroud)

这是主场景,菜单位于 Scenekit 之上:

import SwiftUI

struct MainView: View {
    var body: some View {

        ZStack {
        ScenekitView()
        Menu()
        }


    }
}
Run Code Online (Sandbox Code Playgroud)

问题:如何将数据从 ScenekitView 传递到菜单,以便我可以切换和显示/隐藏统计信息?

我尝试了 ObservableObject 但无法使其工作。还研究了其他 SO 线程,但没有一个对我有用。

任何帮助深表感谢!

为大家干杯在此输入图像描述

ARG*_*Geo 5

更新日期2023 年 2 月 5 日

要将数据传递给updateNSView(_:context:)实例updateUIView(_:context:)方法,您需要创建@State属性@Binding

使用以下代码了解如何操作(我为 macOS 编写了此代码,但您可以轻松地将其更改为 iOS):

import SwiftUI
import SceneKit

struct ContentView: View {
    
    @State var stats: Bool = true

    var body: some View {
        HStack {
            ScenekitView(showStats: $stats)
            VStack {
                Button(action: {
                    self.stats.toggle()
                }, label: {
                    Text(self.stats ? "ON" : "OFF")
                })
            }.frame(width: 150.0)
        }
    }
}    
struct ScenekitView: NSViewRepresentable {

    @Binding var showStats: Bool
    let sceneView = SCNView(frame: .zero)
    let scene = SCNScene(named: "art.scnassets/ship.scn")!

    func scnScene(stat: Bool, context: Context) -> SCNView {
        sceneView.scene = scene
        sceneView.showsStatistics = stat
        return sceneView
    } 
    func makeNSView(context: Context) -> SCNView {
        scnScene(stat: true, context: context)
    }  
    func updateNSView(_ uiView: SCNView, context: Context) {
        uiView.allowsCameraControl = true
        uiView.backgroundColor = NSColor.black
        uiView.showsStatistics = showStats        
    }
}
Run Code Online (Sandbox Code Playgroud)

在此输入图像描述

在此输入图像描述

此外,您可以使用Coordinator对象和delegate此协调器来使用control属性切换对象以及在方法内以 60 fps 更新任何内容renderer()(例如)。

在这种情况下,您不需要使用updateNSView()updateUIView()方法:

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
        
            control.sceneView.backgroundColor = NSColor(
                             red: CGFloat(arc4random_uniform(256)) / 255.0,
                           green: CGFloat(arc4random_uniform(256)) / 255.0,
                            blue: CGFloat(arc4random_uniform(256)) / 255.0,
                           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) { }
}
Run Code Online (Sandbox Code Playgroud)