如何将UIView转换为图像


93

我想将UIView转换为图像并将其保存在我的应用程序中。有人可以告诉我如何拍摄视图的屏幕截图或将其转换为图像,以及将其保存在应用程序(而不是相机胶卷)中的最佳方法是什么?这是该视图的代码:

var overView   = UIView(frame: CGRectMake(0, 0, self.view.frame.width/1.3, self.view.frame.height/1.3))
overView.center = CGPointMake(CGRectGetMidX(self.view.bounds),
CGRectGetMidY(self.view.bounds)-self.view.frame.height/16);
overView.backgroundColor = UIColor.whiteColor()
self.view.addSubview(overView)
self.view.bringSubviewToFront(overView)

Answers:


187

扩展UIView应该可以解决问题。

extension UIView {

    // Using a function since `var image` might conflict with an existing variable
    // (like on `UIImageView`)
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

苹果不鼓励使用UIGraphicsBeginImageContext引入P3色域的iOS 10操作系统。UIGraphicsBeginImageContext仅sRGB和32位。他们介绍了新的UIGraphicsImageRendererAPI,该API完全基于颜色进行管理,基于块,具有PDF和图像的子类,并自动管理上下文生存期。查看WWDC16会话205了解更多详细信息(图像渲染在11:50左右开始)

为确保它能在所有设备上正常运行,请#available回退到iOS的早期版本:

extension UIView {

    // Using a function since `var image` might conflict with an existing variable
    // (like on `UIImageView`)
    func asImage() -> UIImage {
        if #available(iOS 10.0, *) {
            let renderer = UIGraphicsImageRenderer(bounds: bounds)
            return renderer.image { rendererContext in
                layer.render(in: rendererContext.cgContext)
            }
        } else {
            UIGraphicsBeginImageContext(self.frame.size)
            self.layer.render(in:UIGraphicsGetCurrentContext()!)
            let image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            return UIImage(cgImage: image!.cgImage!)
        }
    }
}

9
应该将其标记为正确答案。所有其他选项将导致渲染的图像失去清晰度,并看起来像素化/模糊。
TuplingD

唯一正确的方法!对于初学者来说,用法是:let image = customView.asImage()
Fabio

1
是否可以使用透明背景渲染UIView?我的有一些圆角。
ixany18年

@ixany这应该已经解决了。我只是在操场上尝试了以下方法,但它起作用了: let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100)); view.backgroundColor = .black; view.layer.cornerRadius = 9; view.asImage();
Naveed J.

2
谢谢!如果有人知道,我很想知道hackingwithswift.com/example-code/media/…的代码有什么区别:return renderer.image {ctx in drawHierarchy(in:bounds,afterScreenUpdates:true)}
Kqtr

63

你可以使用扩展

extension UIImage {
    convenience init(view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in:UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: image!.cgImage!)
    }
}

这是迅速的3/4版本:

extension UIImage {
    convenience init(view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in:UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: image!.cgImage!)
    }
}

5
我很尴尬地成为问这个的人,但是我怎么称呼这个呢?我没有图像,也没有要成为图像的UIView。上面的代码给了我一个UIImage扩展...但是现在呢?
戴夫G'8

8
let image = UIImage(view: myView)
doctorBroctor

对我不起作用,因为UIGraphicsGetCurrentContext()可以为nil并且代码崩溃。
Juan Carlos Ospina Gonzalez

@JuanCarlosOspinaGonzalez如果UIGraphicsGetCurrentContext返回nil,那么我怀疑您需要检查UIGraphicsBeginImageContext以确定失败的原因。我的猜测是您的尺寸中的一个尺寸为零或以其他方式无效,但我不知道是什么原因导致该尺寸失败。
约翰·斯蒂芬

2
您还应该记住屏幕的缩放比例,因此我将用以下内容替换初始化程序的第一行: UIGraphicsBeginImageContextWithOptions(view.frame.size, false, UIScreen.main.scale)
iVentis

