Frage iOS: Was ist der schnellste und performanteste Weg, um einen Screenshot programmatisch zu erstellen?


In meiner iPad App möchte ich einen Screenshot einer UIView machen, die einen großen Teil des Bildschirms ausmacht. Leider sind die Subviews ziemlich tief verschachtelt, so dass es zu lange dauert, den Screenshot zu erstellen und eine Seite danach zu curlen.

Gibt es einen schnelleren Weg als den "Üblichen"?

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

Wenn möglich, möchte ich vermeiden, meine Sichtweise zu cachen oder zu restrukturieren.


75
2017-11-01 08:42


Ursprung


Antworten:


Ich habe eine bessere Methode gefunden, die die Snapshot-API wann immer möglich verwendet.

Ich hoffe, es hilft.

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

Möchten Sie mehr über iOS 7 Snapshots erfahren?

Objective-C-Version:

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




EDIT 3. Oktober 2013 Aktualisiert, um die neue super schnelle Methode drawViewHierarchyInRect: afterScreenUpdates: in iOS 7 zu unterstützen.


Nein. CALayers renderInContext: Soweit ich weiß, ist das der einzige Weg, dies zu tun. Sie könnten eine UIView-Kategorie wie diese erstellen, um es Ihnen zu erleichtern, vorwärts zu gehen:

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

Damit könntest du vielleicht sagen [self.view.window imageRepresentation] in einem View-Controller, und erhalten Sie einen vollständigen Screenshot Ihrer App. Dies kann jedoch die Statusleiste ausschließen.

BEARBEITEN:

Und darf ich hinzufügen. Wenn Sie eine UIView mit transparentem Inhalt haben und eine Bilddarstellung mit dem darunterliegenden Inhalt benötigen, können Sie eine Bilddarstellung der Containeransicht abrufen und das Bild zuschneiden, indem Sie einfach das Rect der Teilansicht übernehmen und in den Container konvertieren Ansichten Koordinatensystem.

[view convertRect:self.bounds toView:containerView]

Zu sehen, Antwort auf diese Frage: Ein Bild beschneiden


18
2017-11-01 10:25



iOS 7 hat eine neue Methode eingeführt, mit der Sie eine Ansichtshierarchie in den aktuellen Grafikkontext zeichnen können. Dies kann verwendet werden, um ein UIImage sehr schnell.

Als Kategorie Methode implementiert am 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 ist wesentlich schneller als das vorhandene renderInContext: Methode.

UPDATE FÜR SWIFT: Eine Erweiterung, die das Gleiche tut:

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



Ich habe die Antworten mit einer einzigen Funktion kombiniert, die für alle iOS-Versionen ausgeführt wird, auch für Retina-Geräte oder Geräte, die nicht gespeichert werden.

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



Was Sie als Alternative verlangen, ist das Lesen der GPU (da der Bildschirm aus beliebig vielen durchscheinenden Ansichten zusammengesetzt wird), was ebenfalls eine inhärent langsame Operation ist.


1
2017-11-07 23:34



Für mich hat das Einstellen der InterpolationQualität einen langen Weg zurückgelegt.

CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);

Wenn Sie sehr detaillierte Bilder aufnehmen, ist diese Lösung möglicherweise nicht akzeptabel. Wenn Sie Text als Snapshot erstellen, werden Sie den Unterschied kaum bemerken.

Dies verkürzt die Zeit, um den Schnappschuss deutlich zu machen, sowie ein Bild, das viel weniger Speicher verbraucht.

Dies ist immer noch von Vorteil mit der Methode drawViewHierarchyInRect: afterScreenUpdates:.


1
2017-10-11 21:26