ARKit添加UIView Xib文件Swift 4

时间:2018-10-03 22:08:21

标签: ios swift uiview arkit

我在检测到锚点时向我的SCNBox添加了一个xib UIView文件作为素材,但是当我关闭该ViewController时,应用程序冻结了,这是我的代码:

var detectedDataAnchor: ARAnchor?
var myView = UIView()

override func viewDidLoad() {
  super.viewDidLoad()
  myView = (Bundle.main.loadNibNamed("ARViewOne", owner: nil, options: nil)![0] as? UIView)!
}



func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {

      if self.detectedDataAnchor?.identifier == anchor.identifier {
        let node = SCNNode()
        let box = SCNBox(width: 0.1, height: 0.1, length: 0.1,
                                 chamferRadius: 0.0)
        let imageMaterial = SCNMaterial()

        imageMaterial.diffuse.contents = myView
        box.materials = [imageMaterial]
        let cube = SCNNode(geometry: box)
        node.addChildNode(cube)
        return node

    }
   return nil
}

 @IBAction func back(_ sender: UIButton) {
     // here the app freezes
     navigationController?.popViewController(animated: true)
  }

当我回到VC时,我的应用程序没有响应任何触摸事件

1 个答案:

答案 0 :(得分:4)

好的,首先要做的是

您不能将UIView的{​​{1}}添加为contents的内容。阅读here

我在这里猜测,但这很可能是您冻结/崩溃的原因,因为当您弹出控制器时,场景会被初始化,并尝试清理实际上已损坏的材料(它将处理它和其他类型一样。)

一般来说是

SCNMaterialProperty

在我看来,您对此有点陌生(完全没问题!)。您正在强行展开和强制投射(以一种奇怪的方式,myView = (Bundle.main.loadNibNamed("ARViewOne", owner: nil, options: nil)![0] as? UIView)! 基本上只是as? UIView!),这总是很危险的。您确定xib中的第一个顶级对象是as! UIView吗?此外,最初实例化的UIView(在UIView中从未使用过,为什么不在此处使用可选内容(您可以使用隐式解包的内容,尽管我自己也不喜欢这样做)。这样做:

var myView = UIView()

var myViewImage: UIImage? override func viewDidLoad() { super.viewDidLoad() let myView = Bundle.main.loadNibNamed("ARViewOne", owner: nil, options: nil).first as? UIView myViewImage = myView?.asImage() } func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? { guard let image = myViewImage, self.detectedDataAnchor?.identifier == anchor.identifier else { return nil } let node = SCNNode() let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.0) let imageMaterial = SCNMaterial() imageMaterial.diffuse.contents = image box.materials = [imageMaterial] let cube = SCNNode(geometry: box) node.addChildNode(cube) return node } // this would be outside your controller class, i.e. the top-level of a swift file extension UIView { func asImage() -> UIImage { let renderer = UIGraphicsImageRenderer(bounds: bounds) return renderer.image { rendererContext in layer.render(in: rendererContext.cgContext) } } } 扩展名来自here,所以感谢Naveed J。)

这假设a)您的xib确实正确,并且b)您只希望“视图的外观”,即节点上的一种“屏幕快照”。它将是静止图像,显然,您无法将视图的整个行为(附加的手势识别器等)都捕获到这样的节点上。对于这些事情,我建议您打开一个新问题(在完成此操作后,您会感到满意)。

根据Fabio的评论进行编辑:

我忘记了UIView在另一个队列/线程上被调用。实际上,您应该只在主线程上生成视图图像,因为警告“ UIView.bounds必须仅从主线程使用”。我更改了上面的代码以反映这一点。请记住,swift本质上不是线程安全的,上述工作是因为renderer(_:nodeFor:)将在渲染器开始发送viewDidLoad()调用之前(在主线程上)被调用。因此,您可以确定检索图像时不会出现访问冲突,但这意味着您不应在主线程的某个位置修改所述图像。

如果您需要以某种方式更改图像/即时创建图像,则可以使用另一种方法。我个人将使用委托人的另一种方法renderer(_:nodeFor:),而不是在renderer(_:didAdd:for:)中自己创建节点。此方法不期望返回值,因此如下所示:

renderer(_:nodeFor:)

通过从委托中省去var myView: UIView? override func viewDidLoad() { super.viewDidLoad() myView = Bundle.main.loadNibNamed("ARViewOne", owner: nil, options: nil).first as? UIView } func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) { DispatchQueue.main.async { self.attachCustomNode(to: node, for: anchor) } } func attachCustomNode(to node: SCNNode, for anchor: ARAnchor) { guard let theView = myView, self.detectedDataAnchor?.identifier == anchor.identifier else { return } let box = SCNBox(width: 0.1, height: 0.1, length: 0.1, chamferRadius: 0.0) let imageMaterial = SCNMaterial() imageMaterial.diffuse.contents = theView.asImage() box.materials = [imageMaterial] let cube = SCNNode(geometry: box) node.addChildNode(cube) return node } ,ARKit会为您创建一个空节点。基本上,这与您在renderer(_:nodeFor:)中所做的相同。然后,它调用renderer(_:nodeFor:),它不期望返回值。因此,从那里“切换到主队列”并添加必要的子级。

我很确定这是可行的,IIRC SceneKit会自动在其渲染线程中批处理添加项(即,它在节点renderer(_:didAdd:for:)中的实际作用)。因此,以上所有“准备工作”都发生在主线程上,您可以在其中安全地使用视图的addChildNode()来创建图像。节点已设置好,添加节点时,SceneKit会在其渲染线程上正确执行此操作。

您应该要做的只是将bounds放在您的DispatchQueue.main.async方法中,然后在其中更改材质的扩散含量。那将意味着您从另一个线程修改了添加的节点,但是您不能确定它是否可以正常工作而不会产生冲突。看起来好像可行,而且我不知道SceneKit是否能以某种方式防止这种情况发生,但我不会依靠它。反正这是个不好的风格。

我希望这一切足以使您的项目正常进行。 :)