Reputation: 533
I am having a cylinder as a SCNCylinder
in a SCNView
. I want to rotate the cylinder by a number of given angles. I am using SwiftUI to give the required input (angle of rotation). Suppose I give it a input as 90° . Currently it can easily rotate by 90°, but if I give it a second input of 180°, it goes back to its original position and then it rotates by 180°. I want it to rotate by 180° from the position it got after its 90° rotation.
Here is my code :
struct ContentView: View {
@State var rotationAngle: Float = 0
var body: some View {
VStack{
Text("180°").onTapGesture {
self.rotationAngle = 180.0
}
Divider()
Text("90°").onTapGesture {
self.rotationAngle = 90.0
}
SceneKitView(angle: $rotationAngle)
.position(x: 225.0, y: 175)
.frame(width: 300, height: 300, alignment: .center)
}
}
}
struct SceneKitView: UIViewRepresentable {
@Binding var angle: Float
func degreesToRadians(_ degrees: Float) -> CGFloat {
return CGFloat(degrees * .pi / 180)
}
func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView {
let sceneView = SCNView()
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = UIColor.white
sceneView.frame = CGRect(x: 0, y: 10, width: 0, height: 1)
return sceneView
}
func updateUIView(_ sceneView: SCNView, context: UIViewRepresentableContext<SceneKitView>) {
sceneView.scene?.rootNode.enumerateChildNodes { (node, stop) in
node.removeFromParentNode() }
let cylinder = SCNCylinder(radius: 0.02, height: 2.0)
let cylindernode = SCNNode(geometry: cylinder)
cylindernode.position = SCNVector3(x: 0, y: 0, z: 0)
cylinder.firstMaterial?.diffuse.contents = UIColor.green
cylindernode.pivot = SCNMatrix4MakeTranslation(0, -1, 0)
let rotation = SCNAction.rotate(by: self.degreesToRadians(self.angle),
around: SCNVector3(1, 0, 0), duration: 5)
cylindernode.runAction(rotation)
sceneView.scene?.rootNode.addChildNode(cylindernode)
}
I wish it to rotate properly no matter how many angles I give it.
Upvotes: 0
Views: 678
Reputation: 1435
To be clear, you want the cylinder to continue rotating from it's current position every time the button is pressed not have the cylinder go to a fixed either 90 or 180 position.
Because your current code seems to be setup to do the second thing (except for the rotate:by
). The rotationAngle is held as a fixed state. Really you should be sending rotate commands to the node without keeping the amount it rotated around in a state variable.
But, working with the code you have now:
Inside the updateUIView
stick to only doing updates to your nodes. Right now, you are tearing down and recreating the entire node hierarchy to it's original position every time rotationAngle
changes. That's probably why thing are redrawing.
Instead do that setup inside makeUIView
or inside an initializer for SceneKitView
and in updateUIView
just apply the new rotation.
Second problem is that when you set rotationAngle
by pressing the button to say 180, if you press the same button again, the value is set again to 180. The value hasn't changed so as far as SwiftUI is concerned, there is no need to call updateUIView
. There's a couple ways to make it update, but a quick and easy way is probably just rotate by 0 then immediately after by the angle you want.
But again you really should not hold that angle in a state variable. Better to just make a rotateCylinder(angle: Angle)
function in SceneKitView
and call it from the button.
Also, why not use the new Angle
type? Here's your code with some of those changes. I think this what you are trying to do, but I could be wrong.
import SwiftUI
import UIKit
import SceneKit
struct ContentView: View {
@State var rotationAngle: Angle = .zero
var body: some View {
VStack{
HStack {
Spacer()
Text("180°").onTapGesture {
self.rotationAngle = .zero
self.rotationAngle = .degrees(180)
}
Spacer()
Divider()
Spacer()
Text("90°").onTapGesture {
self.rotationAngle = .zero
self.rotationAngle = .degrees(90)
}
Spacer()
}.frame(height: 100).padding()
SceneKitView(radius: 0.2, height: 2, angle: $rotationAngle)
}
}
}
struct SceneKitView: UIViewRepresentable {
@Binding var angle: Angle
let node: SCNNode
init(radius: CGFloat, height: CGFloat, angle: Binding<Angle>) {
let cylinder = SCNCylinder(radius: radius, height: height)
cylinder.firstMaterial?.diffuse.contents = UIColor.green
self.node = SCNNode(geometry: cylinder)
self._angle = angle
}
func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView {
let sceneView = SCNView()
sceneView.scene = SCNScene()
sceneView.autoenablesDefaultLighting = true
sceneView.scene?.rootNode.addChildNode(node)
return sceneView
}
func updateUIView(_ sceneView: SCNView, context: UIViewRepresentableContext<SceneKitView>) {
print("Updating view \(angle.degrees)")
// To continue rotating
let rotation = SCNAction.rotate(by: CGFloat(angle.radians), around: SCNVector3(1, 0, 0), duration: 3)
// To go to a fixed angle state
//let rotation = SCNAction.rotate(toAxisAngle: SCNVector4(1, 0, 0, angle.radians), duration: 3)
node.runAction(rotation)
}
}
Upvotes: 1