Як захопити UIView на UIImage без втрати якості на дисплеї сітківки


303

Мій код працює нормально для звичайних пристроїв, але створює розмиті зображення на пристроях сітківки.

Хтось знає рішення моєї проблеми?

+ (UIImage *) imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContext(view.bounds.size);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

розмита Мені здається, правильна шкала загубилась ...
— Даніель

я також. зустрілися з тим же питанням.
— RainCast

Де ви показуєте результат зображення? Як пояснили інші, я вважаю, що ви переглядаєте зображення на масштаб 1, тоді як ваш пристрій має масштаб 2 або 3. Отже, ви зменшуєте масштаб до нижчої роздільної здатності. Це втрата якості, але не повинно розмиватися. Але коли ви знову переглядаєте це зображення (на тому ж екрані?) На пристрої масштабу 2, воно перетвориться з низької роздільної здатності на більш високу, використовуючи 2 пікселі на піксель на скріншоті. Для цього масштабування, швидше за все, використовується інтерполяція (за замовчуванням на iOS), усереднення значень кольору для додаткових пікселів.
— Ганс

Відповіді:


667

Перехід від використання UIGraphicsBeginImageContextдо UIGraphicsBeginImageContextWithOptions(як це зафіксовано на цій сторінці ). Пройдіть 0,0 для шкали (третій аргумент), і ви отримаєте контекст з коефіцієнтом масштабу, рівним екрану.

UIGraphicsBeginImageContextвикористовує фіксований масштабний коефіцієнт 1,0, тому ви фактично отримуєте абсолютно те саме зображення на iPhone 4, як і на інших iPhone. Б'юсь об заклад, що або iPhone 4 застосовує фільтр, коли ви неявно його масштабуєте, або просто ваш мозок підбирає його менш гострим, ніж усе навколо.

Отже, я здогадуюсь:

#import <QuartzCore/QuartzCore.h>

+ (UIImage *)imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

І в Swift 4:

func image(with view: UIView) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
    defer { UIGraphicsEndImageContext() }
    if let context = UIGraphicsGetCurrentContext() {
        view.layer.render(in: context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        return image
    }
    return nil
}

6
Відповідь Томмі чудово, але вам потрібно імпортувати, #import <QuartzCore/QuartzCore.h>щоб видалити renderInContext:попередження.
— gwdp

2
@WayneLiu ви можете чітко вказати коефіцієнт масштабу 2,0, але ви, мабуть, не отримаєте саме зображення якості iPhone 4, тому що будь-які завантажені растрові файли були б стандартними версіями, а не версіями @ 2x. Я не думаю, що для цього немає рішення, оскільки немає способу доручити всім, хто зараз тримається на UIImageперезавантаженні, але змусити версію з високою роздільною здатністю (і ви, швидше за все, зіткнетеся з проблемами завдяки меншій оперативної пам’яті, доступній у до -ретина пристрої все одно).
— Томмі

6
Замість того, щоб використовувати 0.0fпараметр шкали, чи прийнятніше використовувати [[UIScreen mainScreen] scale], він також працює.
— Адам Картер

20
@Adam Carter scale: The scale factor to apply to the bitmap. If you specify a value of 0.0, the scale factor is set to the scale factor of the device’s main screen.Це явно задокументовано, тому 0.0f простіше і краще на мою думку.
— cprcrack

5
Ця відповідь застаріла для iOS 7. Дивіться мою відповідь щодо нового "найкращого" методу для цього.
— Діма

224

Зараз прийнята відповідь застаріла, принаймні, якщо ви підтримуєте iOS 7.

Ось що вам слід використовувати, якщо ви підтримуєте лише iOS7 +:

+ (UIImage *) imageWithView:(UIView *)view
{
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0f);
    [view drawViewHierarchyInRect:view.bounds afterScreenUpdates:NO];
    UIImage * snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return snapshotImage;
}

Швидкий 4:

func imageWithView(view: UIView) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
    defer { UIGraphicsEndImageContext() }
    view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
    return UIGraphicsGetImageFromCurrentImageContext()
}

Згідно з цією статтею ви бачите, що новий метод iOS7 у drawViewHierarchyInRect:afterScreenUpdates:багато разів швидший, ніж renderInContext:.орієнтир


5
Безперечно, drawViewHierarchyInRect:afterScreenUpdates:це набагато швидше. Я щойно запускав Профілер часу в Інструментах. Моє покоління зображень пішло від 138 до 27 мс.
— ThomasCle

9
Чомусь це не спрацювало для мене, коли я намагався створити спеціальні значки для маркерів Google Maps; я отримав лише чорні прямокутники :(
— JakeP

6
@CarlosP ви пробували настройки afterScreenUpdates:в YES?
— Діма

7
встановити afterScreenUpdates до ТА вирішив для мене проблему чорного прямокутника
— hyouuu

4
Я також отримував чорні зображення. Виявилося, що якщо я зателефоную в код viewDidLoadабо viewWillAppear:зображення чорні; Мені довелося це зробити viewDidAppear:. Тому я теж нарешті повернувся renderInContext:.
— manicaesar

32

Я створив розширення Swift на основі рішення @Dima:

extension UIImage {
    class func imageWithView(view: UIView) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0)
        view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
        let img = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img
    }
}

