iOS: какой способ сделать снимок экрана программным способом самый быстрый и эффективный?

в моем приложении для iPad я хотел бы сделать снимок экрана UIView, занимающего большую часть экрана. К сожалению, подвиды довольно глубоко вложены, поэтому на создание снимка экрана и анимацию скручивания страницы уходит много времени.

Есть ли способ быстрее "обычного"?

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

Если возможно, я бы хотел избежать кеширования или реструктуризации моего представления.


person swalkner    schedule 01.11.2011    source источник
comment
Не забудьте вызвать UIGraphicsEndImageContext, когда закончите.   -  person ZunTzu    schedule 25.10.2012


Ответы (6)


Я нашел лучший метод, который по возможности использует API моментальных снимков.

Я надеюсь, что это помогает.

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

Хотите узнать больше о снимках состояния iOS 7 ?

Версия 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;
}
person 3lvis    schedule 05.11.2011
comment
Работает ли это решение лучше, чем решение, предложенное на оригинальном плакате? Мои собственные тесты показывают, что это точно так же. В целом, я бы выбрал исходное решение, поскольку код намного проще. - person Greg Maletic; 09.05.2012
comment
@GregMaletic: Да, другое решение выглядит проще, но оно работает через UIView, это работает через UIWindow, поэтому оно более полно. - person 3lvis; 10.05.2012
comment
Я до сих пор не могу понять, почему это решение быстрее. Большинство приложений для iOS содержат только одно окно. Разве не должно быть хорошо только [self.window.layer renderInContext: context]? - person Muhammad Hassan Nasr; 09.11.2012
comment
Я не верю, что это работает. Проблемы с производительностью renderInContext хорошо задокументированы, и вызов его на уровне Window не решит этого. - person Adam; 23.02.2013
comment
Это почти ответ! Однако есть один крошечный недостаток: в случае преобразования слоев в вашем UIView вы можете рассмотреть возможность рендеринга presentationLayer вместо самого слоя, например, o: [window.layer.presentationLayer renderInContext:context]; - person Kirualex; 20.10.2013
comment
Это не работает, если одно из ваших представлений использует AVCaptureVideoPreviewLayer (или другие возможные элементы камеры). Я думаю, что следующие вопросы и ответы Apple Tech помогут в этом, но я не подтвердил: developer.apple.com/library/ios/qa/qa1714/_index.html - person kanso; 02.01.2014
comment
Я должен добавить, что это не работает для iOS 6 и ниже. Теперь он работает с iOS 7. - person kanso; 02.01.2014
comment
+1 за то, что у меня есть единственный ответ по SA, который, как я обнаружил, поддерживает все UIInterfaceOrientations - person Jasper; 05.03.2014
comment
Не работает в iOS8. Чтобы он работал, вам нужно постоянно `imageSize = [UIScreen mainScreen] .bounds.size;` и пропускать часть переворачивания. - person Antzi; 04.02.2015
comment
Я пробую это на IOS9, и у меня постоянно получается, что renderInContext превосходит drawViewHierarchyInRect. По большому счету. drawViewHierarchyInRect для рендеринга требуется 0,15 с, а для renderInContext - 0,02 и ниже. Это новая функция IOS9? - person Dmitry Fink; 06.11.2015
comment
@DmitryFink, если это так, я нигде не видел, чтобы это было задокументировано - person 3lvis; 06.11.2015
comment
Это работает нормально, но не делает снимок экрана для сеанса камеры или просмотра stackoverflow.com/questions/41239254/ - person Chlebta; 21.12.2016
comment
В моих тестах renderInContext также работает намного лучше, чем drawViewHierarchyInRect, iOS 11.2. - person Nikolay Dimitrov; 16.01.2018


ИЗМЕНИТЬ 3 октября 2013 г. Обновлено для поддержки нового сверхбыстрого метода drawViewHierarchyInRect: afterScreenUpdates: в iOS 7.


Нет. CALayer renderInContext: насколько мне известно, единственный способ сделать это. Вы можете создать такую ​​категорию UIView, чтобы упростить себе дальнейшее продвижение:

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

Благодаря этому вы могли бы сказать [self.view.window imageRepresentation] в контроллере представления и получить полный снимок экрана вашего приложения. Однако это может исключить строку состояния.

РЕДАКТИРОВАТЬ:

И позвольте мне добавить. Если у вас есть UIView с прозрачным содержимым и вам нужно представление изображения с нижележащим содержимым, вы можете получить представление изображения контейнера и обрезать это изображение, просто взяв прямоугольник подпредставления и преобразовав его в контейнер. просматривает систему координат.

