我可以让 SCNBox 可点击和交互吗?
Posted
技术标签:
【中文标题】我可以让 SCNBox 可点击和交互吗?【英文标题】:Can I make SCNBox tappable and interactive? 【发布时间】:2021-12-31 18:56:39 【问题描述】:我正在尝试在 SwiftUI 中使用 SCNBox 创建一个旋转立方体,您可以在其中点击框的每一侧并显示不同的弹出屏幕/视图显示文本等。我有旋转 SCNBox 立方体,但我该如何制作它可点击和交互,我如何让它重定向到另一个视图?
这就是我目前尝试将 Box Scene 插入 SwiftUI 视图的方式,然后该视图将进入现有应用程序的视图层次结构。
import SwiftUI
import SceneKit
class BoxNode: SCNNode
override init()
super.init()
self.geometry = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0.0)
self.geometry?.firstMaterial?.shininess = 50
let action = SCNAction.rotate(by: 360 * CGFloat(Double.pi / 180), around: SCNVector3(x:0, y:1, z:0), duration: 8)
let repeatAction = SCNAction.repeatForever(action)
self.runAction(repeatAction)
required init?(coder: NSCoder)
fatalError("init(coder:) has not been implemented")
struct BoxScene: View
var scene = SCNScene(named: "MyScene")
var cameraNode: SCNNode?
scene?.rootNode.childNode(withName: "camera", recursively: false)
var boxNode: SCNNode?
scene?.rootNode.addChildNode(BoxNode())
var body: some View
SceneView(
scene: scene,
pointOfView: cameraNode,
options: []
)
但是,此代码无法编译 - 它表示“表达式类型不明确,下一行中没有更多上下文:
var boxNode: SCNNode?
scene?.rootNode.addChildNode(BoxNode())
【问题讨论】:
如何在 SwiftUI 视图中显示 SCNBox?您应该显示该代码。 目前我正试图弄清楚如何做到这一点。我想将 SCNBox 集成到带有标签的现有应用程序中。目标是让此框进入其中一个选项卡(当前使用 TabView),一旦您点击框的一侧,您将获得 (a) 一个弹出窗口,和/或 (b) 重定向到另一个应用程序中的选项卡。问题是将盒子集成到现有应用程序中(目前它们是单独的 Xcode 项目。提前致谢! 你将需要展示比你已经完成的更多的代码。目前你没有提供足够的代码来帮助你。 刚刚编辑了我的原始问题以添加您在上面请求的代码......虽然它没有编译。 您可能需要继承 SKScene 并覆盖 touchesBegan 以捕获触摸事件。你可以将闭包传递给你的子类,这样你就可以对你的触摸事件采取行动。 【参考方案1】:这是一个使用SCNBox
可能会有所帮助的示例。
BoxNode
是一个自定义SCNNode
,它使用SCNBox
作为其几何形状。每一面都设置为不同的颜色,以便于查看,如果您想要展示更详细的内容,可以使用UIImage
s。由于我们使用的是 SceneKit
、UIKit
和 SwiftUI
,请确保在需要的地方导入它们。
class BoxNode: SCNNode
override init()
let length: CGFloat = 5
let box = SCNBox(width: length, height: length, length: length, chamferRadius: 0)
box.materials = [UIColor.red, .yellow, .green, .blue, .purple, .brown].map
let material = SCNMaterial()
material.diffuse.contents = $0
material.isDoubleSided = true
material.transparencyMode = .aOne
return material
super.init()
self.geometry = box
self.name = "boxNode"
required init?(coder: NSCoder)
fatalError("init(coder:) has not been implemented")
然后在自定义SCNScene
中使用它,它将BoxNode
添加为根节点并设置旋转动画并旋转立方体。
class GameScene: SCNScene
override init()
super.init()
let cubeNode = BoxNode()
self.rootNode.addChildNode(cubeNode)
let xAngle = SCNMatrix4MakeRotation(.pi / 3.8, 1, 0, 0)
let zAngle = SCNMatrix4MakeRotation(-.pi / 4, 0, 0, 1)
cubeNode.pivot = SCNMatrix4Mult(SCNMatrix4Mult(xAngle, zAngle), cubeNode.transform)
// Rotate the cube
let animation = CAKeyframeAnimation(keyPath: "rotation")
animation.values = [SCNVector4(1, 1, 0.3, 0 * .pi),
SCNVector4(1, 1, 0.3, 1 * .pi),
SCNVector4(1, 1, 0.3, 2 * .pi)]
animation.duration = 5
animation.repeatCount = HUGE
cubeNode.addAnimation(animation, forKey: "rotation")
required init?(coder aDecoder: NSCoder)
fatalError("init(coder:) has not been implemented")
然后将GameScene
设置在SCNView
内部UIViewController
内部。 SCNView
固定在 ViewController 的边缘,我们覆盖 touchesBegan
以便我们可以与 SceneView
交互。我还创建了一个变量sideTapped
,因为这将允许您注入一个回调,以便您可以判断哪一侧已被点击了您的立方体。
class GameSceneViewController: UIViewController
private let sceneView: SCNView = .init(frame: .zero)
private let scene = GameScene()
var sideTapped: ((Int) -> Void)?
override func viewDidLoad()
super.viewDidLoad()
sceneView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(sceneView)
NSLayoutConstraint.activate([
sceneView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
sceneView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
sceneView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
sceneView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
])
sceneView.scene = scene
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
guard let touchPoint = touches.first?.location(in: sceneView) else return
guard let hitTestResult = sceneView.hitTest(touchPoint, options: nil).first else return
guard hitTestResult.node is BoxNode else return
sideTapped?(hitTestResult.geometryIndex)
当我们在SwiftUI
中使用它时,我们需要一个UIViewControllerRepresentable
。这里我们将sideTapped
函数传递给GameSceneViewController
。
struct GSViewControllerRepresentable: UIViewControllerRepresentable
let sideTapped: ((Int) -> Void)?
func makeUIViewController(context: Context) -> GameSceneViewController
let vc = GameSceneViewController()
vc.sideTapped = sideTapped
return vc
func updateUIViewController(_ uiViewController: GameSceneViewController, context: Context)
现在我们可以将其添加到 SwiftUI
视图中。
struct ContentView: View
var body: some View
GSViewControllerRepresentable side in
print(side)
// Perform additional logic here
.frame(width: 200, height: 200)
最终结果应该是这样的:
点击立方体的一侧会打印一个从 0 到 5 的数字,表示您点击了哪一侧。根据所使用的材料,数字应与材料的索引相对应,因此在这种情况下:
0 为红色 1 为黄色 2 为绿色 3 是蓝色 4是紫色 5 为棕色这应该会给你一些你应该能够关闭导航的东西。
我不是 SceneKit 方面的专家,但我在周末玩了它,并且几乎完成了你所要求的事情。希望这能给你一个想法,有很多方法可以实现你想做的事情,这是一种可能性。
如果您想滑动立方体来改变面,而不是让它自己旋转,您可能想看看这个answer。
【讨论】:
非常感谢!这正是我所需要的,而且效果很好。一个问题 - 你知道如何将被点击的一侧重定向到应用程序中的现有视图吗?我尝试了以下但没有成功:` struct ContentView: View var body: some View GSViewControllerRepresentable side in if side = 1 FirstView() else AnotherView() .frame(width: 200, height: 200 ) ` 这永远不会起作用,因为您获得侧面的回调不是 ViewBuilder。您需要更新 ContentView 中的一些状态值,然后驱动视图的变化。此链接中的第二个代码示例应该可以帮助您hackingwithswift.com/quick-start/swiftui/…,尽管它使用字符串它应该适用于使用 Ints。 GSViewControllerRepresentable 基本上是一个花哨的按钮,它在其闭包中返回一个值,您可以将该值设置为链接示例中的选择。 现在当我点击一侧时,View 会在底部弹出,而 Cube 仍然在屏幕顶部。您知道如何将其调整为完全将 ** 重定向到另一个视图吗?我当前的代码如下所示:` struct CubeView: View @State private var selection: Int? = nil var body: some View GSViewControllerRepresentable side in selection = side .frame(width: 250, height: 350) if selection == 1 FirstView() else ` 我在上一条评论中列出的教程是你需要做的。 谢谢——我就是这么做的。但是,View 并没有取代 Cube,而是出现在 Cube 下方,并且 Cube 仍然可见。想知道如何从视图中删除 Cube,但由于我不熟悉 ViewControllerRepresentable 行为,我不确定如何调试它。再次感谢!以上是关于我可以让 SCNBox 可点击和交互吗?的主要内容,如果未能解决你的问题,请参考以下文章
emacs 如何让buffer变的可交互 让buffer操作shell