Pregunta iOS: ¿cuál es la forma más rápida y efectiva de realizar una captura de pantalla de forma programática?


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.


75
2017-11-01 08:42


origen


Respuestas:


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

110
2017-11-05 00:41




EDICIÓN 3 de octubre de 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

Por esto, podrías ser capaz de decir [self.view.window imageRepresentation] en un controlador de vista, y obtenga 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: Recortando un UIImage


18
2017-11-01 10:25



iOS 7 introdujo un nuevo método que le permite dibujar una jerarquía de vista en el contexto gráfico actual. Esto puede usarse para obtener un UIImage muy rapido.

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 existente renderInContext: método.

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

9
2017-09-20 20:44



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

3
2018-01-09 06:14



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.


1
2017-11-07 23:34



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 :.


1
2017-10-11 21:26