iOS: який найшвидший та найефективніший спосіб зробити скріншот скрізь програмно?


77

у своєму додатку для iPad я хотів би зробити скріншот UIView, який займає велику частину екрана. На жаль, підгляди досить глибоко вкладені, тому потрібно багато часу, щоб зробити скріншот та анімувати згортання сторінки.

Чи є швидший спосіб, ніж "звичайний"?

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

Якщо це можливо, я хотів би уникати кешування чи реструктуризації мого подання.


6
Не забудьте зателефонувати до UIGraphicsEndImageContext, коли закінчите.
— ZunTzu

Відповіді:


111

Я знайшов кращий метод, який використовує 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;
}

2
Чи є це рішення кращим, ніж рішення, запропоноване оригінальним плакатом? Мої власні тести показують, що це точно те саме. Загалом, я б погодився з оригінальним рішенням, оскільки код набагато простіший.
— Грег Малетич

@GregMaletic: Так, інше рішення виглядає простішим, але працює через UIView, це працює над UIWindow, тому є більш повним.
— 3lvis

Я досі не можу зрозуміти, чому це рішення швидше. Більшість програм iOS містить лише одне вікно. Чи не просто [self.window.layer renderInContext: context] має бути нормальним?
— Мухаммед Хасан Наср

1
Я не вірю, що це працює. Проблеми з продуктивністю renderInContext добре задокументовані, і виклик цього на шарі Вікна не збирається це виправляти.
— Адам

1
У моїх тестах renderInContext також працює набагато краще, ніж drawViewHierarchyInRect, iOS 11.2
— Микола Димитров

18

EDIT 3 жовтня 2013 р. Оновлено для підтримки нового супершвидкого методу drawViewHierarchyInRect: afterScreenUpdates: в iOS 7.


Ні. CALayer's renderInContext: наскільки я знаю, це єдиний спосіб зробити це. Ви можете створити категорію UIView, як ця, щоб полегшити собі подальший розвиток:

UIView + знімок екрана. H

#import <UIKit/UIKit.h>

@interface UIView (Screenshot)

- (UIImage*)imageRepresentation;

@end

UIView + знімок екрана.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


дуже дякую; Зараз я використовую категорію; але я шукаю більш ефективний спосіб зробити скріншот ...: /
— swalkner

@EDIT: це те, що я роблю - я отримую зображення контейнера. Але це не допомагає мені вирішити мою проблему щодо продуктивності ...
— swalkner

Для мене те саме ... чи не існує способу, щоб не перетворити все?
— Крістіан Шнорр

Це правда, що iOS використовує внутрішні подання зображень, щоб пришвидшити візуалізацію. Відображаються лише перегляди, що змінюють. Але якщо ви запитуєте, як отримати внутрішнє зображення, без необхідності перемальовування, я не думаю, що це можливо. Як вже згадувалося вище, це зображення, ймовірно, живе в графічному процесорі і, швидше за все, недоступне через загальнодоступні API.
— Тренськов

Мені потрібно було користуватися afterScreenUpdates:YES, але в іншому випадку це чудово працює.
— EthanB

10

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

Ви перевіряли, що це насправді швидше? Мої тести привели до дуже незначного покращення продуктивності, навіть якщо afterScreenUpdates встановлено на НІ.
— maxpower

@maxpower Я приурочив виконання і отримав збільшення швидкості більш ніж на 50%. Зі старим renderInContext: це займало близько 0,18 с, а з цим - 0,063. Я вважаю, що ваші результати будуть відрізнятися залежно від процесора на вашому пристрої.

це лише я, або self.drawViewHierarchyInRect(self.bounds, afterScreenUpdates: true)на мить викликає дивну помилку відображення під час її виконання? Я не отримую те саме питання з self.layer.renderInContext(UIGraphicsGetCurrentContext()).
— Капітан COOLGUY

3

Я поєднав відповіді на одну функцію, яка буде запущена для будь-яких версій iOS, навіть для сітківки або пристроїв, що не зберігають.

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

2

Для мене встановлення якості інтерполяції пройшло довгий шлях.

CGContextSetInterpolationQuality(ctx, kCGInterpolationNone);

Якщо ви робите знімки дуже детальних зображень, це рішення може бути неприйнятним. Якщо ви робите знімок тексту, ви навряд чи помітите різницю.

Це скоротило час, щоб зробити значний знімок, а також створити зображення, яке споживало набагато менше пам'яті.

Це все ще вигідно з методом drawViewHierarchyInRect: afterScreenUpdates:.


Чи можете ви сказати мені, які саме відмінності ви спостерігаєте? Я спостерігаю незначне збільшення часу.
— daveMac

На жаль, не можу. Я більше не маю доступу до проекту. Поміняли роботу. але я можу сказати, що вигляд, який знімали на екрані, мав, мабуть, 50 + - 10 переглядів у спадної ієрархії. Я також можу сказати, що приблизно 1/4 - 1/3 переглядів були переглядами зображень
— maxpower

1
При подальшому дослідженні речей єдиний раз, коли я взагалі бачу різницю в налаштуванні інтерполяції, полягає в тому, якщо ви змінюєте розмір подання, коли його виводите або робите в меншому контексті.
— daveMac

Я припускаю, що це здебільшого залежить від конкретного контексту, про який йдеться. Принаймні ще одна людина бачила значні результати від цього. дивіться коментар до цієї відповіді. stackoverflow.com/questions/11435210 / ...
— MAXPOWER

1

Альтернативою, яку ви просите, є читання графічного процесора (оскільки екран складається з будь-якої кількості напівпрозорих виглядів), що за своєю суттю також є повільним.


отже, немає швидшого рішення?
— swalkner

це не на ios, оскільки gpu та cpu мають один і той же таран
— nevyn
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.