Come acquisire UIView su UIImage senza perdita di qualità sul display retina


303

Il mio codice funziona bene per i dispositivi normali ma crea immagini sfocate sui dispositivi retina.

Qualcuno conosce una soluzione al mio problema?

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

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

sfocato. Mi sembra che la scala giusta si sia persa ...
Daniel,

anch'io. incontrato lo stesso problema.
RainCast,

Dove stai visualizzando l'immagine del risultato? Come altri hanno spiegato, credo che tu stia eseguendo il rendering della vista su un'immagine con scala 1, mentre il tuo dispositivo ha scala 2 o 3. Quindi stai ridimensionando a una risoluzione inferiore. Questa è una perdita di qualità ma non dovrebbe essere sfocata. Ma quando guardi di nuovo questa immagine (sullo stesso schermo?) Su un dispositivo con scala 2, questa verrà convertita dalla bassa risoluzione alla maggiore, usando 2 pixel per pixel nello screenshot. Questo upscaling utilizza molto probabilmente l'interpolazione (impostazione predefinita su iOS), calcolando la media dei valori di colore per i pixel extra.
Hans Terje Bakke

Risposte:


667

Passa dall'uso di UIGraphicsBeginImageContexta UIGraphicsBeginImageContextWithOptions(come documentato in questa pagina ). Passa 0,0 per la scala (il terzo argomento) e otterrai un contesto con un fattore di scala uguale a quello dello schermo.

UIGraphicsBeginImageContextutilizza un fattore di scala fisso di 1,0, quindi in realtà stai ottenendo esattamente la stessa immagine su un iPhone 4 come su altri iPhone. Scommetto che l'iPhone 4 sta applicando un filtro quando lo ridimensioni implicitamente o solo il tuo cervello lo sta rilevando essendo meno nitido di tutto ciò che lo circonda.

Immagino:

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

E in 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
La risposta di Tommy va bene, ma devi comunque importare #import <QuartzCore/QuartzCore.h>per rimuovere gli renderInContext:avvisi.
gwdp,

2
@WayneLiu potresti specificare esplicitamente un fattore di scala di 2.0 ma probabilmente non otterrai esattamente un'immagine di qualità di iPhone 4 perché qualsiasi bitmap caricata sarebbe la versione standard anziché la versione @ 2x. Non penso che ci sia una soluzione a questo in quanto non c'è modo di istruire tutti coloro che al momento si stanno aggrappando a un UIImagericaricare ma forzare la versione ad alta risoluzione (e probabilmente incontreresti problemi a causa della minore RAM disponibile in pre -retina dispositivi comunque).
Tommy,

6
Invece di usare il 0.0fparametro for scale, è più accettabile da usare [[UIScreen mainScreen] scale], funziona anche.
Adam Carter,

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.È esplicitamente documentato, quindi 0.0f è più semplice e migliore secondo me.
cprcrack,

5
Questa risposta non è aggiornata per iOS 7. Vedi la mia risposta per il nuovo metodo "migliore" per farlo.
Dima

224

La risposta attualmente accettata non è più aggiornata, almeno se si supporta iOS 7.

Ecco cosa dovresti usare se stai supportando solo 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;
}

Swift 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()
}

Come da questo articolo , puoi vedere che il nuovo metodo iOS7 drawViewHierarchyInRect:afterScreenUpdates:è molte volte più veloce di renderInContext:.prova delle prestazioni


5
Non c'è dubbio, drawViewHierarchyInRect:afterScreenUpdates:è molto più veloce. Ho appena eseguito un profiler temporale in Strumenti. La mia generazione di immagini è passata da 138ms a 27ms.
ThomasCle

