iOS: what's the fastest, most performant way to make a screenshot programmatically?
in my iPad app, I'd like to make a screenshot of a UIView taking a big part of the screen. Unfortunately, the subviews are pretty deeply nested, so it takes to long to make the screenshot and animate a page curling afterwards.
Is there a faster way than the "usual" one?
UIGraphicsBeginImageContext(self.bounds.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
If possible, I'd like to avoid caching or restructuring my view.
I've found a better method that uses the snapshot API whenever possible.
I hope it helps.
class func screenshot() -> UIImage {
var imageSize = CGSize.zero
let orientation = UIApplication.shared.statusBarOrientation
if UIInterfaceOrientationIsPortrait(orientation) {
imageSize = UIScreen.main.bounds.size
} else {
imageSize = CGSize(width: UIScreen.main.bounds.size.height, height: UIScreen.main.bounds.size.width)
}
UIGraphicsBeginImageContextWithOptions(imageSize, false, 0)
for window in UIApplication.shared.windows {
window.drawHierarchy(in: window.bounds, afterScreenUpdates: true)
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image!
}
Wanna know more about iOS 7 Snapshots?
Objective-C version:
+ (UIImage *)screenshot
{
CGSize imageSize = CGSizeZero;
UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
if (UIInterfaceOrientationIsPortrait(orientation)) {
imageSize = [UIScreen mainScreen].bounds.size;
} else {
imageSize = CGSizeMake([UIScreen mainScreen].bounds.size.height, [UIScreen mainScreen].bounds.size.width);
}
UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
CGContextRef context = UIGraphicsGetCurrentContext();
for (UIWindow *window in [[UIApplication sharedApplication] windows]) {
CGContextSaveGState(context);
CGContextTranslateCTM(context, window.center.x, window.center.y);
CGContextConcatCTM(context, window.transform);
CGContextTranslateCTM(context, -window.bounds.size.width * window.layer.anchorPoint.x, -window.bounds.size.height * window.layer.anchorPoint.y);
if (orientation == UIInterfaceOrientationLandscapeLeft) {
CGContextRotateCTM(context, M_PI_2);
CGContextTranslateCTM(context, 0, -imageSize.width);
} else if (orientation == UIInterfaceOrientationLandscapeRight) {
CGContextRotateCTM(context, -M_PI_2);
CGContextTranslateCTM(context, -imageSize.height, 0);
} else if (orientation == UIInterfaceOrientationPortraitUpsideDown) {
CGContextRotateCTM(context, M_PI);
CGContextTranslateCTM(context, -imageSize.width, -imageSize.height);
}
if ([window respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)]) {
[window drawViewHierarchyInRect:window.bounds afterScreenUpdates:YES];
} else {
[window.layer renderInContext:context];
}
CGContextRestoreGState(context);
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
EDIT October 3. 2013 Updated to support the new super fast drawViewHierarchyInRect:afterScreenUpdates: method in iOS 7.
No. CALayer's renderInContext: is as far as I know the only way to do this. You could create a UIView category like this, to make it easier for yourself going forward:
UIView+Screenshot.h
#import <UIKit/UIKit.h>
@interface UIView (Screenshot)
- (UIImage*)imageRepresentation;
@end
UIView+Screenshot.m
#import <QuartzCore/QuartzCore.h>
#import "UIView+Screenshot.h"
@implementation UIView (Screenshot)
- (UIImage*)imageRepresentation {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, YES, self.window.screen.scale);
/* iOS 7 */
if ([self respondsToSelector:@selector(drawViewHierarchyInRect:afterScreenUpdates:)])
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:NO];
else /* iOS 6 */
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage* ret = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return ret;
}
@end
By this you might be able to say [self.view.window imageRepresentation]
in a view controller, and get a full screenshot of your app. This might exclude the statusbar though.
EDIT:
And may I add. If you have an UIView with transparent content, and needs an image representation WITH the underlaying content as well, you can grab an image representation of the container view and crop that image, simply by taking the rect of the subview and converting it to the container views coordinate system.
[view convertRect:self.bounds toView:containerView]
To crop see answer to this question: Cropping an UIImage
iOS 7 introduced a new method that allows you to draw a view hierarchy into the current graphics context. This can be used to get an UIImage
very fast.
Implemented as category method on UIView
:
- (UIImage *)pb_takeSnapshot {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
It is considerably faster then the existing renderInContext:
method.
UPDATE FOR SWIFT: An extension that does the same:
extension UIView {
func pb_takeSnapshot() -> UIImage {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, UIScreen.mainScreen().scale);
self.drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)
// old style: self.layer.renderInContext(UIGraphicsGetCurrentContext())
let image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
}
I combined the answers to single function which will be running for any iOS versions, even for retina or non-retains devices.
- (UIImage *)screenShot {
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, [UIScreen mainScreen].scale);
else
UIGraphicsBeginImageContext(self.view.bounds.size);
#ifdef __IPHONE_7_0
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000
[self.view drawViewHierarchyInRect:self.view.bounds afterScreenUpdates:YES];
#endif
#else
[self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
#endif
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}