41

通过drawViewHierarchyInRect:afterScreenUpdates:将UIView转换为图像,它比renderInContext快很多倍

重要说明:请勿从viewDidLoad或viewWillAppear调用此函数,请确保在显示/完全加载视图后捕获该视图

对象C

     UIGraphicsBeginImageContextWithOptions(myView.bounds.size, myView.opaque, 0.0f);
     [myView drawViewHierarchyInRect:myView.bounds afterScreenUpdates:NO];
     UIImage *snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext();
     UIGraphicsEndImageContext();

     myImageView.image =  snapshotImageFromMyView;

保存编辑的图像相册

     UIImageWriteToSavedPhotosAlbum(snapshotImageFromMyView, nil,nil, nil);

迅捷3/4

    UIGraphicsBeginImageContextWithOptions(myView.bounds.size, myView.isOpaque, 0.0)
    myView.drawHierarchy(in: myView.bounds, afterScreenUpdates: false)
    let snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    print(snapshotImageFromMyView)
    myImageView.image = snapshotImageFromMyView

具有扩展功能的超简单通用化,iOS11,swift3 / 4

extension UIImage{
    convenience init(view: UIView) {

    UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
    view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    self.init(cgImage: (image?.cgImage)!)

  }
}


Use : 
 //myView is completly loaded/visible , calling this code after only after viewDidAppear is call
 imgVV.image = UIImage.init(view: myView)
 // Simple image object
 let img =  UIImage.init(view: myView)

3
没有为我工作,我为UiImage安装了blackScreen
Badr

真的?? 您可以在此处粘贴代码吗?U可能使用了错误的UIView对象来转换为UIImage。
ViJay Avhad '17

`func convertViewIntoImg()-> Void {imgFakeCard.image = UIImage.init(view:card)}`在您的swift 3扩展名中,此函数在viewDidLoad中调用
Badr Filali

1
感谢您发布代码并提及您已在viewDidLoad中调用。这是您出问题的地方。您正在捕获视图快照,然后将其加载到内存中。请尝试在viewDidAppear中调用代码,以确保可以解决问题。如有任何疑问,请回复。
ViJay Avhad

这项工作非常感谢,问题出在我给您打电话的扩展名
Badr Filali

20

在iOS 10上:

extension UIImage {
    convenience init(view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: (image?.cgImage)!)
    }
}

1
这会导致崩溃。
KingPolygon

18

iOS 10和Swift 3的最佳做法

虽然仍支持iOS 9和更早版本, 但自iOS 13起,仍可使用Xcode 11.1,Swift 5.1

extension UIView {

    func asImage() -> UIImage? {
        if #available(iOS 10.0, *) {
            let renderer = UIGraphicsImageRenderer(bounds: bounds)
            return renderer.image { rendererContext in
                layer.render(in: rendererContext.cgContext)
            }
        } else {
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
            defer { UIGraphicsEndImageContext() }
            guard let currentContext = UIGraphicsGetCurrentContext() else {
                return nil
            }
            self.layer.render(in: currentContext)
            return UIGraphicsGetImageFromCurrentImageContext()
        }
    }
}

我不确定问题的含义是:

将其保存在应用程序(不是相机胶卷)中的最佳方法是什么?


16
    UIGraphicsBeginImageContext(self.view.bounds.size);        
    self.view.layer.renderInContext(UIGraphicsGetCurrentContext())
    var screenShot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();


1
UIGraphicsGetImageFromCurrentImageContext返回UIImage。无需将其投放到UIImage
Leo Dabus 2015年

这需要整个视图的屏幕截图吗?我只想将UIView即我的代码中的概述(即self.view的子视图)转换为图像。不是整个视图!
Sameer Hussain 2015年

@Vakas当我尝试打印image.it时显示得太小了。这是什么解决方案
Krutarth Patel 16/09/24

