Здесь мы имеем дело с двумя разными системами, которые не обязательно взаимодействуют друг с другом: механизм рендеринга SceneKit
и система рисования UIKit
/CoreGraphics
, которая пытается сделать снимок экрана. Когда вы скрываете SCNNode
, это не делает представление недействительным мгновенно для перерисовки, как скрытие подвида UIView
, поэтому установка afterScreenUpdates
на true
не имеет эффекта в данный момент времени. После того, как узел помечен как скрытый, после того, как мы знаем, что цикл рендеринга SceneKit
завершился один раз, и после того, как представление готово к перерисовке на экране, мы можем сделать снимок экрана. Мы можем прослушивать события в цикле рендеринга, создав SCNSceneRendererDelegate
для SCNView
(SCNSceneRendererDelegate). Мы можем реализовать метод делегата renderer:didRenderScene:atTime:
.
Назначьте делегата для просмотра сцены (scnView.delegate = self
). Если вы хотите сделать снимок экрана, скройте узел и установите логический флаг, который находится в той же области, что и делегат true
:
Node1.hidden = true
screenCaptureFlag = true // screenCaptureFlag is a controller property
Затем вы реализуете свой делегат:
func renderer(renderer: SCNSceneRenderer, didRenderScene scene: SCNScene, atTime time: NSTimeInterval) {
if screenCaptureFlag {
screenCaptureFlag = false // unflag
let scnView = self.view as! SCNView
// Dispatch asynchronously to main queue
// Will run once SCNView is ready to be redrawn on screen
// Also avoids SceneKit rendering freeze
dispatch_async(dispatch_get_main_queue()) {
// Get your screenshot the simple way
let screenshot = scnView.snapshot()
// Or use your function
// let screenshot = self.turnViewToImage(scnView, opaque: false, afterUpdates: true)
// Then save the screenshot, or do whatever you want
let urlPath = NSURL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first!).URLByAppendingPathComponent("Image.png")
try! UIImagePNGRepresentation(screenshot)!.writeToURL(urlPath, options: .AtomicWrite)
}
}
}
Этот метод делегата не идеален для нас, потому что, хотя сцена визуализируется, она еще не готова к перерисовке представления на экране, потому что этот метод делегата дает нам возможность выполнить любой пользовательский рендеринг, который мы хотим. Кроме того, если вы попытаетесь вызвать здесь drawViewHierarchyInRect:afterScreenUpdates:
или snapshot
, рендеринг SceneKit полностью остановится (как в симуляторе, так и на устройстве для iOS 9.3), что может быть ошибкой SceneKit. У нас нет лучшего варианта для методов делегата, но мое решение состоит в том, чтобы отправить асинхронный вызов в основную очередь, которая запустится после того, как цикл рендеринга SceneKit полностью завершится и отрендеренное изображение будет готово для перерисовки на экране. Если вы используете drawViewHierarchyInRect:afterScreenUpdates:
, убедитесь, что afterScreenUpdates
установлено на true
, потому что кажется, что на данный момент фактическое рисование еще не выполнено. И, как я уверен, вы знаете, убедитесь, что асинхронный вызов работает в основном потоке, потому что UIKit
объекты никогда не должны касаться другого потока.
Кстати, я воспроизвел вашу проблему и нашел свое решение, используя шаблон проекта SceneKit по умолчанию, предоставленный Xcode 7.3 (тот, что с вращающимся кораблем). Я переключаю скрытое свойство корабля каждый раз, когда нажимаю на вид сцены, а затем устанавливаю флаг, чтобы сделать снимок экрана. При необходимости мы можем использовать этот шаблон в качестве основы для дальнейшего обсуждения.
person
Christopher Whidden
schedule
01.05.2016