Ладно, сначала обо всем по порядку:
Вы не можете добавить UIView
как contents
из SCNMaterialProperty
.Прочитайте здесь .
Я догадываюсь здесь, но это, вероятно, причина остановки / сбоя, которую вы видите, поскольку сцена становится деактивированной, когда вы вставляете контроллер в нее и пытаетесьочистить тот материал, который на самом деле сломан (он будет относиться к нему как к другому типу).
Вообще говоря, строка
myView = (Bundle.main.loadNibNamed("ARViewOne", owner: nil, options: nil)![0] as? UIView)!
выглядит для меня как будто вы немного новичокэто (что совершенно нормально!).Вы разворачиваете силу и применяете силу (странным образом, as? UIView!
- это просто as! UIView
), что всегда опасно.Вы уверены, что первым объектом верхнего уровня в вашей xib является 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)
}
}
}
(Расширение UIView
было взято из здесь , так что спасибо Навиду Дж.)
Это предполагает, что а) ваш xib действительно правильный иб) вы просто хотите «внешний вид вида», то есть своего рода «скриншот» на узле.Это будет неподвижное изображение, очевидно, вы не можете получить полное поведение представления (прикрепленные распознаватели жестов и тому подобное) на таком узле, как этот.Для этих вещей я бы предложил открыть новый вопрос (после того, как вы сделали это здесь, к вашему удовлетворению).
Редактировать в ответ на комментарий Фабио:
Я забылчто renderer(_:nodeFor:)
вызывается в другой очереди / потоке.Вы действительно должны генерировать изображение вида только в главном потоке, как говорится в предупреждении «UIView.bounds следует использовать только из основного потока».Я изменил код выше, чтобы отразить это.Имейте в виду, что swift не является поточно-ориентированным по своей природе, выше работает, так как 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
}
Опуская renderer(_:nodeFor:)
от вашего делегата, ARKit создает пустой узел для вас.По сути, это то же самое, что вы делаете в любом случае в renderer(_:nodeFor:)
.Затем он вызывает renderer(_:didAdd:for:)
, который не ожидает возвращаемого значения.Поэтому оттуда я «переключаюсь в основную очередь» и добавляю нужного потомка.
Я почти уверен, что это работает, IIRC SceneKit автоматически упаковывает дополнение (то есть, что оно фактически делает внутри addChildNode()
) узла в его потоке рендеринга.Таким образом, вся «подготовительная работа» выше происходит в главном потоке, где вы можете безопасно использовать bounds
представления для создания изображения.Узел настроен, и когда вы добавляете его, SceneKit позаботится о том, чтобы сделать это правильно в потоке рендеринга.
То, что вы должны не сделать, это просто поместить DispatchQueue.main.async
внутри вашего renderer(_:nodeFor:)
метод и изменить там диффузное содержание материала.Это означает, что вы изменяете добавленный узел из другого потока, но вы не можете быть уверены, что он работает без конфликтов.Может показаться, что это работает, и я не знаю, защищает ли SceneKit от этого, но я бы не стал полагаться на это.В любом случае, это плохой стиль.
Надеюсь, этого достаточно, чтобы ваш проект заработал.:)