6
屏幕快照图像质量不好,怎么办?
Neela

13

例如,如果我有一个尺寸视图:100 100时50 50。我可以使用以下屏幕截图:

    UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), false, 0);
    self.view.drawViewHierarchyInRect(CGRectMake(-50,-5-,view.bounds.size.width,view.bounds.size.height), afterScreenUpdates: true)
    var image:UIImage = UIGraphicsGetImageFromCurrentImageContext();

在self.view行中,参数有误。我认为应该是-5而不是-5-
Krutarth Patel

@sameer我有一个问题。当我打印此图像时,它太小了
Krutarth Patel

7

我认为,使用初始化程序的方法并不是很好,因为它会创建两个图像。

我更喜欢这样:

extension UIView {
    var snapshot: UIImage? {
        UIGraphicsBeginImageContext(self.frame.size)
        guard let context = UIGraphicsGetCurrentContext() else {
            return nil
        }
        layer.render(in: context)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }
}

您应该将属性称为image以外的名称。这会干扰具有称为image的属性的子类,例如UIImageView。
霍布斯巨虎

其次@HobbestheTige的建议,并更名为财产
蒂诺

6

Swift 4.2,iOS 10

extension UIView {

    // If Swift version is lower than 4.2, 
    // You should change the name. (ex. var renderedImage: UIImage?)

    var image: UIImage? {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in layer.render(in: rendererContext.cgContext) }
    }
}

样品

let view = UIView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
view.backgroundColor = .blue

let view2 = UIView(frame: CGRect(x: 10, y: 10, width: 20, height: 20))
view2.backgroundColor = .red
view.addSubview(view2)

let imageView = UIImageView(image: view.image)

在此处输入图片说明


只是想指出一点,如果您在项目中使用任何UIImageViews,则需要将其命名为其他名称,否则.image变为只读。也许将其称为“ screenCapture”而不是“ image”。
克里斯(Chris

@克里斯是的,你是对的。如果Swift版本低于4.2,则应更改名称。感谢您指出错误。

6

这对我来说适用于Xcode 9 / Swift 3.2 / Swift 4Xcode 8 / Swift 3

 if #available(iOS 10.0, *) {

     // for Xcode 9/Swift 3.2/Swift 4 -Paul Hudson's code
     let renderer = UIGraphicsImageRenderer(size: view!.bounds.size)
     let capturedImage = renderer.image { 
         (ctx) in
         view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: true)
     }
     return capturedImage

 } else {

     // for Xcode 8/Swift 3
     UIGraphicsBeginImageContextWithOptions((view!.bounds.size), view!.isOpaque, 0.0)
     view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: false)
     let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
     UIGraphicsEndImageContext()
     return capturedImage!
 }

这是在函数中使用它的方法:

fileprivate func captureUIImageFromUIView(_ view:UIView?) -> UIImage {

     guard (view != nil) else{

        // if the view is nil (it's happened to me) return an alternative image
        let errorImage = UIImage(named: "Error Image")
        return errorImage
     }

     // if the view is all good then convert the image inside the view to a uiimage
     if #available(iOS 10.0, *) {

         let renderer = UIGraphicsImageRenderer(size: view!.bounds.size)
         let capturedImage = renderer.image { 
             (ctx) in
             view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: true)
         }
         return capturedImage

     } else {

         UIGraphicsBeginImageContextWithOptions((view!.bounds.size), view!.isOpaque, 0.0)
         view!.drawHierarchy(in: view!.bounds, afterScreenUpdates: false)
         let capturedImage = UIGraphicsGetImageFromCurrentImageContext()
         UIGraphicsEndImageContext()
         return capturedImage!
     }
}

这是处理从函数返回的图像的方法:

@IBOutlet weak fileprivate var myCustomView: UIView!
var myPic: UIImage?

let myImageView = UIImageView()

