How to capture UIView to UIImage without loss of quality on retina display

How to capture UIView to UIImage without loss of quality on retina display



My code works fine for normal devices but creates blurry images on retina devices.



Does anybody know a solution for my issue?


+ (UIImage *) imageWithView:(UIView *)view

UIGraphicsBeginImageContext(view.bounds.size);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];

UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return img;





Ugly, in what way?
– Marcelo Cantos
Dec 2 '10 at 11:05





blurry. It seems to me the right scale got lost...
– Daniel
Dec 2 '10 at 12:12






me too. met the same issue.
– RainCast
Jul 20 '16 at 23:45




15 Answers
15



Switch from use of UIGraphicsBeginImageContext to UIGraphicsBeginImageContextWithOptions (as documented on this page). Pass 0.0 for scale (the third argument) and you'll get a context with a scale factor equal to that of the screen.


UIGraphicsBeginImageContext


UIGraphicsBeginImageContextWithOptions



UIGraphicsBeginImageContext uses a fixed scale factor of 1.0, so you're actually getting exactly the same image on an iPhone 4 as on the other iPhones. I'll bet either the iPhone 4 is applying a filter when you implicitly scale it up or just your brain is picking up on it being less sharp than everything around it.


UIGraphicsBeginImageContext



So, I guess:


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



And 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





This works great for me! Thanks.
– Daniel
Dec 2 '10 at 13:19





Tommy answer is fine , but you still need to import #import <QuartzCore/QuartzCore.h> to remove renderInContext: warning .
– gwdp
May 10 '11 at 3:29


#import <QuartzCore/QuartzCore.h>


renderInContext:





Instead of using the 0.0f for the scale parameter, is it more acceptable to use [[UIScreen mainScreen] scale], it works too.
– Adam Carter
Aug 14 '12 at 17:06


0.0f


[[UIScreen mainScreen] scale]





@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. It's explicitly documented, so 0.0f is simpler and better in my opinion.
– cprcrack
Oct 24 '13 at 11:36


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.





This answer is out of date for iOS 7. See my answer for the new "best" method to do this.
– Dima
Mar 19 '14 at 2:18



The current accepted answer is now out of date, at least if you are supporting iOS 7.



Here is what you should be using if you are only supporting 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)
view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
let snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return snapshotImage



As per this article, you can see that the new iOS7 method drawViewHierarchyInRect:afterScreenUpdates: is many times faster than renderInContext:. benchmark


drawViewHierarchyInRect:afterScreenUpdates:


renderInContext:





There is no doubt, that drawViewHierarchyInRect:afterScreenUpdates: is a lot faster. I just ran a Time profiler in Instruments. My image generation went from 138ms to 27ms.
– Thomas Clemensen
Jul 21 '14 at 7:09



drawViewHierarchyInRect:afterScreenUpdates:





For some reason this didn't work for me when I was trying to create custom icons for Google Maps markers; all I got was black rectangles :(
– JakeP
Sep 10 '14 at 8:44





@CarlosP have you tried setting afterScreenUpdates: to YES?
– Dima
Dec 11 '14 at 20:31


afterScreenUpdates:


YES





set afterScreenUpdates to YES fixed the black rectangle issue for me
– hyouuu
Jan 6 '15 at 10:40





I was also receiving black images. It turned out that if I call the code in viewDidLoad or viewWillAppear: the images are black; I had to do it in viewDidAppear:. So I also finally reverted to renderInContext:.
– manicaesar
Nov 16 '15 at 10:22


viewDidLoad


viewWillAppear:


viewDidAppear:


renderInContext:



I have created a Swift extension based on @Dima solution:


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




Usage:


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





Thanks for the idea! Just as an aside, you can also defer UIGraphicsEndImageContext() immediately after beginning the context and avoid having to introduce the local variable img ;)
– Dennis L
Jan 17 '16 at 1:03





Thanks so much for the well-detailed explanation and usage!
– Zeus
Sep 21 '18 at 3:03



To improve answers by @Tommy and @Dima, use the following category to render UIView into UIImage with transparent background and without loss of quality. Working on iOS7. (Or just reuse that method in implementation, replacing self reference with your image)


self


#import <UIKit/UIKit.h>

@interface UIView (RenderToImage)

- (UIImage *)imageByRenderingView;

@end


#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





If I use drawViewHierarchyInRect with afterScreenUpdates:YES my image aspect ratio has changed and the image get distorted.
– confile
Apr 21 '15 at 9:54





Does this happen when your uiview content is changed? If yes then try to re-generate this UIImage again. Sorry can't test this myself because I'm on phone
– Glogo
Apr 21 '15 at 12:15





I have the same problem and seems like no one has noticed this, did you find any solution to this problem? @confile ?
– Reza.Ab
Jun 5 '17 at 15:38





This is just what I need but it didn't work. I tried making @interface and @implementation both (RenderViewToImage) and adding #import "UIView+RenderViewToImage.h" to the header in ViewController.h so is this the correct usage ? e.g. UIImageView *test = [[UIImageView alloc] initWithImage:[self imageByRenderingView]]; [self addSubview:test]; ?
– Greg
Feb 24 '18 at 5:05



@interface


@implementation


(RenderViewToImage)


#import "UIView+RenderViewToImage.h"


ViewController.h


UIImageView *test = [[UIImageView alloc] initWithImage:[self imageByRenderingView]]; [self addSubview:test];





and this method in ViewController.m was the view I tried to render - (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
Feb 24 '18 at 5:13



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;



iOSSwift



Using modern 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






thanks! works... although i dont see any difference
– masaldana2
Jun 15 '18 at 9:15



Swift 3



The Swift 3 solution (based on Dima's answer) with UIView extension should be like this:


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




Drop-in Swift 3.0 extension that supports the new iOS 10.0 API & the previous method.



Note:


!


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





Swift 2.0:



Using extension method:


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





Usage:


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)



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




All Swift 3 answers did not worked for me so I have translated the most accepted answer:


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!




Some times drawRect Method makes problem so I got these answers more appropriate. You too may have a look on it
Capture UIImage of UIView stuck in DrawRect method


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



Here's a Swift 4 UIView extension based on the answer from @Dima.


extension UIView

func image () -> UIImage?

UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0)

drawHierarchy(in: bounds, afterScreenUpdates: false)

let image = UIGraphicsGetImageFromCurrentImageContext()

UIGraphicsEndImageContext()

return image




In this method just pass a view object and it will returns a UIImage object.


-(UIImage*)getUIImageFromView:(UIView*)yourView

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



Add this to method to UIView Category


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





Hiya there, while this may well answer the question, please be aware that other users might not be as knowledgeable as you. Why don't you add a little explanation as to why this code works? Thanks!
– Vogel612
Apr 21 '15 at 10:54



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Some of your past answers have not been well-received, and you're in danger of being blocked from answering.



Please pay close attention to the following guidance:



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)