ios – 使用隐藏的SCNNode捕获SCNView的图像/屏幕截图:在屏幕更新后不能正常工作的drawViewHierarchyInRect

下面的代码捕获了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项目模板找到了我的解决方案.每次点击场景视图时,我都会切换船的隐藏属性,然后设置标记以捕获屏幕截图.如果需要,我们可以使用此模板作为进一步讨论的基础.

相关文章

UITabBarController 是 iOS 中用于管理和显示选项卡界面的一...
UITableView的重用机制避免了频繁创建和销毁单元格的开销,使...
Objective-C中,类的实例变量(instance variables)和属性(...
从内存管理的角度来看,block可以作为方法的传入参数是因为b...
WKWebView 是 iOS 开发中用于显示网页内容的组件,它是在 iO...
OC中常用的多线程编程技术: 1. NSThread NSThread是Objecti...