【问题标题】:How to convert a UIView to an image如何将 UIView 转换为图像
【发布时间】:2015-08-22 03:52:54
【问题描述】:

我想将 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)

【问题讨论】:

标签: ios swift uiview screenshot


【解决方案1】:

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

Apple 不鼓励在引入 P3 色域的 iOS 10 中使用 UIGraphicsBeginImageContextUIGraphicsBeginImageContext 仅 sRGB 和 32 位。他们引入了新的UIGraphicsImageRenderer API,它是完全颜色管理的、基于块的、具有 PDF 和图像的子类,并自动管理上下文生命周期。查看WWDC16 session 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!)
        }
    }
}

【讨论】:

  • 这应该被标记为正确答案。所有其他选项都会导致渲染图像失去清晰度并看起来像素化/模糊。
  • 唯一正确的方法!初学者的用法是:let image = customView.asImage()
  • 是否可以用透明背景渲染 UIView?我的有一些圆角。
  • 谢谢!如果有人知道,我很想知道hackingwithswift.com/example-code/media/… 中的代码有什么区别: return renderer.image { ctx in drawHierarchy(in: bounds, afterScreenUpdates: true) }
  • 如果视图包含 SCNScene 子视图,这不起作用
【解决方案2】:

你可以使用扩展

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

这里是 swift 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!)
    }
}

【讨论】:

  • 我很尴尬成为一个问这个的人,但是我该怎么称呼这个呢?我没有想要成为图像的图像和 UIView。上面的代码给了我一个 UIImage 扩展......但是现在呢?
  • let image = UIImage(view: myView)
  • 对我不起作用,因为 UIGraphicsGetCurrentContext() 可以为零并且代码崩溃。
  • @JuanCarlosOspinaGonzalez 如果 UIGraphicsGetCurrentContext 返回 nil,那么我怀疑您需要检查 UIGraphicsBeginImageContext 以找出失败的原因。我的猜测是您的尺寸之一是零或其他无效,但我真的不知道是什么导致它失败。
  • 你还应该记住屏幕的比例,所以我会用这个替换初始化程序的第一行:UIGraphicsBeginImageContextWithOptions(view.frame.size, false, UIScreen.main.scale)
【解决方案3】:

通过 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)

【讨论】:

  • 不适合我我的 UiImage 有一个黑屏
  • 真的吗??你能把你的代码贴在这里吗?你可能使用了错误的 UIView 对象来转换为 UIImage。
  • ` func convertViewIntoImg() -> Void { imgFakeCard.image = UIImage.init(view: card) } ` 随着你在 swift 3 中的扩展,这个函数在 viewDidLoad 中被调用
  • 感谢您发布代码并提及您已在 viewDidLoad 中调用。这是你出错的地方。您正在捕获视图的快照,然后将其加载到内存中。请尝试在 viewDidAppear 中调用代码,这肯定会解决问题。有任何问题请回复。
  • 太棒了,谢谢,问题出在我调用你的扩展程序的地方
【解决方案4】:

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

【讨论】:

  • 这会导致崩溃。
【解决方案5】:

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

我不确定这个问题是什么意思:

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

