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

问题描述 投票:0回答:18

我的代码在普通设备上运行良好,但在视网膜设备上创建模糊图像。

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

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

    UIImage * img = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return img;
}
uiimage uikit scale retina-display image-capture
18个回答
679
投票

从使用

UIGraphicsBeginImageContext
切换到
UIGraphicsBeginImageContextWithOptions
(如本页 所记录)。将 0.0 传递给比例(第三个参数),你将得到一个比例因子等于屏幕比例因子的上下文。

UIGraphicsBeginImageContext
使用固定比例因子 1.0,因此您实际上在 iPhone 4 上获得的图像与在其他 iPhone 上完全相同。我敢打赌,当你隐式地放大它时,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
}

239
投票

目前接受的答案现在已经过时了,至少如果你支持 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:
renderInContext:
快很多倍。 benchmark


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)

30
投票

iOS Swift

使用现代 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
    }
}

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

15
投票

斯威夫特 3

具有 UIView 扩展的 Swift 3 解决方案(基于 Dima 的回答)应该是这样的:

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

9
投票

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

extension UIView {

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

        return renderer.image { layer.render(in: $0.cgContext) }
    }
}

6
投票

Drop-in Swift 3.0 扩展,支持新的 iOS 10.0 API 和以前的方法。

注:

  • 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
投票

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

3
投票

这是基于@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
   }
}

1
投票

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

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

所以请确保您传递给

UIGraphicsImageRenderer
的尺寸是点,而不是像素。

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


0
投票

有时 drawRect 方法会产生问题,所以我得到这些答案更合适。你也可以看看它 捕获 UIView 的 UIImage 卡在 DrawRect 方法中


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

0
投票

最近一定发生了一些变化,因为我无法按照上述所有答案获得视网膜显示图像。

我最后做了什么:

    static func takeScreenshot(of viewController: UIViewController) -> UIImage? {
        guard let view = viewController.view else {
            return nil
        }

        let scale = UIScreen.main.scale
        let size = CGSize(width: view.bounds.size.width * scale, height: view.bounds.size.height * scale)

        UIGraphicsBeginImageContextWithOptions(size, view.isOpaque, scale)

        defer { UIGraphicsEndImageContext() }

        if let context = UIGraphicsGetCurrentContext() {
            context.scaleBy(x: scale, y: scale) // Set the scale of the graphics context
            
            view.layer.render(in: context)
            
            let screenshot = UIGraphicsGetImageFromCurrentImageContext()
            
            return screenshot
        }

        return nil

    }

-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;
}
© www.soinside.com 2019 - 2024. All rights reserved.