EDIT: Swift 4 вдосконалена версія

extension UIImage {
    class func imageWithView(_ view: UIView) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0)
        defer { UIGraphicsEndImageContext() }
        view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
        return UIGraphicsGetImageFromCurrentImageContext() ?? UIImage()
    }
}

Використання:

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))  
let image = UIImage.imageWithView(view)

2
Дякую за ідею! Як і вбік, ви також можете відкласти {UIGraphicsEndImageContext ()} одразу після початку контексту і уникнути необхідності введення локальної змінної img;)
— Dennis L

Дякую за добре детальне пояснення та використання!
— Зевс

Чому це classфункція, яка займає перегляд?
— Рудольф Адамкович

Це працює як staticфункція, але оскільки немає потреби в переосмисленні, ви можете просто змінити його на функцію staticзамість class.
— Хеберті Альмейда

25

iOS Швидкий

Використання сучасного UIGraphicsImageRenderer

public extension UIView {
    @available(iOS 10.0, *)
    public func renderToImage(afterScreenUpdates: Bool = false) -> UIImage {
        let rendererFormat = UIGraphicsImageRendererFormat.default()
        rendererFormat.opaque = isOpaque
        let renderer = UIGraphicsImageRenderer(size: bounds.size, format: rendererFormat)

        let snapshotImage = renderer.image { _ in
            drawHierarchy(in: bounds, afterScreenUpdates: afterScreenUpdates)
        }
        return snapshotImage
    }
}

@ masaldana2, можливо, ще немає, але як тільки вони почнуть вимальовувати речі або додавати апаратне прискорене візуалізацію чи вдосконалення, вам краще бути на плечах гігантів (AKA, використовуючи останні Apple API)
— Juan Boero

Хтось знає про те, яка вистава порівнює це rendererзі старим drawHierarchy..?
— Vive

24

Щоб покращити відповіді від @Tommy та @Dima, використовуйте наступну категорію, щоб передати UIView в UIImage з прозорим фоном і без втрати якості. Робота над iOS7. (Або просто використайте цей метод у впровадженні, замінивши selfпосилання на ваше зображення)

UIView + RenderViewToImage.h

#import <UIKit/UIKit.h>

@interface UIView (RenderToImage)

- (UIImage *)imageByRenderingView;

@end

UIView + RenderViewToImage.m

#import "UIView+RenderViewToImage.h"

@implementation UIView (RenderViewToImage)

- (UIImage *)imageByRenderingView
{
    UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
    [self drawViewHierarchyInRect:self.bounds afterScreenUpdates:YES];
    UIImage * snapshotImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return snapshotImage;
}

@end

1
Якщо я використовую drawViewHierarchyInRect з afterScreenUpdates: ТАК співвідношення сторін зображення змінилося, і зображення спотворилося.
— конфіл

Це трапляється при зміні вмісту uiview? Якщо так, то спробуйте знову створити цей UIImage. Вибачте, не можу це перевірити сам, бо я телефоную
— Глого,

У мене така ж проблема і, схоже, ніхто цього не помітив, чи знайшли ви якесь рішення цієї проблеми? @confile?
— Reza.Ab

Це саме те, що мені потрібно, але це не спрацювало. Я спробував зробити @interfaceі те, і @implementationінше (RenderViewToImage)і додати #import "UIView+RenderViewToImage.h"до заголовка, ViewController.hтак це правильне використання? наприклад UIImageView *test = [[UIImageView alloc] initWithImage:[self imageByRenderingView]]; [self addSubview:test];?
— Грег

і цей метод ViewController.mбув таким, який я намагався надати- (UIView *)testView { UIView *v1 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 50, 50)]; v1.backgroundColor = [UIColor redColor]; UIView *v2 = [[UIView alloc] initWithFrame:CGRectMake(50, 50, 150, 150)]; v2.backgroundColor = [UIColor blueColor]; [v2 addSubview:v1]; return v2; }
— Грег,

15

Швидкий 3

Рішення Swift 3 (засноване на відповіді Діми ) з розширенням UIView має бути таким:

extension UIView {
    public func getSnapshotImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0)
        self.drawHierarchy(in: self.bounds, afterScreenUpdates: false)
        let snapshotImage: UIImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return snapshotImage
    }
}

6

Розширення Swing-in Swift 3.0, яке підтримує новий iOS 10.0 API та попередній метод.

Примітка:

  • Перевірка версії iOS
  • Зверніть увагу на використання відстрочки для спрощення очищення контексту.
  • Також буде застосовано шкалу непрозорості та поточної точки зору.
  • Ніщо не просто розкручується, використовуючи !що може спричинити збій.

