SwiftUI – 将数据从 SwiftUIView 传递到 SceneKit
SwiftUI – Passing data from SwiftUIView to SceneKit
目标:
使用 Scenekit (UIViewRepresentable) 控制 SwiftUI 视图的 SwiftUI 切换按钮
// 显示 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()
}
}
这是控制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()
}
}
这是主场景,菜单位于 Scenekit 之上:
import SwiftUI
struct MainView: View {
var body: some View {
ZStack {
ScenekitView()
Menu()
}
}
}
问题:如何将数据从 ScenekitView 传递到菜单,以便切换和 show/hide 统计信息?
我尝试了 ObservableObject 但无法使其工作。还查看了其他 SO 线程,但 none 对我有用。
非常感谢任何帮助!
大家干杯
更新:2020 年 3 月 11 日。
To pass data to updateNSView(_:context:)
or updateUIView(_:context:)
instance method you need to create @State
and @Binding
properties.
使用以下代码了解如何操作:
我已经为 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
}
}
Additionally you can use Coordinator
object and delegate
to this coordinator for switching objects using control
property and for updating anything at 60 fps inside renderer()
method (for example).
在这种情况下你不需要使用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
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) { }
}
目标: 使用 Scenekit (UIViewRepresentable) 控制 SwiftUI 视图的 SwiftUI 切换按钮
// 显示 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()
}
}
这是控制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()
}
}
这是主场景,菜单位于 Scenekit 之上:
import SwiftUI
struct MainView: View {
var body: some View {
ZStack {
ScenekitView()
Menu()
}
}
}
问题:如何将数据从 ScenekitView 传递到菜单,以便切换和 show/hide 统计信息?
我尝试了 ObservableObject 但无法使其工作。还查看了其他 SO 线程,但 none 对我有用。
非常感谢任何帮助!
大家干杯
更新:2020 年 3 月 11 日。
To pass data to
updateNSView(_:context:)
orupdateUIView(_:context:)
instance method you need to create@State
and@Binding
properties.
使用以下代码了解如何操作:
我已经为 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
}
}
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).
在这种情况下你不需要使用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
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) { }
}