@IBAction fileprivate func saveImageButtonTapped(_ sender: UIButton) {

   myPic = captureUIImageFromUIView(myCustomView)

   // display the pic inside a UIImageView
   myImageView.image = myPic!
}

我从Paul Hudson 得到了Xcode 9 / Swift 3.2 / Swift 4的答案将uiview转换为uiimage

很久以前我从某处获得Xcode 8 / Swift 3,却忘了:(


图像在哪里显示和/或保存?
Famic Tech '18

@FamicTech一旦创建了图像,则由年轻人决定如何处理。在我的代码示例中,我只是将UIView转换为名为“ myPic”的UIImage,但对“ myPic”没有任何操作。下一步可能是在UIImageVIew中显示“ myPic”,如下所示:myImageVIew.image = myPic。如果您使用的是collectionView,并且想要在其中显示图像,则可以在每个单元格中创建一个UIImageVIew,然后通过数据源ex中的indexPath.item在UIImageVIew中显示图像(myPic):myImageView.image = dataSource [indexPath .item] .myPic
Lance Samaria

@FamicTech您在犯一个小错误。这与collectionVIew无关。您有2种不同的类类型:UIImage和UIView。它们都可以用来显示任何图像,但是它们都以不同的方式来显示。iOS中的几乎所有内容都是UIView的子类,但UIImage仅可以显示图像(2种不同的东西)。该函数的作用是获取UIVIew并将其转换为UIImage。一个简单的例子。如果要将4.0(Double)转换为4(Int),则将其转换为Int(4.0)结果为4。但是,使用此方法您可以将UIVIew中的图像转换为UIImage。理解?
Lance Samaria

我要尝试做的是让用户来到我的“收藏夹视图控制器”并按一个应该采用当前视图的按钮,该按钮恰好是“收藏夹视图” 10x10,并创建整个10x10网格的图像(请注意,整个网格在视图中不完全可见)。上面的代码是否可以解决该用例?
Famic Tech '18

该按钮位于导航控制器中。用户单击它,它将“应该”使用导航控制器下方显示的“收藏夹视图”,并使用“收藏夹视图”中“单元格”中的所有信息创建收藏夹视图的图像(PDF也可以使用)。
Famic Tech '18

5
var snapshot = overView.snapshotViewAfterScreenUpdates(false)

或在Objective-C中

UIView* snapshot = [overView snapshotViewAfterScreenUpdates:NO];

imageView与image相同吗?如果是这样,我如何将其保存在我的应用程序中?
Sameer Hussain 2015年

4

您可以使用这样的扩展名轻松地使用它

// Take a snapshot from a view (just one view)
let viewSnapshot = myView.snapshot

// Take a screenshot (with every views in screen)
let screenSnapshot = UIApplication.shared.snapshot

// Take a snapshot from UIImage initialization
UIImage(view: self.view)

如果您想使用这些扩展方法/变量,请实施此

  1. UIImage扩展

    extension UIImage {
        convenience init(view: UIView) {
            if let cgImage = view.snapshot?.cgImage {
                self.init(cgImage: cgImage)
            } else {
                self.init()
            }
        }
    }
  2. UIView扩展

    extension UIView {
    
        var snapshot: UIImage? {
            UIGraphicsBeginImageContextWithOptions(bounds.size, isOpaque, 0.0)
            if UIGraphicsGetCurrentContext() != nil {
                drawHierarchy(in: bounds, afterScreenUpdates: true)
                let screenshot = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()
                return screenshot
            }
            return nil
        }
    }
  3. UIApplication扩展

    extension UIApplication {
    
        var snapshot: UIImage? {
            return keyWindow?.rootViewController?.view.snapshot
        }
    }

我对zPositions的子视图进行了操作,其他答案没有给我正确的图层位置,谢谢。
Ashkan Ghodrat

3

或iOS 10或更高版本,您可以使用新的UIGraphicsImageRenderer +推荐的drawHierarchy,在某些情况下它可能比layer.renderInContext快得多

extension UIView {
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
        return renderer.image { _ in
            self.drawHierarchy(in: CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height), afterScreenUpdates: false)
        }
    }
}

