web-dev-qa-db-fra.com

iOS: quel est le moyen le plus rapide et le plus performant de faire une capture d'écran par programmation?

dans mon application iPad, je voudrais faire une capture d'écran d'une UIView prenant une grande partie de l'écran. Malheureusement, les sous-vues sont imbriquées assez profondément, il faut donc beaucoup de temps pour faire la capture d'écran et animer une page qui s'enroule ensuite.

Existe-t-il un moyen plus rapide que celui "habituel"?

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

Si possible, je voudrais éviter de mettre en cache ou de restructurer ma vue.

77
swalkner

J'ai trouvé une meilleure méthode qui utilise l'API snapshot autant que possible.

J'espère que ça aide.

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

Vous voulez en savoir plus sur les instantanés iOS 7?

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;
}
111
3lvis

EDIT 3 octobre 2013 Mis à jour pour prendre en charge la nouvelle méthode drawViewHierarchyInRect: afterScreenUpdates: ultra rapide dans iOS 7.


Non. CALINER renderInContext: autant que je sache, c'est la seule façon de le faire. Vous pouvez créer une catégorie UIView comme celle-ci, pour vous faciliter la tâche à l'avenir:

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

Par cela, vous pourriez dire [self.view.window imageRepresentation] dans un contrôleur de vue et obtenez une capture d'écran complète de votre application. Cela pourrait cependant exclure la barre d'état.

MODIFIER:

Et puis-je ajouter. Si vous avez une vue UIV avec un contenu transparent et avez également besoin d'une représentation d'image AVEC le contenu sous-jacent, vous pouvez saisir une représentation d'image de la vue du conteneur et recadrer cette image, simplement en prenant le rect de la sous-vue et en la convertissant en conteneur vues système de coordonnées.

[view convertRect:self.bounds toView:containerView]

Pour recadrer, voir la réponse à cette question: Recadrage d'un UIImage

18
Trenskow

iOS 7 a introduit une nouvelle méthode qui vous permet de dessiner une hiérarchie de vues dans le contexte graphique actuel. Cela peut être utilisé pour obtenir un UIImage très rapidement.

Implémenté comme méthode de catégorie sur UIView:

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

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

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

Il est considérablement plus rapide que le renderInContext: méthode.

PDATE FOR Swift: Une extension qui fait de même:

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

J'ai combiné les réponses à une seule fonction qui fonctionnera pour toutes les versions d'iOS, même pour les appareils rétiniens ou non conservés.

- (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
Hemang

Pour moi, définir InterpolationQuality a beaucoup évolué.

CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);

Si vous prenez des photos très détaillées, cette solution peut ne pas être acceptable. Si vous prenez un instantané de texte, vous remarquerez à peine la différence.

Cela a permis de réduire considérablement le temps nécessaire pour prendre la photo et de créer une image qui consomme beaucoup moins de mémoire.

Cela est toujours bénéfique avec la méthode drawViewHierarchyInRect: afterScreenUpdates :.

2
maxpower

Ce que vous demandez comme alternative, c'est de lire le GPU (puisque l'écran est composé de plusieurs vues translucides), ce qui est aussi une opération intrinsèquement lente.

1
David Dunham