下面的代码捕获了SCNView1的屏幕截图,但它并没有完全正常工作.
SCNView1包含Node1.目标是在没有Node1的情况下捕获SCNView1.
但是,将afterScreenUpdates设置为true(或false)无效:屏幕截图包含Node1,无论如何.
有什么问题?
Node1.hidden = true let screenshot = turnViewToImage(SCNView1,opaque: false,afterUpdates: true) // Save screenshot to disk func turnViewToImage(targetView: UIView,opaque: Bool,afterUpdates: Bool = false) -> UIImage { UIGraphicsBeginImageContextWithOptions(targetView.bounds.size,opaque,UIScreen.mainScreen().scale) let context = UIGraphicsGetCurrentContext() CGContextSetInterpolationQuality(context,CGInterpolationQuality.High) targetView.drawViewHierarchyInRect(targetView.bounds,afterScreenUpdates: afterUpdates) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image }
解决方法
我们在这里处理两个不一定通信的系统:SceneKit渲染引擎和试图捕获屏幕截图的UIKit / CoreGraphics绘图系统.当您隐藏SCNNode时,它不会立即使重绘的视图无效,就像隐藏UIView子视图一样,因此将afterScreenUpdates设置为true在此时没有效果.在节点被标记为隐藏之后,在我们知道SceneKit渲染循环已经完成一次之后,并且在视图准备好在屏幕上重绘之后,我们可以捕获屏幕截图.我们可以通过为SCNView(
SCNSceneRendererDelegate)创建SCNSceneRendererDelegate来监听渲染循环中的事件.我们可以实现渲染器:didRenderScene:atTime:delegate方法.
将委托分配给场景视图(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,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项目模板找到了我的解决方案.每次点击场景视图时,我都会切换船的隐藏属性,然后设置标记以捕获屏幕截图.如果需要,我们可以使用此模板作为进一步讨论的基础.