Using MagnificationGesture in SceneView

SwiftUI Gestures like MagnificationGesture seem well targeted to manipulating a View. In an app using SceneKit, I am usually most interested in manipulating the SceneView’s scene (SCNScene type) contents. As in this example, in which I’m using MagnificationGesture to change the FOV of SceneView’s pointOfView node's camera. What I’ve coded works, but I long ago learned that working code doesn’t mean it works well or performantly.

So my question is whether I am doing the MagnificationGesture correctly to change the FOV of the camera in the pointOfView node? Please use simple words in small sentences in your advice and criticisms.

Code Block swift
import SwiftUI
import SceneKit
struct ContentView: View {
@State private var magnify = CGFloat(1.0)
@State private var doneMagnifying = false
@GestureState var magnifyBy = CGFloat(1.0)
var magnification: some Gesture {
MagnificationGesture()
.updating($magnifyBy) { (currentState, gestureState, transaction) in
gestureState = currentState
print("magnifyBy = \(self.magnifyBy)")
}
.onChanged{ (value) in
self.magnify = value
}
.onEnded { _ in
self.doneMagnifying = true
}
}
// The camera node for the scene.
var cameraNode: SCNNode? {
get{
var node = SCNNode()
node = aircraftScene.rootNode.childNode(withName: "distantCameraNode", recursively: true)!
let maximumFOV: CGFloat = 25 // This is what determines the farthest point into which to zoom.
let minimumFOV: CGFloat = 90 // This is what determines the farthest point from which to zoom.
let currentFOV = node.camera?.fieldOfView
if !doneMagnifying {
print("Still magnifying")
node.camera?.fieldOfView *= magnifyBy
} else {
print("Done magnifying. Yippeee!!!")
node.camera?.fieldOfView *= magnify
}
if node.camera!.fieldOfView <= maximumFOV {
node.camera!.fieldOfView = maximumFOV
}
if node.camera!.fieldOfView >= minimumFOV {
node.camera!.fieldOfView = minimumFOV
}
return node
}
}
var aircraftScene: SCNScene {
get {
print("Loading Scene Assets.")
guard let scene = SCNScene(named: "art.scnassets/ship.scn")
else {
print("Oopsie, no scene")
return SCNScene()
}
return scene
}
}
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
SceneView (
scene: aircraftScene,
pointOfView: cameraNode,
options: []
)
.background(Color.black)
.gesture(magnification)
VStack() {
Text("Hello, SceneKit!").multilineTextAlignment(.leading).padding()
.foregroundColor(Color.gray)
.font(.largeTitle)
Text("Pinch to zoom.")
.foregroundColor(Color.gray)
.font(.title)
Spacer(minLength: 300)
}
}
}
}



I just want to add that running the above code on an iPhone 11 (iOS 14b3) results in a very bad user-experience with a frame rate of 2 fps. So, either I'm doing something horribly wrong, which is likely, or else SceneView may not be ready for prime-time, which would be sad.
So, can't edit or correct my own previous posts. Nice!

The above code of mine obviously doesn't work. Worse, it's bad code. Junk, really.

There are a couple of ways to handle creating an "aircraftScene" instance of SCNScene, @StateObject and doing a copy( ). I chose, after talking asking friends more experienced than me for their advice, to use @StateObject implementation of the "aircraftScene". But guess what?!? Apple didn't change SCNScene to conform to ObservedObject like it did for . So one needs to extend SCNScene to conform to ObservableObject in order to use the @StateObject property wrapper on an instance of SCNScene.

Unfortunately, this extension of SCNScene as an ObservableObject does not synthesize the objectWillChange publisher, which would emit a value change before any of its @Published properties changed.

Below is the simplified version of what I've done. This works. But not well. The performance never exceeds 53 fps and so can't be used for production sims or games.

If you find something amiss, which I'm sure you will, please post it here.

Code Block swift
import SwiftUI
import SceneKit
extension SCNScene: ObservableObject {
}
struct SwiftUISceneKitUsingStateObjectVarsContentView: View {
@State private var sunlightSwitch = true
@State private var magnify = CGFloat(1.0)
@StateObject var aircraftScene = SCNScene(named: "art.scnassets/ship.scn")!
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
SceneView (
scene: aircraftScene,
pointOfView: aircraftScene.rootNode.childNode(withName: "distantCameraNode", recursively: true)
)
.background(Color.black)
.gesture(MagnificationGesture()
.onChanged{ (value) in
print("magnify = \(self.magnify)")
self.magnify = value
let camera = self.aircraftScene.rootNode.childNode(withName: "distantCameraNode", recursively: true)?.camera
camera!.fieldOfView /= magnify
}
.onEnded{ _ in
print("Ended pinch\n\n")
}
)
VStack() {
Text("Hello, SceneKit!").multilineTextAlignment(.leading).padding()
.foregroundColor(Color.gray)
.font(.largeTitle)
Text("Pinch to zoom.")
.foregroundColor(Color.gray)
.font(.title)
Text("Magnification: \(magnify, specifier: "%.2f")")
.foregroundColor(Color.gray)
.font(.title3)
.padding()
Text("FOV: \((self.aircraftScene.rootNode.childNode(withName: "distantCameraNode", recursively: true)?.camera!.fieldOfView)!, specifier: "%.2f")")
.foregroundColor(Color.gray)
.font(.title3)
Spacer(minLength: 300)
Button( action: {
withAnimation{
self.sunlightSwitch.toggle()
}
let sunlight = self.aircraftScene.rootNode.childNode(withName: "sunlightNode", recursively: true)?.light
if self.sunlightSwitch == true {
sunlight!.intensity = 2000.0
} else {
sunlight!.intensity = 0.0
}
}) {
Image(systemName: sunlightSwitch ? "lightbulb.fill" : "lightbulb")
.imageScale(.large)
.accessibility(label: Text("Light Switch"))
.padding()
}
}
}
.statusBar(hidden: true)
}
}


So, let's say you want to swap between cameras, provided you have more than one, in a scene. I banged my head on this for a bit of time, primarily because I'm either dim-witted (a real strong possibility) or just too set in my 57-year old ways, trying to change the pointOfView property in SceneView. Eventually, it hit me that all I'm trying to do is change the "withName" String in .childNode(withName: String, recursively: Bool). And boy!, that's easy.

The above code I posted is junk and, frankly, embarrassing. Here's the better code. I hope it helps.

Code Block swift
import SwiftUI
import SceneKit
extension SCNScene: ObservableObject {
}
struct SwiftUISceneKitUsingStateObjectVarsContentView: View {
@State private var povSwitch = true
@State private var pointOfView = "distantCamera"
@StateObject var aircraftScene = SCNScene(named: "art.scnassets/ship.scn")!
var body: some View {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
SceneView (
scene: aircraftScene,
pointOfView: aircraftScene.rootNode.childNode(withName: pointOfView, recursively: true)
)
.background(Color.black)
VStack() {
Text("Hello, SceneKit!").multilineTextAlignment(.leading).padding()
.foregroundColor(Color.gray)
.font(.largeTitle)
Text("Change the camera.")
.foregroundColor(Color.gray)
.font(.title)
Spacer(minLength: 300)
Button( action: {
withAnimation{
self.povSwitch.toggle()
}
if self.povSwitch == true {
self.pointOfView = "distantCamera"
} else {
self.pointOfView = "aircraftCamera"
}
}) {
Image(systemName: sunlightSwitch ? "video.fill" : "video")
.imageScale(.large)
.accessibility(label: Text("Camera Switch"))
.padding()
}
}
}
.statusBar(hidden: true)
}
}


Using MagnificationGesture in SceneView
 
 
Q