3

谢谢@Bao Tuan Diep!我要补充。

使用代码时:

yourView.layer.render(in:UIGraphicsGetCurrentContext()!)

您必须注意:

 - If you had used `autoLayout` or `Masonry` in `yourView` (that you want to convert) .
 - If you did not add `yourView` to another view which means that `yourView` was not used as a subview but just an object.

然后,您必须使用

[yourView setNeedsLayout];
[yourView layoutIfNeeded];

yourView之前更新yourView.layer.render(in:UIGraphicsGetCurrentContext()!)

否则,您可能会得到一个不包含任何元素的图像对象


谢谢你 帮助很大!
Maksim Kniazev

2

斯威夫特4.2

import Foundation
import UIKit  

extension UIImage {

    convenience init(view: UIView) {

        UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)
        view.drawHierarchy(in: view.bounds, afterScreenUpdates: false)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: (image?.cgImage)!)

    } 
}

使用:

让img = UIImage.init(view:self.holderView)


1

Swift 3中实现

将以下代码添加到类范围之外。

extension UIImage {
    convenience init(_ view: UIView) {
        UIGraphicsBeginImageContext(view.frame.size)
        view.layer.render(in: UIGraphicsGetCurrentContext()!)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        self.init(cgImage: (image?.cgImage)!)
    }
}

用法:

let image = UIImage( Your_View_Outlet )

收到错误“绘制至少未渲染一次的视图(0x1040a6970,UIView)需要”
kishu mewara 18-4-27

0

我实现了@NaveedJ。像这样的方法,它的工作就像一个魅力。

这是他的范围:

extension UIView {

    // Using a function since `var image` might conflict with an existing variable
    // (like on `UIImageView`)
    func asImage() -> UIImage {
        let renderer = UIGraphicsImageRenderer(bounds: bounds)
        return renderer.image { rendererContext in
            layer.render(in: rendererContext.cgContext)
        }
    }
}

这是我的实现方法。

//create an image from yourView to display
//determine the frame of the view/imageimage
let screen = self.superview!.bounds
let width = screen.width / 4 //make image 1/4 width of screen
let height = width
let frame = CGRect(x: 0, y: 0, width: width, height: height)
let x = (screen.size.width - frame.size.width) * 0.5
let y = (screen.size.height - frame.size.height) * 0.5
let mainFrame = CGRect(x: x, y: y, width: frame.size.width, height: frame.size.height)

let yourView = YourView() //instantiate yourView
yourView.frame = mainFrame //give it the frame
yourView.setNeedsDisplay() //tell it to display (I am not 100% sure this is needed)

let characterViewImage = yourView.asImage()

0

自iOS 10起提供带有新UIGraphicsImageRenderer的初始化程序:

extension UIImage{
    convenience init(view: UIView) {

    let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
    let canvas = CGRect(x: 0, y: 0, width: bounds.size.width, height: bounds.size.height)
    let image = renderer.image { _ in
        self.drawHierarchy(in: canvas, afterScreenUpdates: false)
    }
    self.init(cgImage: (image?.cgImage)!)
  }
}

0

和我一起工作很好!

迅捷4

 extension UIView {

    func toImage() -> UIImage {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, self.isOpaque, 0.0)
        self.drawHierarchy(in: self.bounds, afterScreenUpdates: false)
        let snapshotImageFromMyView = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return snapshotImageFromMyView!
    }

    }

return snapshotImageFromMyView!线程1:致命错误:展开一个可选值时意外发现nil
Takasur


-1
please try below code.
-(UIImage *)getMainImageFromContext
{
UIGraphicsBeginImageContextWithOptions(viewBG.bounds.size, viewBG.opaque, 0.0);
[viewBG.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
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.