[view convertRect:self.bounds toView:containerView]

Чтобы обрезать, см. Ответ на этот вопрос: Обрезка UIImage

person Trenskow    schedule 01.11.2011
comment
большое спасибо; Я использую категорию прямо сейчас; но я ищу более производительный способ сделать снимок экрана ...: / - person swalkner; 01.11.2011
comment
@EDIT: вот что я делаю - я получаю изображение контейнера. Но это не помогает мне решить мою проблему с производительностью ... - person swalkner; 01.11.2011
comment
То же самое для меня ... разве нет способа без повторного рендеринга всего? - person Christian Schnorr; 01.11.2011
comment
Верно, что iOS использует внутренние представления изображений для ускорения рендеринга. Повторно отрисовываются только просмотры, которые изменились. Но если вы спрашиваете, как получить внутреннее представление изображения без необходимости перерисовки, я не думаю, что это возможно. Как упоминалось выше, этот образ, вероятно, находится в графическом процессоре и, скорее всего, недоступен через общедоступные API. - person Trenskow; 11.11.2011
comment
Мне нужно было использовать afterScreenUpdates:YES, но в остальном он отлично работает. - person EthanB; 02.01.2014

iOS 7 представила новый метод, который позволяет рисовать иерархию представлений в текущем графическом контексте. Это может быть использовано для очень быстрого получения UIImage.

Реализован как метод категорий на UIView:

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

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

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

Это значительно быстрее, чем существующий метод renderInContext:.

ОБНОВЛЕНИЕ SWIFT: расширение, которое делает то же самое:

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;
    }
}
person Klaas    schedule 20.09.2013
comment
Вы проверяли, что это на самом деле быстрее? Мои тесты привели к очень небольшому повышению производительности, даже если для параметра afterScreenUpdates установлено значение NO. - person maxpower; 12.10.2013
comment
@maxpower Я рассчитал время выполнения и получил увеличение скорости более чем на 50%. Со старым renderInContext: потребовалось около 0,18 с, а с этим - 0,063. Я считаю, что ваши результаты будут зависеть от процессора вашего устройства. - person ; 16.12.2014
comment
это только у меня или self.drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true) вызывает на мгновение странную ошибку отображения во время выполнения? У меня нет такой же проблемы с self.layer.renderInContext(UIGraphicsGetCurrentContext()). - person CaptainCOOLGUY; 22.12.2014

Я объединил ответы в одну функцию, которая будет работать для любых версий iOS, даже для устройств Retina или устройств без сохранения.

- (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;
}
person Hemang    schedule 09.01.2014

Для меня установка InterpolationQuality имела большое значение.

CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);

Если вы делаете снимки с очень подробными изображениями, это решение может быть неприемлемым. Если вы снимаете текст, вы почти не заметите разницы.

Это значительно сокращает время на создание снимка, а также делает изображение, которое потребляет гораздо меньше памяти.

Это по-прежнему полезно для метода drawViewHierarchyInRect: afterScreenUpdates :.

person maxpower    schedule 11.10.2013
comment
Можете ли вы сказать мне, какие именно различия вы видите? Я вижу небольшое увеличение времени. - person daveMac; 08.02.2014
comment
К сожалению, я не могу. У меня больше нет доступа к проекту. Поменял работу. но я могу сказать, что представление, которое снималось с экрана, вероятно, имело 50 + - 10 представлений в нисходящей иерархии. Я также могу сказать, что от 1/4 до 1/3 просмотров были просмотры изображений. - person maxpower; 13.02.2014
comment
При дальнейшем исследовании я вижу разницу при установке интерполяции только тогда, когда вы изменяете размер представления при его рендеринге или рендеринге в меньшем контексте. - person daveMac; 13.02.2014
comment
Я предполагаю, что это в основном зависит от конкретного контекста, о котором идет речь. По крайней мере, еще один человек увидел значительные результаты от этого. см. комментарий к этому ответу. stackoverflow.com / questions / 11435210 / - person maxpower; 14.02.2014

В качестве альтернативы вы просите прочитать данные графического процессора (поскольку экран состоит из любого количества полупрозрачных представлений), что тоже по своей сути является медленной операцией.

person David Dunham    schedule 07.11.2011
comment
так что нет более быстрого решения? - person swalkner; 10.11.2011
comment
его нет на ios, так как gpu и cpu используют одну и ту же оперативную память - person nevyn; 13.12.2016