IOS: 什么是最快,最有效的编程截屏方式?

在我的 iPad 应用程序中,我想要制作一个截图的 UIView 占据了屏幕的很大一部分。不幸的是,子视图是相当深的嵌套,所以它需要很长的截图和动画页卷曲后。

有没有比“通常”更快的方法?

UIGraphicsBeginImageContext(self.bounds.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

如果可能的话,我希望避免缓存或重构视图。

35099 次浏览

编辑: 2013年10月3日 更新以支持 iOS7中的新的超快绘制 ViewHi忽略: after ScreenUpdates: 方法。


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

通过这种方法,你可以在视图控制器中输入 [self.view.window imageRepresentation],然后得到应用程序的完整截图。但这可能会排除状态栏。

编辑:

容我补充一句。如果你有一个内容透明的 UiView,并且需要一个包含底层内容的图像表示,你可以抓取一个容器视图的图像表示,然后裁剪这个图像,只需要把子视图的正确部分转换成容器视图的坐标系。

[view convertRect:self.bounds toView:containerView]

裁剪请看这个问题的答案: 裁剪 UIImage

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!
}

想知道更多关于 iOS7快照的信息吗?

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;
}

你所要求的替代方案是读取 GPU (因为屏幕是由任意数量的半透明视图合成的) ,这本身也是一个缓慢的操作。

IOS7引入了一种新方法,允许您在当前图形上下文中绘制视图层次结构。这可以用来得到一个 UIImage非常快。

UIView上作为类别方法实施:

- (UIImage *)pb_takeSnapshot {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);


[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];


UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}

它比现有的 renderInContext:方法快得多。

更新为 SWIFT : 做同样事情的扩展:

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;
}
}

对我来说,设置插值质量有很大的帮助。

CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);

如果您正在快照非常详细的图像,这种解决方案可能是不可接受的。如果您正在快照文本,则几乎不会注意到其中的差异。

这大大缩短了拍摄快照的时间,同时也大大减少了对内存的消耗。

这对于 draViewHiLayer yInRect: after ScreenUpdate: 方法仍然是有益的。

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;
}