好吧,那么首先:
UIView
作为
contents
的
SCNMaterialProperty
. 读出
here
.
一般来说
myView = (Bundle.main.loadNibNamed("ARViewOne", owner: nil, options: nil)![0] as? UIView)!
as? UIView!
as! UIView
),这总是很危险的。确定xib中的第一个顶级对象是
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)
}
}
}
(The)
UIView视图
here
,所以谢谢纳维德J.)
这假设a)您的xib确实正确,b)您只需要“视图的外观”,即节点上的一种“屏幕截图”。这将是一个静态图像,很明显,你不能把视图的整个行为(附加的手势识别器等)放到这样的节点上。对于这些事情,我建议你打开一个新的问题(在你满意的完成这个问题之后)。
针对法比奥的评论编辑:
我忘了
renderer(_:nodeFor:)
在不同的队列/线程上被调用。您确实应该只在主线程上生成视图图像,正如警告“UIView.bounds must be used from main thread only”所说的那样。我修改了上面的代码来反映这一点。请记住,swift本身并不是线程安全的,上面的工作原理是
viewDidLoad()
将在渲染器开始发送之前调用(在主线程上)
如果您需要以某种方式更改图像/动态创建它,有一种替代方法。就我个人而言,我会使用代理的其他方法,
renderer(_:didAdd:for:)
,而不是自己在中创建节点
渲染器(\: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
}
通过省略
渲染器(\:nodeFor:)
ARKit从您的委托中为您创建一个空节点。基本上这和你在工作中做的事情是一样的
渲染器(\:nodeFor:)
. 然后它会呼叫
,它不期望返回值。因此,我从那里“切换到主队列”并添加必要的子队列。
我很确定这是可行的,IIRC SceneKit会自动批处理添加(即它在节点的
addChildNode()
bounds
创建图像。节点得到设置,当您添加它时,SceneKit会在其渲染线程上正确地进行设置。
你应该做什么
不
DispatchQueue.main.async
渲染器(\:nodeFor:)
方法并更改其中材质的漫反射内容。这意味着您可以从另一个线程修改添加的节点,但是您不能确保这样做没有冲突。它看起来可能有用,我不知道SceneKit是否能防范这种情况,但我不相信这一点。不管怎么说,这是一种糟糕的风格。