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;
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:
.
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;
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.
Ugly, in what way?
– Marcelo Cantos
Dec 2 '10 at 11:05