webentwicklung-frage-antwort-db.com.de

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

in meiner iPad-App möchte ich einen Screenshot von einem UIView machen, der einen großen Teil des Bildschirms einnimmt. Leider sind die Unteransichten ziemlich tief verschachtelt, sodass es zu lange dauert, den Screenshot zu erstellen und eine Seite zu animieren, die sich danach kräuselt.

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 es vermeiden, meine Sichtweise zwischenzuspeichern oder umzustrukturieren.

77
swalkner

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

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

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


Nein. Der renderInContext von CALayer: ist meines Wissens der einzige Weg, dies zu tun. Sie können eine UIView-Kategorie wie diese erstellen, um sich das weitere Vorgehen zu erleichtern:

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

Auf diese Weise können Sie vielleicht sagen, [self.view.window imageRepresentation] in einem Ansichtscontroller, 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 auch eine Bilddarstellung MIT dem darunter liegenden Inhalt benötigen, können Sie eine Bilddarstellung der Containeransicht abrufen und dieses Bild zuschneiden, indem Sie einfach das Rechteck der Unteransicht nehmen und es in den Container konvertieren Ansichten Koordinatensystem.

[view convertRect:self.bounds toView:containerView]

Zum Zuschneiden siehe Antwort auf diese Frage: IImage zuschneiden

18
Trenskow

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 sehr schnell ein UIImage zu erhalten.

Implementiert als Kategoriemethode auf 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 bisherige renderInContext: Methode.

PDATE FOR Swift: Eine Erweiterung, die dasselbe 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;
    }
}
10
Klaas

Ich habe die Antworten zu einer einzelnen Funktion zusammengefasst, die für alle iOS-Versionen ausgeführt werden kann, auch für Retina- oder Nicht-Retain-Geräte.

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

Für mich hat das Einstellen der InterpolationQuality einen langen Weg hinter sich.

CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);

Wenn Sie sehr detaillierte Bilder aufnehmen, ist diese Lösung möglicherweise nicht akzeptabel. Wenn Sie einen Text aus einer Momentaufnahme machen, werden Sie den Unterschied kaum bemerken.

Dadurch konnte die Zeit für die Schnappschussaufnahme erheblich verkürzt und ein Bild erstellt werden, das viel weniger Speicherplatz verbraucht.

Dies ist bei der Methode drawViewHierarchyInRect: afterScreenUpdates: weiterhin von Vorteil.

2
maxpower

Alternativ können Sie die GPU lesen (da der Bildschirm aus einer beliebigen Anzahl von durchsichtigen Ansichten besteht), was ebenfalls von Natur aus langsam ist.

1
David Dunham