The code below captures a screenshot of SCNView1, but it's not quite working.
SCNView1 contains Node1. The goal is to capture SCNView1 without Node1.
However, setting afterScreenUpdates
to true (or false) doesn't help: the screenshot contains Node1 no matter what.
What's the problem?
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
}
We're dealing with two different systems here that don't necessarily communicate: the SceneKit
rendering engine and the UIKit
/CoreGraphics
drawing system that's trying to capture the screenshot. When you hide a SCNNode
, it doesn't instantly invalidate the view for redrawing like hiding a UIView
subview would, so setting afterScreenUpdates
to true
doesn't have an effect at this point in time. After the node is marked as hidden, after we know the SceneKit
render loop has completed once, and after the view is ready to be redrawn on screen, we can then capture the screenshot. We can listen to events in the render loop by creating a SCNSceneRendererDelegate
for the SCNView
(SCNSceneRendererDelegate). We can implement the renderer:didRenderScene:atTime:
delegate method.
Assign a delegate to your scene view (scnView.delegate = self
).
When you want to do your screen capture, hide the node and set a boolean flag that's in the same scope as the delegate to true
:
Node1.hidden = true
screenCaptureFlag = true // screenCaptureFlag is a controller property
Then you'll implement your delegate:
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)
}
}
}
This delegate method isn't perfect for us because even though the scene is rendered, it isn't yet ready to redraw the view on screen because this delegate method is giving us a chance to do any custom rendering that we want. Also, if you try to call drawViewHierarchyInRect:afterScreenUpdates:
or snapshot
here, SceneKit rendering will completely stop (both in simulator and on device for iOS 9.3), which may be a SceneKit bug. We don't have a better option for delegate methods, but my solution is to dispatch an async call on the main queue which will run after the SceneKit render loop is fully completed and the rendered image is ready to be redrawn on screen. If you use drawViewHierarchyInRect:afterScreenUpdates:
, make sure afterScreenUpdates
is set to true
because it appears that at this point the actual drawing still hasn't been performed yet. And as I'm sure you know, make sure the async call operates on the main thread, because UIKit
objects should never be touched from a different thread.
By the way, I replicated your problem and found my solution using the default SceneKit project template provided by Xcode 7.3 (the one with the spinning ship). I toggle the ship's hidden property every time the scene view is tapped and then set the flag to capture a screenshot. We can use this template as a basis for further discussion if needed.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With