如何在不损失视网膜显示质量的情况下将UIView捕获到UIImage


303

我的代码在正常设备上工作正常,但在视网膜设备上创建了模糊的图像。

有人知道我的问题的解决方案吗?

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

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}

模糊。在我看来,正确的尺度已经迷失了……
Daniel

我也是。遇到了同样的问题。
RainCast

您在哪里显示结果图像?正如其他人所解释的,我相信您正在将视图渲染到缩放比例为1的图像上,而设备的缩放比例为2或3。因此,您将缩放比例降低到较低的分辨率。这是质量的损失,但不应模糊。但是,当您在缩放比例为2的设备上再次查看此图像(在同一屏幕上?)时,它将使用屏幕截图中的每像素2像素从低分辨率转换为高分辨率。此升级最有可能使用插值(iOS上的默认设置),对额外像素的颜色值进行平均。
汉斯·特耶·巴克

Answers:


667

从使用切换UIGraphicsBeginImageContextUIGraphicsBeginImageContextWithOptions(如本页所示)。传递0.0作为比例(第三个参数),您将获得一个比例因子等于屏幕比例的上下文。

UIGraphicsBeginImageContext使用1.0的固定比例因子,因此您实际上在iPhone 4上获得与其他iPhone完全相同的图像。我敢打赌,当您隐式放大iPhone 4时,iPhone 4会应用过滤器,或者只是您的大脑察觉到它的清晰度不如周围的一切。

所以,我猜:

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

在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
Tommy的回答很好,但是您仍然需要导入#import <QuartzCore/QuartzCore.h>以删除renderInContext:警告。
2011年

2
@WayneLiu您可以显式指定2.0的比例因子,但您可能无法获得iPhone 4的高质量图像,因为任何已加载的位图都是标准版本,而不是@ 2x版本。我认为没有解决方案,因为没有办法指示当前坚持使用a的每个人都UIImage重新加载,但是要强制使用高分辨率版本(由于预装的RAM较少,您可能会遇到问题。 -视网膜设备)。
汤米

6
代替使用0.0fscale参数,使用还是可以接受的[[UIScreen mainScreen] scale],它也可以工作。
亚当·卡特

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.已明确记录,因此我认为0.0f更简单更好。
cprcrack

5
对于iOS 7,此答案已过时。有关新的“最佳”方法,请参见我的答案。
Dima 2014年

224

当前接受的答案已经过期,至少在您支持iOS 7的情况下。

如果仅支持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;
}

斯威夫特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()
}

根据本文,您可以看到新的iOS7方法drawViewHierarchyInRect:afterScreenUpdates:比Windows 7 快许多倍renderInContext:基准


5
毫无疑问,这drawViewHierarchyInRect:afterScreenUpdates:要快得多。我只是在Instruments中运行了一个时间分析器。我的图像生成时间从138ms变为27ms。
ThomasCle 2014年

9
由于某些原因,当我尝试为Google Maps标记创建自定义图标时,这对我不起作用;我只有黑色矩形:(
JakeP 2014年

6
@CarlosP您是否尝试设置afterScreenUpdates:YES
Dima 2014年

7
将afterScreenUpdates设置为YES为我解决了黑色矩形问题
hyouuu

4
我也收到黑色图像。原来,如果我调用代码viewDidLoadviewWillAppear:图像是黑色的;我必须在做viewDidAppear:。所以我也终于回到了renderInContext:
manicaesar 2015年

32

我已经基于@Dima解决方案创建了一个Swift扩展:

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

编辑:Swift 4改进版本

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

用法:

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

2
谢谢你的主意!顺便说一句,您也可以在开始上下文后立即推迟{UIGraphicsEndImageContext()},而不必引入局部变量img;)
Dennis L

非常感谢详细的解释和使用!
宙斯

为什么这个class功能带有视图?
RudolfAdamkovič

这就像一个static函数一样工作,但是由于不需要覆盖,您只需将其更改为staticfunc即可class
Heberti Almeida

25

的iOS 迅速

使用现代的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也许还没有,但是一旦他们开始弃用东西或添加硬件加速的渲染或改进,您最好站在巨人的肩膀上(又名使用最后的Apple API)
Juan Boero '19

有人知道renderer与旧的drawHierarchy.. 相比性能如何吗?
Vive

24

要改善@Tommy和@Dima的答案,请使用以下类别将UIView渲染为具有透明背景且质量不受影响的UIImage 。在iOS7上工作。(或者只是在实现中重用该方法,self引用为图像)

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
如果我对afterScreenUpdates:YES使用drawViewHierarchyInRect,则我的图像纵横比已更改并且图像变形。
提交

更改uiview内容时会发生这种情况吗?如果是,则尝试再次重新生成此UIImage。抱歉,无法自己测试,因为我在打电话
Glogo 2015年

我有同样的问题,似乎没人注意到这一点,您找到解决此问题的任何方法了吗?@confile?
Reza.Ab

这就是我所需要的,但是没有用。我试图使@interface@implementation(RenderViewToImage),加入#import "UIView+RenderViewToImage.h"到头部的ViewController.h,所以这是正确的用法?例如UIImageView *test = [[UIImageView alloc] initWithImage:[self imageByRenderingView]]; [self addSubview:test];
格雷格

而这个方法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; }
Greg

15

迅捷3

带有UIView扩展的Swift 3解决方案(基于Dima的answer)应如下所示:

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

支持新的iOS 10.0 API和先前方法的嵌入式Swift 3.0扩展。

注意:

  • iOS版本检查
  • 请注意使用defer来简化上下文清理。
  • 还将应用视图的不透明度和当前比例。
  • 不会解包任何!可能导致崩溃的内容。

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:

使用扩展方法:

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
   }

}

用法:

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

Swift 3.0实施

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

3

Swift 3的所有答案都不适用于我,因此我翻译了最受欢迎的答案:

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

这是一个基于@Dima的答案的Swift 4 UIView扩展

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

2

对于Swift 5.1,您可以使用以下扩展名:

extension UIView {

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

        return renderer.image {
            rendererContext in

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

1

UIGraphicsImageRenderer是iOS 10中引入的相对较新的API。您可以通过指定磅值构造UIGraphicsImageRenderer 。image方法接受一个Closure参数,并返回一个位图,该位图是由于执行传递的Closure而产生的。在这种情况下,结果是原始图像按比例缩小以在指定范围内绘制。

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

因此,请确保您要传递的尺寸 UIGraphicsImageRenderer是磅,而不是像素。

如果图像大于预期,则需要将尺寸除以比例因子。


我很好奇,您能帮我吗?stackoverflow.com/questions/61247577/… 我正在使用UIGraphicsImageRenderer,问题是我希望导出的图像的尺寸大于设备上的实际视图。但是,子视图(标签)的大小不够理想,因此质量下降。
的Ondrej 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

在此方法中,只需传递一个视图对象,它将返回一个UIImage对象。

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

-6

将此添加到方法的UIView类别

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

2
Hiya,虽然可以很好地回答这个问题,但请注意其他用户可能不如您熟悉。您为什么不添加一些有关此代码为何起作用的解释?谢谢!
Vogel612'2015-4-21
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.