How can I add a Scenekit Scene to a SwiftUI view?
I tried the following Hello World, using the standard Ship Scene example...
import SwiftUI
import Scene
You don't need use UIViewRepresentable
anymore. Here's an update code for SwiftUI
import SwiftUI
import SceneKit
struct ContentView: View {
var scene: SCNScene? {
SCNScene(named: "Models.scnassets/Avatar.scn")
}
var cameraNode: SCNNode? {
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 2)
return cameraNode
}
var body: some View {
SceneView(
scene: scene,
pointOfView: cameraNode,
options: [
.allowsCameraControl,
.autoenablesDefaultLighting,
.temporalAntialiasingEnabled
]
)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Rawbee's answer does the job if you are creating a SwiftUIView
inside a Game project (the default game project that Xcode will create for you)
But if you are in a Single View App project, you can create the same SceneView like this:
drag the art.scnassets
folder (which contains 2 files: ship.scn
and texture.png
) from your game project to your Single View App project.
In your Single View App project create a new SwiftUI file — I called it: ScenekitView
. This is a structure which conforms to the UIViewRepresentable
protocol
Copy and paste the code below to this file and turn on live preview mode
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
}
}
#if DEBUG
struct ScenekitView_Previews : PreviewProvider {
static var previews: some View {
ScenekitView()
}
}
#endif
I'm not a pro but this code worked for me (Xcode 11 beta 4)
In order for this to work, your SwiftUI View must conform to UIViewRepresentable
. There's more info about that in Apple's tutorial: Interfacing with UIKit.
import SwiftUI
struct SwiftUIView : UIViewRepresentable {
func makeUIView(context: Context) -> UIView {
return UIStoryboard(name: "Main", bundle: Bundle.main).instantiateInitialViewController()!.view
}
func updateUIView(_ view: UIView, context: Context) {
}
}
#if DEBUG
struct SwiftUIView_Previews : PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
#endif
Note that you'll have to turn on live preview to see it working.