【讨论】:

    【解决方案6】:
        UIGraphicsBeginImageContext(self.view.bounds.size);        
        self.view.layer.renderInContext(UIGraphicsGetCurrentContext())
        var screenShot = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    

    【讨论】:

    • UIGraphicsGetImageFromCurrentImageContext 返回 UIImage。无需将其转换为 UIImage
    • 这会截取整个视图是吗?我只想将我的代码中的 UIView (即 self.view 的子视图)转换为图像。不是整个视图!
    • @Vakas 当我尝试打印图像时。它显示太小。有什么解决方案
    • 截图图片质量不好,怎么办?
    【解决方案7】:

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

        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-
    • @sameer 我有一个问题。当我打印这张图片时,它太小了
    【解决方案8】:

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

    我更喜欢这个:

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

    【讨论】:

    • 您应该将该属性称为图像以外的名称。这可能会干扰具有称为图像的属性的子类,例如 UIImageView。
    • 听从@HobbestheTige 的建议并重命名了属性
    【解决方案9】:

    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,您应该更改名称。感谢您指出错误。
    【解决方案10】:

    这适用于 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 convert uiview to uiimageXcode 9/Swift 3.2/Swift 4 回答

    很久以前我从 SO 的某个地方得到了 Xcode 8/Swift 3,但我忘记了在哪里 :(

    【讨论】:

    • 图片在哪里显示和/或保存?
    • @FamicTech 创建图像后,由年轻人决定如何处理它。在我的代码示例中,我只是将 UIView 转换为名为“myPic”的 UIImage,但我没有对“myPic”做任何事情。下一个可能的步骤是在 UIImageVIew 中显示“myPic”,如下所示:myImageVIew.image = myPic。如果您使用的是 collectionView 并且想要在其中显示图像,那么您在每个单元格中创建一个 UIImageVIew,然后通过数据源中的 indexPath.item 在 UIImageVIew 中显示图像(myPic),例如:myImageView.image = dataSource[indexPath .item].myPic
    • @FamicTech 你犯了一个小错误。这与collectionVIew 无关。您有 2 种不同的类类型:UIImage 和 UIView。它们都可用于显示任何图像,但它们都以不同的方式进行。 iOS 中的几乎所有东西都是 UIView 的子类,但 UIImage 只能显示图像(两种不同的东西)。这个函数所做的是获取 UIVIew 并将其转换为 UIImage。一个更简单的例子。如果您想将 4.0(一个 Double)转换为 4(一个 Int),则将其转换为 Int(4.0) 结果为 4。但是这样您就可以将 UIVIew 中的图像转换为 UIImage。明白了吗?
    • 我想要做的是让用户来到我的 Collection View Controller 并按下一个按钮,该按钮应该采用当前视图,这恰好是一个 Collection View 10x10 并创建整个图像10x10 网格(请注意,整个网格在视图中并不完全可见)。您上面的代码是否解决了该用例?
    • 按钮在导航控制器中。用户单击它,它“应该”获取导航控制器下显示的集合视图并创建集合视图的图像(PDF 也可以),其中包含集合视图中单元格中的所有信息。
    【解决方案11】:
    var snapshot = overView.snapshotViewAfterScreenUpdates(false)
    

    或在objective-c中

    UIView* snapshot = [overView snapshotViewAfterScreenUpdates:NO];
    

    【讨论】:

    • imageView 和 image 一样吗?如果是这样,我如何将其保存在我的应用程序中?
    【解决方案12】:

    您可以通过使用这样的扩展轻松使用它

    // 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. UI应用扩展

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

    【讨论】:

    • 我有视图与子视图 zPositions 被操纵,其他答案没有给我正确的层位置,谢谢。
    【解决方案13】:

    或者 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)
            }
        }
    }
    

    【讨论】:

      【解决方案14】:

      感谢@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.layer.render(in:UIGraphicsGetCurrentContext()!) 之前更新yourView

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

      【讨论】:

        【解决方案15】:

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

        【讨论】:

          【解决方案16】:

          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)”
          【解决方案17】:

          我像这样实现了@Naveed J. 的方法,效果很好。

          这是他的扩展:

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

          【讨论】:

            【解决方案18】:

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

            【讨论】:

              【解决方案19】:

              跟我一起好好工作吧!

              Swift4

               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
              【解决方案20】:

              对于包含模糊子视图的视图(例如 UIVisualEffectView 实例),只有 drawViewHierarchyInRect:afterScreenUpdates 有效。

              @ViJay Avhad's answer 在这种情况下是正确的。

              【讨论】:

                【解决方案21】:

                当视图包含 Scene Kit 子视图、Metal 或 Sprite Kit 子视图时,使用 UIGraphicsImageRenderer 不起作用。在这些情况下,请使用

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

                【讨论】:

                  【解决方案22】:
                  please try below code.
                  -(UIImage *)getMainImageFromContext
                  {
                  UIGraphicsBeginImageContextWithOptions(viewBG.bounds.size, viewBG.opaque, 0.0);
                  [viewBG.layer renderInContext:UIGraphicsGetCurrentContext()];
                  UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
                  UIGraphicsEndImageContext();
                  return img;
                  }
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2019-12-03
                    • 2011-07-25
                    • 2012-12-25
                    • 1970-01-01
                    • 2011-05-14
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多