UIImage from SpriteKit scene

Sometimes you find yourself in a situation, when you need to snapshot a UIImage from SpriteKit, to export into the Photo Library, or a thumbnail to show up somewhere, or probably more often a shot just to blur it while you overlay some transparent UIKit content on the top of it. I was just in such situation lately, and after some trial and error I ended up with a UIImage.

This article can spare you this exploring period. If you’re in a hurry, just scroll to the end. Anyway, if you’re in a hurry you probably do not even read this intro paragraph.

UIImage from SpriteKit using SKTexture

This approach was the first try, since I recalled an SKView method -textureFromNode: that takes an SKTexture snapshot of any node. As this is something resulting in a bitmap, it was seemingly a good starting point.

Exported a texture of the whole scene, then the story ends here. I was using snapshot to apply a gaussian blur filter, so I put a sprite with this snapshot into an SKEffectNode, then applied a CIFilter.

Every CIFilter has an outputImage property that holds an image of the result. Even the first sentence of the documentation goes “The CIFilter class produces a CIImage object as output.”, but any CIFilter in SpriteKit just do not hold any output image after processing. I made several attempts, UIKit comparisons, but without results.

UIImage from SpriteKit using Core Animation renderInContext

Next attempt was the classic solution, render the SKView layer into a new image context using CALayer method renderInContext. Like many snapshot during the past years, the code just goes below.

-(UIImage*)snapshot
{
    // Captures UIKit content only.
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return snapshotImage;
}

But the resulting image was empty. To be more precise, the UIKit subviews of the SKView were exported, but the scene content just did not showed up on the snapshot.

Probably the scene just did not rendered for that run loop frame at the time the layers were rendered. Maybe with putting this method to the very end of the run loop somehow it could be the solutiong, but there is a more simple yet iOS 7 only method out there.

UIImage from SpriteKit using iOS 7 UIKit features

Unlike the above, this method works.

-(UIImage*)snapshot
{
    // Captures SpriteKit content!
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
    UIImage *snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return snapshotImage;
}

There are some new method providing screenshots of UIView hierarchies in iOS 7, possibly Apple engineers noticed nearly the same as just we do. So iOS 7 introduces a method -drawViewHierarchyInRect:afterScreenUpdates: that captures the content of a UIView after the screen has updated for that frame. At that time the SKView content is rendered, so is captured into the snapshot. Once you have the snapshot in the context, UIImage exporting goes like it used to be.

As you may noticed, this method has a bounds input that can capture the view content into a smaller image than the original view. This input is suitable for performance optimization, especially when you’re to blur the resulting image. A blurred view simply don’t have to be full sized, since upscaling is hardly noticable on the blurred images. The image quality per performance trade is the matter of your taste here. Another article here called Create iOS 7 blur effect with latest APIs just discuss this issue.

Happy scene exporting for everyone.

DISCLAIMER. THE INFORMATION ON THIS BLOG (INCLUDING BUT NOT LIMITED TO ARTICLES, IMAGES, CODE SNIPPETS, SOURCE CODES, EXAMPLE PROJECTS, ETC.) IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE INFORMATION ON THIS BLOG (INCLUDING BUT NOT LIMITED TO ARTICLES, IMAGES, CODE SNIPPETS, SOURCE CODES, EXAMPLE PROJECTS, ETC.).

Become a Patron!
  • Stuart Welsh

    Is it possible to get a UIImage from just a sprite as opposed to a view or scene?

    I’ve tried using textureFromNode but this doesn’t work when using a retina display.