With UIKit I have created a SceneView with an object, that can perform SCNActions when a button has been pressed:
import UIKit
import SceneKit
class ViewController: UIViewController {
@IBOutlet weak var ScenekitView: SCNView!
var scene:SCNScene!
var ship:SCNNode!
@IBAction func button(_ sender: Any) {
let sequence = SCNAction.sequence([SCNAction.moveBy(x: 0, y: 0, z: -10, duration: 0.5),SCNAction.moveBy(x: 0, y: 0, z: 10, duration: 0.5)])
ship.runAction(sequence)
}
override func viewDidLoad() {
super.viewDidLoad()
scene = SCNScene(named: "mainScene.scn")!
var cameraNode: SCNNode!
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
cameraNode.scale = SCNVector3(1, 1, 0.5)
cameraNode.eulerAngles = SCNVector3(0, 0, 0)
ScenekitView.scene = scene
ship = scene.rootNode.childNode(withName: "ship reference", recursively: true)!
let pos = SCNVector3Make(0, 0, 0)
ship.runAction(SCNAction.move(to: pos, duration: 1))
}
}
Now I want to achieve the same using SwiftUI.
While I am able to, in this example, let the camera perform an action once the view has loaded, I have no idea how to let it perform another action after the press of a button. Especially because the node is defined in a function in another sub view.
This is what I got so far:
import SwiftUI
import SceneKit
struct ContentView: View {
var body: some View{
VStack {
ScenekitView().ignoresSafeArea()
Button("move") {
//here I would like to call a function including a SCNAction
}
}
}
}
struct ScenekitView : UIViewRepresentable {
func makeUIView(context: Context) -> SCNView {
let scene = SCNScene(named: "SceneFile.scn")!
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
scene.rootNode.addChildNode(cameraNode)
cameraNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 5, duration: 1)))
let scnView = SCNView()
return scnView
}
func updateUIView(_ scnView: SCNView, context: Context) {
scnView.scene = scene
scnView.allowsCameraControl = false
}
}
Thanks for any helpful ideas.
You can create an object owned by the parent view (ContentView
) that distributes the scene reference to the ScenekitView
and allows access from the Button
.
You might want to adjust details (like where the camera setup is done), but this is the general concept:
class SceneManager : ObservableObject {
let scene = SCNScene(named: "SceneFile.scn")!
var ship:SCNNode
init() {
ship = scene.rootNode.childNode(withName: "ship reference", recursively: true)!
}
func move() {
let pos = SCNVector3Make(0, 0, 0)
ship.runAction(SCNAction.move(to: pos, duration: 1))
}
}
struct ContentView: View {
@StateObject var sceneManager = SceneManager()
var body: some View{
VStack {
ScenekitView(scene: sceneManager.scene)
.ignoresSafeArea()
Button("move") {
sceneManager.move()
}
}
}
}
struct ScenekitView : UIViewRepresentable {
var scene : SCNScene
func makeUIView(context: Context) -> SCNView {
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)
scene.rootNode.addChildNode(cameraNode)
cameraNode.runAction(SCNAction.repeatForever(SCNAction.rotateBy(x: 0, y: 0, z: 5, duration: 1)))
let scnView = SCNView()
scnView.scene = scene
return scnView
}
func updateUIView(_ scnView: SCNView, context: Context) {
scnView.allowsCameraControl = false
}
}