Screen Capture in iOS Apps

by Bob McCune on September 8, 2011

I occasionally come across the need to grab the contents of a view as an image. This is often the result of needing to perform some non-stock, animated transition between views, but there are a variety of reasons why this might be useful. Thanks to the Core Animation framework’s CALayer class, this is easy to do.

All UIView instances have an underlying instance of a CALayer. The layer is responsible for rendering the view’s contents and performing any view-related animations. CALayer defines a method called renderInContext: which allows you to render the layer, and its sublayers, into a given graphics context:

Before you can access any layer-specific APIs, you’ll need to make sure you’re linking against the QuartzCore framework. Xcode’s default templates don’t link against this framework so you’ll need to select Target Name > Build Phases > Link Binary With Libraries and select QuartzCore.framework.

Additionally, you’ll need to add the following import to your code wherever you are calling the layer’s properties or methods:

With the necessary project configuration out of the way, the next question is where do we get a graphics context into which we can render the view’s content? This can be created using UIKit’s UIGraphicsBeginImageContextWithOptions function which will create a new bitmap-based graphics context for us.

This function takes a CGSize (you view’s size), a BOOL indicating if your view is opaque, and a CGFloat specifying the scaling factor. If you’re rendering a fully opaque, rectangular view you can pass YES for the opaque argument so the alpha channel can be discarded from the context.

Now that we’ve created a graphics context we can use the UIGraphicsGetCurrentContext() and UIGraphicsGetImageFromCurrentImageContext() functions to get reference to this new context and retrieve the rendered image data from it. Finally, we’ll want to call the UIGraphicsEndImageContext() function to clean up this context and remove it from the stack. Putting all this together we end up with the following:

To see this code in action I’ve put together a simple demo app. You can tap on Archer, Lana, or the background to capture the contents of the view and write the image to your Photo Library.

Note: Before running the demo be sure to open the “Photos” app on the Simulator so it can initialize its database or the images won’t be written. Enjoy!

Download Demo

[ 9 comments ]

Mark September 8, 2011 at 2:59 am

Maye I’m missing something, but how is this different than just pressing Power+Home buttons and capturing the screen to the camera roll?

Bob McCune September 8, 2011 at 3:05 am

Yes, you’re missing the point. Power+Home is a handy user feature, but doesn’t do anything for a developer. The intent of this post is to explain how you can capture a view (or any subview’s content) so you can use image data within your app.

Abraham Ventura October 5, 2011 at 3:53 pm

This was a great tutorial.. The best that I have seen that captures the screen.. Thanks.. and thanks for making it fun.. :) I give it a A+ and you got me as a fan.

Luke Carelsen August 3, 2012 at 10:48 am

thanks so much for the tutorial. is it possible that you can achieve the same thing using cocos2d v2.0 without xib file?
im in desperate need to find a solution, ive been awake for 2 days search the internet and have come up empty handed.

thanks in advance

Bob McCune August 3, 2012 at 11:18 am

I don’t have any experience with cocos2d, but I don’t see why not. This technique is not limited to NIB-based apps.

luke carelsen August 3, 2012 at 11:20 am

thanks for the speedy reply. imma give it a shot. im just so tired of going through examples after examples. if i do eventually come up with a working solution i will post here for anyone that might want it in the future.

kind regards

luke carelsen August 3, 2012 at 4:47 pm

i finally figured it out, i dont know how but i did. here is the code for cocos2D v2.0

-(void) cocos2DScreenShot {

CGSize size = [[CCDirector sharedDirector] winSize];

//Create un buffer for pixels
int bufferLenght=size.width*size.height*4;
unsigned char buffer[bufferLenght];

//Read Pixels from OpenGL
glReadPixels(0,0,size.width,size.height,GL_RGBA,GL_UNSIGNED_BYTE,&buffer);
CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, &buffer, bufferLenght, NULL);
CGImageRef iref = CGImageCreate(size.width,size.height,8,32,size.width*4,CGColorSpaceCreateDeviceRGB(),kCGBitmapByteOrderDefault,ref,NULL,true,kCGRenderingIntentDefault);

uint32_t *pixels = (uint32_t *)malloc(bufferLenght);

CGContextRef context = CGBitmapContextCreate(pixels, size.width, size.height, 8, size.width*4, CGImageGetColorSpace(iref), kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

CGContextTranslateCTM(context,0, size.height);
CGContextScaleCTM(context, 1.0, -1.0);

CGContextDrawImage(context, CGRectMake(0.0, 0.0, size.width, size.height), iref);
UIImage *outputImage = [UIImage imageWithCGImage:CGBitmapContextCreateImage(context)];

UIImageWriteToSavedPhotosAlbum(outputImage, nil, nil, nil);

//Dealloc
CGDataProviderRelease(ref);
CGImageRelease(iref);
CGContextRelease(context);
free(pixels);

}

Bob McCune August 3, 2012 at 6:02 pm

I’m glad you got it figured out. Thanks for sharing your solution!

luke carelsen August 4, 2012 at 7:00 am

my code above works perfectly on the simulator but when testing it on a device i get an error, hee is the error:

glReadPixels(0,0,size.width,size.height,GL_RGBA,GL_UNSIGNED_BYTE,&buffer); -Thread 1: EXC_BAD_ACCESS(code=1, address=0x2fb78120)

i have no idea to debug or even begin to understand whats going on here. can anyone maybe enlighten me?

kind regards