So erfassen Sie UIView to UIImage ohne Qualitätsverlust auf dem Retina-Display

303

Mein Code funktioniert gut für normale Geräte, erzeugt jedoch verschwommene Bilder auf Retina-Geräten.

Kennt jemand eine Lösung für mein Problem?

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

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}
Daniel
quelle
verschwommen. Es scheint mir, dass die richtige Skala verloren gegangen ist ...
Daniel
ich auch. traf das gleiche Problem.
RainCast
Wo zeigen Sie das Ergebnisbild an? Wie andere erklärt haben, glaube ich, dass Sie die Ansicht auf ein Bild mit Maßstab 1 rendern, während Ihr Gerät Maßstab 2 oder 3 hat. Sie verkleinern also auf eine niedrigere Auflösung. Dies ist ein Qualitätsverlust, sollte aber nicht verschwimmen. Wenn Sie dieses Bild jedoch erneut (auf demselben Bildschirm?) Auf einem Gerät mit Maßstab 2 betrachten, wird es von der niedrigen Auflösung in eine höhere konvertiert, wobei 2 Pixel pro Pixel im Screenshot verwendet werden. Bei dieser Hochskalierung wird höchstwahrscheinlich Interpolation verwendet (Standardeinstellung unter iOS), wobei die Farbwerte für die zusätzlichen Pixel gemittelt werden.
Hans Terje Bakke

Antworten:

667

Wechseln Sie von Verwendung UIGraphicsBeginImageContextzu UIGraphicsBeginImageContextWithOptions(wie auf dieser Seite dokumentiert ). Übergeben Sie 0.0 für scale (das dritte Argument) und Sie erhalten einen Kontext mit einem Skalierungsfaktor, der dem des Bildschirms entspricht.

UIGraphicsBeginImageContextverwendet einen festen Skalierungsfaktor von 1,0, sodass Sie auf einem iPhone 4 genau das gleiche Bild erhalten wie auf den anderen iPhones. Ich wette, entweder das iPhone 4 wendet einen Filter an, wenn Sie ihn implizit vergrößern, oder nur Ihr Gehirn erkennt, dass er weniger scharf ist als alles um ihn herum.

Also, ich denke:

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

Und 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
}
Tommy
quelle
6
Die Antwort von Tommy ist in Ordnung, aber Sie müssen noch importieren #import <QuartzCore/QuartzCore.h>, um die renderInContext:Warnung zu entfernen .
GWDP
2
@WayneLiu Sie könnten explizit einen Skalierungsfaktor von 2,0 angeben, aber Sie würden wahrscheinlich nicht genau ein Bild in iPhone 4-Qualität erhalten, da alle geladenen Bitmaps die Standardversionen und nicht die @ 2x-Versionen sind. Ich glaube nicht, dass es eine Lösung dafür gibt, da es keine Möglichkeit gibt, alle, die derzeit an einem UIImagefesthalten , anzuweisen, neu zu laden, sondern die hochauflösende Version zu erzwingen (und Sie wahrscheinlich auf Probleme stoßen würden, weil weniger RAM vorab verfügbar ist -retina Geräte sowieso).
Tommy
6
Anstatt 0.0fden Parameter for for scale zu verwenden, ist die Verwendung akzeptabler [[UIScreen mainScreen] scale]und funktioniert auch.
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.Es ist explizit dokumentiert, daher ist 0.0f meiner Meinung nach einfacher und besser.
cprcrack
5
Diese Antwort ist für iOS 7 veraltet. In meiner Antwort finden Sie die neue "beste" Methode, um dies zu tun.
Dima
224

Die derzeit akzeptierte Antwort ist veraltet, zumindest wenn Sie iOS 7 unterstützen.

Folgendes sollten Sie verwenden, wenn Sie nur iOS7 + unterstützen:

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

In diesem Artikel sehen Sie, dass die neue iOS7-Methode drawViewHierarchyInRect:afterScreenUpdates:um ein Vielfaches schneller ist als renderInContext:.Benchmark

Dima
quelle
5
Es besteht kein Zweifel, das drawViewHierarchyInRect:afterScreenUpdates:ist viel schneller. Ich habe gerade einen Zeitprofiler in Instruments ausgeführt. Meine Bilderzeugung ging von 138 ms auf 27 ms.
ThomasCle
9
Aus irgendeinem Grund funktionierte dies bei mir nicht, als ich versuchte, benutzerdefinierte Symbole für Google Maps-Marker zu erstellen. Alles was ich bekam waren schwarze Rechtecke :(
JakeP
6
@CarlosP hast du versucht afterScreenUpdates:auf einzustellen YES?
Dima
7
set afterScreenUpdates to YES hat das Problem mit dem schwarzen Rechteck für mich
behoben
4
Ich erhielt auch schwarze Bilder. Es stellte sich heraus, dass, wenn ich den Code aufrufe viewDidLoadoder viewWillAppear:die Bilder schwarz sind; Ich musste es tun viewDidAppear:. Also bin ich auch endlich zurückgekehrt renderInContext:.
Manicaesar
32

Ich habe eine Swift-Erweiterung basierend auf der @ Dima-Lösung erstellt:

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 verbesserte Version

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

Verwendungszweck:

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))  
let image = UIImage.imageWithView(view)
Heberti Almeida
quelle
2
Danke für die Idee! Abgesehen davon können Sie {UIGraphicsEndImageContext ()} auch unmittelbar nach Beginn des Kontexts verschieben und vermeiden, dass Sie die lokale Variable img einführen müssen;)
Dennis L
Vielen Dank für die ausführliche Erklärung und Verwendung!
Zeus
Warum ist dies eine classFunktion, die eine Ansicht vertritt?
Rudolf Adamkovič
Dies funktioniert wie eine staticFunktion, aber da keine Überschreibung erforderlich ist, können Sie sie einfach in eine Funktion staticanstelle von ändern class.
Heberti Almeida
25

