IOS: какой самый быстрый, самый эффективный способ сделать скриншот программным путем?
в моем приложении для iPad, я хотел бы сделать снимок экрана UIView, занимающий большую часть экрана. К сожалению, subviews довольно глубоко вложены, поэтому требуется сделать снимок экрана и анимировать страницу, скручивающуюся впоследствии.
Есть ли более быстрый способ, чем обычный?
UIGraphicsBeginImageContext(self.bounds.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Если возможно, я бы хотел избежать кэширования или реструктуризации моего представления.
Ответы
Ответ 1
Я нашел лучший метод, который по возможности использует API-интерфейс моментальных снимков.
Надеюсь, это поможет.
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!
}
Хотите узнать больше о моментальных снимках iOS 7?
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;
}
Ответ 2
РЕДАКТИРОВАТЬ 3 октября 2013 года
Обновлен для поддержки нового супер быстрого drawViewHierarchyInRect: afterScreenUpdates: метод в iOS 7.
Нет. CALayer renderInContext: насколько я знаю, единственный способ сделать это. Вы можете создать такую категорию UIView, чтобы облегчить себе переход:
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]
в контроллере представления и получить полный снимок экрана вашего приложения. Однако это может исключать статус-бар.
EDIT:
И я могу добавить. Если у вас есть UIView с прозрачным контентом и требуется представление изображения. С содержимым подстилающего слоя вы также можете захватить представление изображения в виде контейнера и обрезать это изображение, просто взяв прямоугольник надзора и преобразовывая его в контейнер системы координат взглядов.
[view convertRect:self.bounds toView:containerView]
Обрезать см. ответ на этот вопрос: Обрезка UIImage
Ответ 3
В iOS 7 появился новый метод, который позволяет рисовать иерархию представлений в текущем графическом контексте. Это можно использовать, чтобы получить 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;
}
}
Ответ 4
Я объединил ответы на одну функцию, которая будет работать для любых версий 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;
}
Ответ 5
То, что вы просите в качестве альтернативы, - это прочитать графический процессор (поскольку экран скомпонован из любого количества полупрозрачных представлений), который также является слишком медленной операцией.
Ответ 6
Для меня настройка InterpolationQuality прошла долгий путь.
CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);
Если вы снимете очень подробные изображения, это решение может быть неприемлемым. Если вы снимаете текст, вы вряд ли заметите разницу.
Это сократило время, чтобы сделать съемку значительно, а также сделать изображение, которое потребляло гораздо меньше памяти.
Это по-прежнему полезно с помощью метода drawViewHierarchyInRect: afterScreenUpdates:.