How do I take a full screen Screenshot in Swift?

Solution 1:

Instead of just throwing the answer out there, let me explain what your current code does and how to modify it to capture the full screen.

UIGraphicsBeginImageContext(view.frame.size)

This line of code creates a new image context with the same size as view. The main thing to take away here is that the new image context is the same size as view. Unless you want to capture a low resolution (non-retina) version of your application, you should probably be using UIGraphicsBeginImageContextWithOptions instead. Then you can pass 0.0 to get the same scale factor as the devices main screen.

view.layer.renderInContext(UIGraphicsGetCurrentContext())

This line of code will render the view's layer into the current graphics context (which is the context you just created). The main thing to take away here is that only view (and its subviews) are being drawn into the image context.

let image = UIGraphicsGetImageFromCurrentImageContext()

This line of code creates an UIImage object from what has been drawn into the graphics context.

UIGraphicsEndImageContext()

This line of code ends the image context. It's clean up (you created the context and should remove it as well.


The result is an image that is the same size as view, with view and its subviews drawn into it.

If you want to draw everything into the image, then you should create an image that is the size of the screen and draw everything that is on screen into it. In practice, you are likely just talking about everything in the "key window" of your application. Since UIWindow is a subclass of UIView, it can be drawn into a image context as well.

Solution 2:

Swift 4

    /// Takes the screenshot of the screen and returns the corresponding image
    ///
    /// - Parameter shouldSave: Boolean flag asking if the image needs to be saved to user's photo library. Default set to 'true'
    /// - Returns: (Optional)image captured as a screenshot
    open func takeScreenshot(_ shouldSave: Bool = true) -> UIImage? {
        var screenshotImage :UIImage?
        let layer = UIApplication.shared.keyWindow!.layer
        let scale = UIScreen.main.scale
        UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
        guard let context = UIGraphicsGetCurrentContext() else {return nil}
        layer.render(in:context)
        screenshotImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        if let image = screenshotImage, shouldSave {
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        }
        return screenshotImage
    }

Updated for Swift 2

The code you provide works but doesn't allow you to capture the NavigationBar and the StatusBar in your screenshot. If you want to take a screenshot of your device that will include the NavigationBar, you have to use the following code:

func screenShotMethod() {
    let layer = UIApplication.sharedApplication().keyWindow!.layer
    let scale = UIScreen.mainScreen().scale
    UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);

    layer.renderInContext(UIGraphicsGetCurrentContext()!)
    let screenshot = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    UIImageWriteToSavedPhotosAlbum(screenshot, nil, nil, nil)
}

With this code:

  • The first time you launch the app and call this method, the iOS device will ask you the permission to save your image in the camera roll.
  • The result of this code will be a .JPG image.
  • The StatusBar will not appear in the final image.

Solution 3:

Details

  • Xcode 9.3, Swift 4.1
  • Xcode 10.2 (10E125) and 11.0 (11A420a), Swift 5

Tested on iOS: 9, 10, 11, 12, 13

Solution

import UIKit

extension UIApplication {

    func getKeyWindow() -> UIWindow? {
        if #available(iOS 13, *) {
            return windows.first { $0.isKeyWindow }
        } else {
            return keyWindow
        }
    }

    func makeSnapshot() -> UIImage? { return getKeyWindow()?.layer.makeSnapshot() }
}


extension CALayer {
    func makeSnapshot() -> UIImage? {
        let scale = UIScreen.main.scale
        UIGraphicsBeginImageContextWithOptions(frame.size, false, scale)
        defer { UIGraphicsEndImageContext() }
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        render(in: context)
        let screenshot = UIGraphicsGetImageFromCurrentImageContext()
        return screenshot
    }
}

extension UIView {
    func makeSnapshot() -> UIImage? {
        if #available(iOS 10.0, *) {
            let renderer = UIGraphicsImageRenderer(size: frame.size)
            return renderer.image { _ in drawHierarchy(in: bounds, afterScreenUpdates: true) }
        } else {
            return layer.makeSnapshot()
        }
    }
}

extension UIImage {
    convenience init?(snapshotOf view: UIView) {
        guard let image = view.makeSnapshot(), let cgImage = image.cgImage else { return nil }
        self.init(cgImage: cgImage, scale: image.scale, orientation: image.imageOrientation)
    }
}

Usage

imageView.image = UIApplication.shared.makeSnapshot()

// or
imageView.image = view.makeSnapshot()

// or
imageView.image = view.layer.makeSnapshot()

// or
imageView.image = UIImage(snapshotOf: view)

Old solution

Xcode 8.2.1, swift 3

Version 1 for iOS 10x

import UIKit

extension UIApplication {

    var screenShot: UIImage?  {

        if let layer = keyWindow?.layer {
            let scale = UIScreen.main.scale

            UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);
            if let context = UIGraphicsGetCurrentContext() {
                layer.render(in: context)
                let screenshot = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()
                return screenshot
            }
        }
        return nil
    }
}

Version 2 for iOS 9x, 10x

If you will try to use Version 1 code in iOS 9x you will have error: CGImageCreateWithImageProvider: invalid image provider: NULL.

import UIKit

extension UIApplication {

    var screenShot: UIImage?  {

        if let rootViewController = keyWindow?.rootViewController {
            let scale = UIScreen.main.scale
            let bounds = rootViewController.view.bounds
            UIGraphicsBeginImageContextWithOptions(bounds.size, false, scale);
            if let _ = UIGraphicsGetCurrentContext() {
                rootViewController.view.drawHierarchy(in: bounds, afterScreenUpdates: true)
                let screenshot = UIGraphicsGetImageFromCurrentImageContext()
                UIGraphicsEndImageContext()
                return screenshot
            }
        }
        return nil
    }
}

Usage

let screenShot = UIApplication.shared.screenShot!

Solution 4:

Swift UIImage extension:

extension UIImage {

    convenience init?(view: UIView?) {
        guard let view: UIView = view else { return nil }

        UIGraphicsBeginImageContextWithOptions(view.bounds.size, false, UIScreen.main.scale)
        guard let context: CGContext = UIGraphicsGetCurrentContext() else {
            UIGraphicsEndImageContext()
            return nil
        }

        view.layer.render(in: context)
        let contextImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        guard
            let image: UIImage = contextImage,
            let pngData: Data = image.pngData()
            else { return nil }

        self.init(data: pngData)
    }

}

Usage:

let myImage: UIImage? = UIImage(view: myView)

Solution 5:

For ease I would add an extension in it's own file

import UIKit

  public extension UIWindow {

    func capture() -> UIImage {

      UIGraphicsBeginImageContextWithOptions(self.frame.size, self.opaque, UIScreen.mainScreen().scale)
      self.layer.renderInContext(UIGraphicsGetCurrentContext()!)
      let image = UIGraphicsGetImageFromCurrentImageContext()
      UIGraphicsEndImageContext()

      return image
  }
}

call the extension as follows...

let window: UIWindow! = UIApplication.sharedApplication().keyWindow

let windowImage = window.capture()

Similarly, one could extended UIView to capture an image of that....