问题
Goal: SwiftUI toggle button that controls a SwiftUI View with Scenekit (UIViewRepresentable)
// show statistics such as fps and timing information
What I did: This is the 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()
}
}
This is the Menu to control 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()
}
}
And this is Main Scene, with Menu on top of Scenekit:
import SwiftUI
struct MainView: View {
var body: some View {
ZStack {
ScenekitView()
Menu()
}
}
}
Question: How can I pass data from ScenekitView to Menu, so I can toggle and show/hide stats?
I tried ObservableObject but could not make it work. Also looked into other SO threads, but none worked for me.
Any help is much appreciated!
Cheers to all
回答1:
Updated: March 11, 2020.
To pass data to
updateNSView(_:context:)
orupdateUIView(_:context:)
instance method you need to create@State
and@Binding
properties.
Use the following code to find out how to do it:
I've written this code for macOS but you can easily change it for 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
}
}
Additionally you can use
Coordinator
object anddelegate
to this coordinator for switching objects usingcontrol
property and for updating anything at 60 fps insiderenderer()
method (for example).
In this case you do not need to use updateNSView()
or updateUIView()
method:
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) { }
}
来源:https://stackoverflow.com/questions/60582392/swiftui-passing-data-from-swiftuiview-to-scenekit