iOS Schnell

Verwendung des modernen 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
    }
}
Alexander Belyavskiy
quelle
@ masaldana2 vielleicht noch nicht, aber sobald sie anfangen, Dinge zu verwerfen oder hardwarebeschleunigtes Rendern oder Verbesserungen hinzuzufügen, sollten Sie besser auf den Schultern der Riesen sein (AKA mit den letzten Apple-APIs)
Juan Boero
Ist jemandem bewusst, wie hoch die Leistung im Vergleich rendererzur alten ist drawHierarchy?
Vive
24

Um die Antworten von @Tommy und @Dima zu verbessern, verwenden Sie die folgende Kategorie, um UIView mit transparentem Hintergrund und ohne Qualitätsverlust in UIImage zu rendern . Arbeiten an iOS7. (Oder verwenden Sie diese Methode einfach in der Implementierung wieder und ersetzen Sie die selfReferenz durch Ihr Bild.)

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
Glogo
quelle
1
Wenn ich drawViewHierarchyInRect mit afterScreenUpdates: YES verwende, hat sich mein Bildseitenverhältnis geändert und das Bild wird verzerrt.
Confile
Geschieht dies, wenn Ihr uiview-Inhalt geändert wird? Wenn ja, versuchen Sie erneut, dieses UIImage neu zu generieren. Entschuldigung, ich kann das nicht selbst testen, weil ich telefoniere
Glogo
Ich habe das gleiche Problem und anscheinend hat dies niemand bemerkt. Haben Sie eine Lösung für dieses Problem gefunden? @confile?
Reza.Ab
Dies ist genau das, was ich brauche, aber es hat nicht funktioniert. Ich habe versucht , zu machen @interfaceund @implementationbeide (RenderViewToImage)und das Hinzufügen #import "UIView+RenderViewToImage.h"zu dem Header in ViewController.hso ist dies die korrekte Verwendung? zB UIImageView *test = [[UIImageView alloc] initWithImage:[self imageByRenderingView]]; [self addSubview:test];?
Greg
und diese Methode in ViewController.mwar die Ansicht, die ich zu rendern versuchte- (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

Die Swift 3- Lösung (basierend auf Dimas Antwort ) mit UIView-Erweiterung sollte folgendermaßen aussehen:

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
    }
}
Mehdi Hosseinzadeh
quelle
6

Drop-in Swift 3.0-Erweiterung, die die neue iOS 10.0-API und die vorherige Methode unterstützt.

Hinweis:

  • iOS-Versionsprüfung
  • Beachten Sie die Verwendung von Defer, um die Kontextbereinigung zu vereinfachen.
  • Wendet auch die Deckkraft und den aktuellen Maßstab der Ansicht an.
  • Nichts wird nur mit ausgepackt, !was zu einem Absturz führen könnte.

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()
        }
    }
}
Leslie Godwin
quelle
5

Swift 2.0:

Verwenden der Erweiterungsmethode:

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
   }

}

Verwendungszweck:

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)

 }
AG
quelle
3

Schnelle 3.0-Implementierung

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

Alle Swift 3-Antworten haben bei mir nicht funktioniert, daher habe ich die am meisten akzeptierte Antwort übersetzt:

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!
    }
}
Marco Antonio Uzcategui Pescoz
quelle
2

Hier ist eine Swift 4 UIView-Erweiterung, die auf der Antwort von @Dima basiert.

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

Für Swift 5.1 können Sie diese Erweiterung verwenden:

extension UIView {

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

        return renderer.image {
            rendererContext in

            layer.render(in: rendererContext.cgContext)
        }
    }
}
Andrey M.
quelle
1

UIGraphicsImageRendererist eine relativ neue API, die in iOS 10 eingeführt wurde. Sie erstellen einen UIGraphicsImageRenderer, indem Sie eine Punktgröße angeben . Die Bildmethode verwendet ein Abschlussargument und gibt eine Bitmap zurück, die sich aus der Ausführung des übergebenen Abschlusses ergibt. In diesem Fall ist das Ergebnis das Originalbild, das verkleinert wurde, um innerhalb der angegebenen Grenzen zu zeichnen.

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

UIGraphicsImageRendererStellen Sie also sicher, dass die Größe, in die Sie übergeben, Punkte und keine Pixel sind.

Wenn Ihre Bilder größer sind als erwartet, müssen Sie Ihre Größe durch den Skalierungsfaktor teilen.

pkamb
quelle
Ich bin neugierig, könnten Sie mir bitte dabei helfen? stackoverflow.com/questions/61247577/… Ich verwende UIGraphicsImageRenderer. Das Problem ist, dass das exportierte Bild größer sein soll als die tatsächliche Ansicht auf dem Gerät. Bei gewünschter Größe sind Unteransichten (Beschriftungen) jedoch nicht scharf genug, sodass Qualitätsverluste auftreten.
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;
}
PJRadadiya
quelle
-2

Bei dieser Methode übergeben Sie einfach ein Ansichtsobjekt und es wird ein UIImage-Objekt zurückgegeben.

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

Fügen Sie diese Methode zur UIView-Kategorie hinzu

- (UIImage*) capture {
    UIGraphicsBeginImageContext(self.bounds.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.layer renderInContext:context];
    UIImage *img = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return img;
}
Shafraz Buhary
quelle
2
Hallo zusammen, obwohl dies die Frage beantworten kann, beachten Sie bitte, dass andere Benutzer möglicherweise nicht so gut informiert sind wie Sie. Warum fügen Sie nicht eine kleine Erklärung hinzu, warum dieser Code funktioniert? Vielen Dank!
Vogel612