문제

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;
}
도움이 되었습니까?

해결책

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 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.

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
}

다른 팁

The currently 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)
    defer { UIGraphicsEndImageContext() }
    view.drawHierarchy(in: view.bounds, afterScreenUpdates: true)
    return UIGraphicsGetImageFromCurrentImageContext()
}

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

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)

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)

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

iOS Swift

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

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:

  • iOS version check
  • Note the use of defer to simplify the context cleanup.
  • Will also apply the opacity & current scale of the view.
  • Nothing is just unwrapped using ! which could cause a crash.

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)

 }

Swift 3.0 implementation

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!
    }
}

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

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

UIGraphicsImageRenderer is a relatively new API, introduced in iOS 10. You construct a UIGraphicsImageRenderer by specifying a point size. The image method takes a closure argument and returns a bitmap that results from executing the passed closure. In this case, the result is the original image scaled down to draw within the specified bounds.

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

So be sure the size you are passing into UIGraphicsImageRenderer is points, not pixels.

If your images are larger than you are expecting, you need to divide your size by the scale factor.

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;
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top