【问题标题】:Capture image/screenshot of SCNView with SCNNode hidden: drawViewHierarchyInRect afterScreenUpdates not working捕获隐藏 SCNNode 的 SCNView 的图像/屏幕截图:drawViewHierarchyInRect afterScreenUpdates not working
【发布时间】:2016-08-22 12:49:37
【问题描述】:

下面的代码截取了 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
}

【问题讨论】:

    标签: ios swift core-graphics scenekit capture


    【解决方案1】:

    我们在这里处理两个不一定通信的不同系统: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 项目模板找到了我的解决方案。每次点击场景视图时,我都会切换船的隐藏属性,然后设置标志以捕获屏幕截图。如果需要,我们可以将此模板作为进一步讨论的基础。

    【讨论】:

    • 好的,非常感谢。另一个将被探索的选项是在捕获屏幕截图之前引入一些延迟(例如,100 毫秒)。不优雅但可能会起作用,因为用户流已经包含通过网络调用的延迟。
    • 顺便说一句,您以前遇到过类似的问题吗? stackoverflow.com/questions/36728704/…
    • 希望其他人会发布一个不需要标志变量的解决方案,但至少这是可行的。感谢您的帮助!
    • 克里斯 我似乎遇到了类似的问题。我有一个 CoreMotion 更新,每秒运行 30 次,调用一个工作方法。在该方法中,我将场景的相机设置为方向,这将为您提供一个不错的平移视图。但是,当我尝试在同一个动作更新中拍摄快照时,snapshot() 会给出 SIG 或 EXE_BAD,即使完全相同的代码用作按钮上的操作。这听起来像是同一件事吗?
    猜你喜欢
    • 1970-01-01
    • 2012-01-20
    • 1970-01-01
    • 2014-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多