我们在这里处理两个不一定通信的不同系统:SceneKit 渲染引擎和试图捕获屏幕截图的UIKit/CoreGraphics 绘图系统。当您隐藏SCNNode 时,它不会像隐藏UIView 子视图那样立即使视图无效以进行重绘,因此将afterScreenUpdates 设置为true 在此时没有效果。在节点被标记为隐藏之后,在我们知道SceneKit 渲染循环已经完成一次之后,并且在视图准备好在屏幕上重绘之后,我们就可以截屏了。我们可以通过为SCNView (SCNSceneRendererDelegate) 创建一个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 对象。
顺便说一句,我复制了您的问题,并使用 Xcode 7.3(带有旋转船的那个)提供的默认 SceneKit 项目模板找到了我的解决方案。每次点击场景视图时,我都会切换船的隐藏属性,然后设置标志以捕获屏幕截图。如果需要,我们可以将此模板作为进一步讨论的基础。