Як захопити 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.