extension UIView
{
    public func renderToImage(afterScreenUpdates: Bool = false) -> UIImage?
    {
        if #available(iOS 10.0, *)
        {
            let rendererFormat = UIGraphicsImageRendererFormat.default()
            rendererFormat.scale = self.layer.contentsScale
            rendererFormat.opaque = self.isOpaque
            let renderer = UIGraphicsImageRenderer(size: self.bounds.size, format: rendererFormat)

            return
                renderer.image
                {
                    _ in

                    self.drawHierarchy(in: self.bounds, afterScreenUpdates: afterScreenUpdates)
                }
        }
        else
        {
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, self.layer.contentsScale)
            defer
            {
                UIGraphicsEndImageContext()
            }

            self.drawHierarchy(in: self.bounds, afterScreenUpdates: afterScreenUpdates)

            return UIGraphicsGetImageFromCurrentImageContext()
        }
    }
}

5

Swift 2.0:

Використовуючи метод розширення:

extension UIImage{

   class func renderUIViewToImage(viewToBeRendered:UIView?) -> UIImage
   {
       UIGraphicsBeginImageContextWithOptions((viewToBeRendered?.bounds.size)!, false, 0.0)
       viewToBeRendered!.drawViewHierarchyInRect(viewToBeRendered!.bounds, afterScreenUpdates: true)
       viewToBeRendered!.layer.renderInContext(UIGraphicsGetCurrentContext()!)

       let finalImage = UIGraphicsGetImageFromCurrentImageContext()
       UIGraphicsEndImageContext()

       return finalImage
   }

}

Використання:

override func viewDidLoad() {
    super.viewDidLoad()

    //Sample View To Self.view
    let sampleView = UIView(frame: CGRectMake(100,100,200,200))
    sampleView.backgroundColor =  UIColor(patternImage: UIImage(named: "ic_120x120")!)
    self.view.addSubview(sampleView)    

    //ImageView With Image
    let sampleImageView = UIImageView(frame: CGRectMake(100,400,200,200))

    //sampleView is rendered to sampleImage
    var sampleImage = UIImage.renderUIViewToImage(sampleView)

    sampleImageView.image = sampleImage
    self.view.addSubview(sampleImageView)

 }

3

Реалізація Swift 3.0

extension UIView {
    func getSnapshotImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
        drawHierarchy(in: bounds, afterScreenUpdates: false)
        let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return snapshotImage
    }
}

3

Усі відповіді Swift 3 не спрацювали для мене, тому я переклав найбільш прийняту відповідь:

extension UIImage {
    class func imageWithView(view: UIView) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
        view.layer.render(in: UIGraphicsGetCurrentContext()!)
        let img: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return img!
    }
}

2

Ось розширення Swift 4 UIView на основі відповіді від @Dima.

extension UIView {
   func snapshotImage() -> UIImage? {
       UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)
       drawHierarchy(in: bounds, afterScreenUpdates: false)
       let image = UIGraphicsGetImageFromCurrentImageContext()
       UIGraphicsEndImageContext()
       return image
   }
}

2

Для Swift 5.1 ви можете використовувати це розширення:

extension UIView {

    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)

        return renderer.image {
            rendererContext in

            layer.render(in: rendererContext.cgContext)
        }
    }
}

1

UIGraphicsImageRendererце відносно новий API, представлений в iOS 10. Ви будуєте UIGraphicsImageRenderer , вказуючи розмір точки . Метод зображення бере аргумент закриття та повертає растрову карту, що є результатом виконання пройденого закриття. У цьому випадку результат - оригінальне зображення, зменшене до намальованого в межах заданих меж.

https://nshipster.com/image-resizing/

Тому переконайтеся, що розмір, який ви передаєте, UIGraphicsImageRenderer- це бали, а не пікселі.

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


Мені цікаво, чи можете ви мені допомогти у цьому? stackoverflow.com/questions/61247577 / ... Я використовую UIGraphicsImageRenderer, проблема полягає в тому , що я хочу експортованого зображення , щоб бути більше розміру , ніж фактичний вид на пристрої. Але з потрібними розмірами підгляди (мітки) недостатньо гострі - тому є втрата якості.
— Ondřej Korol


0
- (UIImage*)screenshotForView:(UIView *)view
{
    UIGraphicsBeginImageContext(view.bounds.size);
    [view.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // hack, helps w/ our colors when blurring
    NSData *imageData = UIImageJPEGRepresentation(image, 1); // convert to jpeg
    image = [UIImage imageWithData:imageData];

    return image;
}

-2

У цьому методі просто передайте об’єкт перегляду, і він поверне об'єкт UIImage.

-(UIImage*)getUIImageFromView:(UIView*)yourView
{
 UIGraphicsBeginImageContext(yourView.bounds.size);
 [yourView.layer renderInContext:UIGraphicsGetCurrentContext()];
 UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
 UIGraphicsEndImageContext();
 return image;
}

-6

Додайте це до методу до категорії UIView

- (UIImage*) capture {
    UIGraphicsBeginImageContext(self.bounds.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.layer renderInContext:context];
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return img;
}

2
Привіт, хоч це цілком може відповісти на запитання, зауважте, що інші користувачі можуть бути не настільки обізнаними, як ви. Чому ви не додасте невелике пояснення, чому цей код працює? Дякую!
— Vogel612
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.