9
Per qualche motivo questo non ha funzionato per me quando stavo cercando di creare icone personalizzate per i marcatori di Google Maps; tutto ciò che ho ottenuto sono stati i rettangoli neri :(
JakeP,

6
@CarlosP hai provato l'impostazione afterScreenUpdates:a YES?
Dima,

7
imposta afterScreenUpdates su YES risolto il problema del rettangolo nero per me
hyouuu,

4
Stavo anche ricevendo immagini nere. Si è scoperto che se chiamo il codice viewDidLoado viewWillAppear:le immagini sono nere; Ho dovuto farlo dentro viewDidAppear:. Quindi sono anche tornato a renderInContext:.
manicaesar il

32

Ho creato un'estensione Swift basata sulla soluzione @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 versione migliorata

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

Uso:

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

2
Grazie per l'idea! A parte questo, puoi anche rinviare {UIGraphicsEndImageContext ()} subito dopo aver iniziato il contesto ed evitare di dover introdurre la variabile locale img;)
Dennis L,

Grazie mille per la spiegazione e l'utilizzo ben dettagliati!
Zeus,

Perché questa è una classfunzione che considera?
Rudolf Adamkovič,

Funziona come una staticfunzione ma, poiché non è necessario eseguire l'override, puoi semplicemente cambiarla in una staticfunzione anziché class.
Heberti Almeida,

25

iOS veloce

Utilizzando il moderno 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 forse non ancora, ma non appena iniziano a deprecare le cose o ad aggiungere rendering o miglioramenti con accelerazione hardware, è meglio essere sulle spalle dei giganti (AKA usando le ultime API di Apple)
Juan Boero

Qualcuno è a conoscenza di quale sia la prestazione paragonando questo rendereral vecchio drawHierarchy...?
Vive il

24

Per migliorare le risposte di @Tommy e @Dima, utilizzare la seguente categoria per rendere UIView in UIImage con sfondo trasparente e senza perdita di qualità. Funzionando su iOS7. (O semplicemente riutilizzare quel metodo nell'implementazione, sostituendo il selfriferimento con la tua immagine)

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
Se uso drawViewHierarchyInRect con afterScreenUpdates: SÌ le proporzioni dell'immagine sono cambiate e l'immagine risulta distorta.
Confile il

Questo succede quando il contenuto di uiview viene modificato? Se sì, prova a rigenerare nuovamente questo UIImage. Spiacente, non posso provarlo da solo perché sono al telefono
Glogo,

Ho lo stesso problema e sembra che nessuno l'abbia notato, hai trovato una soluzione a questo problema? @confile?
Reza,

Questo è proprio quello di cui ho bisogno ma non ha funzionato. Ho provato facendo @interfacee @implementationsia (RenderViewToImage)e aggiungendo #import "UIView+RenderViewToImage.h"all'intestazione ViewController.hquindi è questo l'uso corretto? ad es UIImageView *test = [[UIImageView alloc] initWithImage:[self imageByRenderingView]]; [self addSubview:test];.
Greg,

e questo metodo è ViewController.mstato il punto di vista che ho cercato di rendere- (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; }
Greg

15

Swift 3

La soluzione Swift 3 (basata sulla risposta di Dima ) con estensione UIView dovrebbe essere così:

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

Estensione Swift 3.0 drop-in che supporta la nuova API iOS 10.0 e il metodo precedente.

Nota:

  • controllo versione iOS
  • Nota l'uso del differimento per semplificare la pulizia del contesto.
  • Applicherà anche l'opacità e la scala corrente della vista.
  • Non viene utilizzato nulla di scartato !che potrebbe causare un arresto anomalo.

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:

Utilizzando il metodo di estensione:

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
   }

}

Uso:

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

Implementazione rapida 3.0

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

3

Tutte le risposte di Swift 3 non hanno funzionato per me, quindi ho tradotto la risposta più accettata:

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

Ecco un'estensione UIView di Swift 4 basata sulla risposta di @Dima.

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

2

Per Swift 5.1 puoi usare questa estensione:

extension UIView {

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

        return renderer.image {
            rendererContext in

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

1

UIGraphicsImageRendererè un'API relativamente nuova, introdotta in iOS 10. Costruisci un UIGraphicsImageRenderer specificando una dimensione in punti . Il metodo image accetta un argomento di chiusura e restituisce una bitmap risultante dall'esecuzione della chiusura passata. In questo caso, il risultato è l'immagine originale ridimensionata per disegnare entro i limiti specificati.

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

Quindi assicurati che la dimensione in cui passi UIGraphicsImageRenderersia punti, non pixel.

Se le tue immagini sono più grandi di quanto ti aspetti, devi dividere le dimensioni per il fattore di scala.


Sono curioso, potresti aiutarmi con questo per favore? stackoverflow.com/questions/61247577/... Sto utilizzando UIGraphicsImageRenderer, problema è che voglio l'immagine esportata per essere più grande formato che la vista reale sul dispositivo. Ma con le dimensioni desiderate le sottoview (etichette) non sono abbastanza nitide, quindi c'è una perdita di qualità.
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

In questo metodo basta passare un oggetto vista e restituirà un oggetto UIImage.

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

-6

Aggiungi questo metodo alla categoria UIView

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

2
Ciao, anche se questo potrebbe rispondere alla domanda, tieni presente che altri utenti potrebbero non essere così informati come te. Perché non aggiungi una piccola spiegazione sul perché questo codice funziona? Grazie!
Vogel612,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.