iOS:以编程方式制作屏幕截图的最快、最高效的方法是什么?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/7964153/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
iOS: what's the fastest, most performant way to make a screenshot programmatically?
提问by swalkner
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.
在我的 iPad 应用程序中,我想制作一个 UIView 的屏幕截图,该屏幕占据了大部分屏幕。不幸的是,子视图嵌套得很深,所以制作屏幕截图和动画页面卷曲需要很长时间。
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.
如果可能,我想避免缓存或重构我的视图。
回答by 3lvis
I've found a better method that uses the snapshot API whenever possible.
我找到了一种更好的方法,它尽可能使用快照 API。
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:
Objective-C 版本:
+ (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;
}
回答by Trenskow
EDIT October 3. 2013Updated to support the new super fast drawViewHierarchyInRect:afterScreenUpdates: method in iOS 7.
编辑 2013 年 10 月 3 日更新以支持 iOS 7 中新的超快速 drawViewHierarchyInRect:afterScreenUpdates: 方法。
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:
否。 CALayer 的 renderInContext: 是据我所知的唯一方法。你可以像这样创建一个 UIView 类别,让自己更容易前进:
UIView+Screenshot.h
UIView+截图.h
#import <UIKit/UIKit.h>
@interface UIView (Screenshot)
- (UIImage*)imageRepresentation;
@end
UIView+Screenshot.m
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.
通过这个,你可以[self.view.window imageRepresentation]
在视图控制器中说,并获得你的应用程序的完整屏幕截图。不过,这可能会排除状态栏。
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.
我可以补充一下。如果您有一个带有透明内容的 UIView,并且还需要带有底层内容的图像表示,您可以抓取容器视图的图像表示并裁剪该图像,只需获取子视图的矩形并将其转换为容器视图坐标系。
[view convertRect:self.bounds toView:containerView]
To crop see answer to this question: Cropping an UIImage
要裁剪,请参阅此问题的答案:Cropping an UIImage
回答by Klaas
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.
iOS 7 引入了一种新方法,允许您将视图层次结构绘制到当前图形上下文中。这可以用来获得UIImage
非常快的速度。
Implemented as category method on UIView
:
作为类别方法实现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.
它比现有renderInContext:
方法快得多。
UPDATE FOR SWIFT: An extension that does the same:
更新 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;
}
}
回答by Hemang
I combined the answers to single function which will be running for any iOS versions, even for retina or non-retains devices.
我结合了单个功能的答案,这些功能将在任何 iOS 版本上运行,即使是视网膜或非保留设备。
- (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;
}
回答by maxpower
For me setting the InterpolationQuality went a long way.
对我来说,设置 InterpolationQuality 有很长的路要走。
CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);
If you are snapshotting very detailed images this solution may not be acceptable. If you are snapshotting text you will hardly notice the difference.
如果您要拍摄非常详细的图像,则此解决方案可能无法接受。如果您正在对文本进行快照,您几乎不会注意到差异。
This cut down the time to take the snap shot significantly as well as making an image that consumed far less memory.
这显着减少了拍摄快照的时间,并减少了消耗更少内存的图像。
This is still beneficial with the drawViewHierarchyInRect:afterScreenUpdates: method.
这对于 drawViewHierarchyInRect:afterScreenUpdates: 方法仍然有益。
回答by David Dunham
What you're asking for as an alternative is to read the GPU (since the screen is composited from any number of translucent views), which is an inherently slow operation too.
作为替代方案,您要求的是读取 GPU(因为屏幕是由任意数量的半透明视图合成的),这本身也是一种缓慢的操作。