我在检测到锚点时向我的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时,我的应用程序没有响应任何触摸事件
答案 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是否能以某种方式防止这种情况发生,但我不会依靠它。反正这是个不好的风格。
我希望这一切足以使您的项目正常进行。 :)