iOS: ¿cuál es la forma más rápida y efectiva de realizar una captura de pantalla de forma programática?
performance core-graphics (6)
Combiné las respuestas a la función única que se ejecutará para cualquier versión de iOS, incluso para dispositivos retina o no retenidos.
- (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;
}
en mi aplicación para iPad, me gustaría hacer una captura de pantalla de una UIView ocupando una gran parte de la pantalla. Desafortunadamente, las subvistas están bastante anidadas, por lo que toma mucho tiempo hacer la captura de pantalla y animar una página que se encrespa después.
¿Hay una manera más rápida que la "habitual"?
UIGraphicsBeginImageContext(self.bounds.size);
[self.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *resultingImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
Si es posible, me gustaría evitar el almacenamiento en caché o la reestructuración de mi vista.
He encontrado un método mejor que usa la API de instantáneas siempre que sea posible.
Espero que ayude.
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!
}
¿Quieres saber más sobre las instantáneas de iOS 7?
Versión 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;
}
Lo que está pidiendo como alternativa es leer la GPU (ya que la pantalla se compone de cualquier número de vistas translúcidas), que también es una operación inherentemente lenta.
Para mí, configurar la InterpolationQuality fue un largo camino.
CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);
Si toma instantáneas de imágenes muy detalladas, esta solución puede no ser aceptable. Si está haciendo un snapshot de texto, apenas notará la diferencia.
Esto redujo el tiempo para tomar la instantánea de manera significativa, así como para crear una imagen que consume mucha menos memoria.
Esto sigue siendo beneficioso con el método drawViewHierarchyInRect: afterScreenUpdates :.
iOS 7 introdujo un nuevo método que le permite dibujar una jerarquía de vista en el contexto gráfico actual. Esto se puede usar para obtener un UIImage
muy rápido.
Implementado como método de categoría en UIView
:
- (UIImage *)pb_takeSnapshot {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);
[self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
Es considerablemente más rápido que el método renderInContext:
ACTUALIZAR PARA SWIFT : una extensión que hace lo mismo:
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;
}
}
EDITAR el 3 de octubre. 2013 Actualizado para admitir el nuevo método súper rápido drawViewHierarchyInRect: afterScreenUpdates: en iOS 7.
No. El renderizado de CALayer: hasta donde yo sé, es la única forma de hacerlo. Puede crear una categoría UIView como esta, para que sea más fácil para usted en el futuro:
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
Con esto, puede decir [self.view.window imageRepresentation]
en un controlador de vista y obtener una captura de pantalla completa de su aplicación. Esto podría excluir la barra de estado.
EDITAR:
Y puedo agregar Si tiene un UIView con contenido transparente y necesita una representación de imagen CON el contenido subyacente también, puede tomar una representación de imagen de la vista de contenedor y recortar esa imagen, simplemente tomando el rect de la subvista y convirtiéndola en el contenedor vistas del sistema de coordenadas.
[view convertRect:self.bounds toView:containerView]
Para recortar, ver la respuesta a esta pregunta: Recortar un UIImage