Search code examples
swiftuiviewuiimageviewuiimageuikit

Can't generate PNG file from UIView snapshot


I want to generate a .png file from UIView snapshot but it's always nil. Could you give me a hint what's wrong with my code? I'm running Catalina 10.15.2 with Xcode 11.3 and iOS 13.3.

Here's my code's snippet:

extension UIView {

    fileprivate func snapShot(view: UIView) -> UIView {

        let snapshot = view.snapshotView(afterScreenUpdates: false)
        self.addSubview(snapshot!)
        return snapshot!
    }
}

class ViewController: UIViewController {

    var image: UIImage?
    var imageView: UIImageView?

    @IBAction func take(_ sender: UIButton) {

        let oneThird = CGRect(x: 0,
                              y: 0,
                          width: view.frame.size.width / 3,
                         height: view.frame.size.height / 3)

        let snap = view.snapShot(view: self.view)
        snap.frame = oneThird

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {

            let png = snap.largeContentImage?.pngData()

            if let pngData = png {
                self.image = UIImage(data: pngData)
            }
            self.imageView = UIImageView(image: self.image)
        }
        print(self.view.subviews.indices)           // 0..<5

        print(imageView?.image as Any)              // NIL
    }
}

Any help appreciated.


Solution

  • Try the following extension:

    extension UIView {
        func getImageFromCurrentContext(bounds: CGRect? = nil) -> UIImage? {
            UIGraphicsBeginImageContextWithOptions(bounds?.size ?? self.bounds.size, false, 0.0)
            self.drawHierarchy(in: bounds ?? self.bounds, afterScreenUpdates: true)
    
            guard let currentImage = UIGraphicsGetImageFromCurrentImageContext() else {
                return nil
            }
    
            UIGraphicsEndImageContext()
    
            return currentImage
        }
    }
    

    Pass the bounds if you would like 1/3 of the view or don't provide a bounds to get the image of the entire view.

    Tested with sample code:

    import UIKit
    
    class ViewController: UIViewController {
    
        @IBOutlet private(set) var testView: UIView!
        @IBOutlet private(set) var testImageView: UIImageView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                self.testImageView.image = self.testView.getImageFromCurrentContext()
            }
        }
    }
    

    I made UIView on the storyboard have a sample label inside it and changed the background color just to verify it is in fact creating the image. Then added an UIImageView under that. Connected the UIView to testView and the UIImageView to testImageView.