81

I've found this code:

func screenShotMethod() {
    //Create the UIImage
    UIGraphicsBeginImageContext(view.frame.size)
    view.layer.renderInContext(UIGraphicsGetCurrentContext())
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    //Save it to the camera roll
    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}

What do I need to do to get all the other elements, like the navigation bar, into the screenshots?

4
  • Do you know how to take full screen screenshot in objective C?
    – Mohit
    Commented Aug 22, 2014 at 14:07
  • yeah, but i don't know the relative code. Commented Aug 22, 2014 at 14:10
  • CALayer *layer = [[UIApplication sharedApplication] keyWindow].layer; CGFloat scale = [UIScreen mainScreen].scale; UIGraphicsBeginImageContextWithOptions(layer.frame.size, NO, scale); [layer renderInContext:UIGraphicsGetCurrentContext()]; image = UIGraphicsGetImageFromCurrentImageContext(); this is to take full screen screen shot in objective C convert it as par swift
    – Mohit
    Commented Aug 22, 2014 at 14:11
  • thanks, but there's not the StatusBar. Commented Aug 22, 2014 at 14:20

22 Answers 22

88

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.

6
  • When I try this to capture the screen the image, the result is an image which is a bit pixelated. I can't figure out why. Do you have any idea David?
    – Sam
    Commented Jul 31, 2015 at 8:29
  • 1
    Notice that I didn't change any of the OPs code, just gave a push in the right direction. The pixelated look you are seeing comes from the low scale factor of the image context. If you read the documentation for UIGraphicsBeginImageContext you will see that it calls UIGraphicsBeginImageContextWithOptions with a scale factor of 1.0. Reading its documentation will tell you that you can set the scale factor to 0.0 to be the same as your main screen. Commented Jul 31, 2015 at 11:10
  • Thanks David. Indeed setting the scale factor to 0 worked.
    – Sam
    Commented Aug 14, 2015 at 13:49
  • I know this is an old thread, but is there a way to capture everything that's rendered on the screen, including an alert?
    – u84six
    Commented Aug 11, 2016 at 15:47
  • 1
    @AlexanderKhitev I don't think it's possible since the keyboard is not part of your application. It would be a privacy nightmare if you were able to capture a screenshot of the whole screen (eg. iPad with two apps in split screen).
    – ndreisg
    Commented Oct 22, 2019 at 5:51
71

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.
9
  • 3
    Great method ! It captures the image in full retina resolution thanks to the scale. Thanks very much for this code ! Yan-
    – Fox5150
    Commented Sep 13, 2015 at 11:52
  • 1
    unfortunately this simply DOES NOT include any CATransform3D you have applied to any layers throughout the image.
    – Fattie
    Commented Aug 1, 2016 at 18:20
  • 1
    Using swift 3.0 under iOS 10.x you need to add the key <key>NSPhotoLibraryUsageDescription</key> <string>Add to your photo library</string> to your Info.plist Commented Jan 17, 2017 at 11:35
  • 1
    how to include status bar too in screenshot ?
    – bLacK hoLE
    Commented Nov 7, 2017 at 8:41
  • 1
    not really correct as you cannot return nil if the type is optional your line "guard let context = UIGraphicsGetCurrentContext() else {return nil}" is incorrect Commented Jul 3, 2018 at 9:25
44

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!
3
  • 1
    What if i want to capture custom appearance of AVPlayerViewController along with UIView? Any suggestion pls!
    – Raghuram
    Commented Feb 8, 2017 at 11:51
  • I tried this code but it doesn't capture keyboard, right? Commented Oct 23, 2018 at 23:11
  • swift 5 returns nil Commented Oct 23, 2022 at 17:13
22

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)
1
  • 2
    That's taking the screenshot of a specific view, not of the entire screen. Commented Oct 10, 2017 at 16:48
10

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

6

The recommended way to create a context in iOS 10 is to use UIGraphicsImageRenderer.

extension UIView {
    func capture() -> UIImage? {
        var image: UIImage?

        if #available(iOS 10.0, *) {
            let format = UIGraphicsImageRendererFormat()
            format.opaque = isOpaque
            let renderer = UIGraphicsImageRenderer(size: frame.size, format: format)
            image = renderer.image { context in
                drawHierarchy(in: frame, afterScreenUpdates: true)
            }
        } else {
            UIGraphicsBeginImageContextWithOptions(frame.size, isOpaque, UIScreen.main.scale)
            drawHierarchy(in: frame, afterScreenUpdates: true)
            image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
        }

        return image
    }
}
1
  • This worked very well but in the case of a scrollable view such as a collection view the resultant image was the first view not the actual current view. Commented May 19, 2017 at 16:06
4

Works with Swift 5 and iOS 13

For anyone looking for a quick answer for a function to return a screenshot of the view as a UIImage:

func getScreenshot() -> UIImage? {
    //creates new image context with same size as view
    // UIGraphicsBeginImageContextWithOptions (scale=0.0) for high res capture
    UIGraphicsBeginImageContextWithOptions(view.frame.size, true, 0.0)

    // renders the view's layer into the current graphics context
    if let context = UIGraphicsGetCurrentContext() { view.layer.render(in: context) }

    // creates UIImage from what was drawn into graphics context
    let screenshot: UIImage? = UIGraphicsGetImageFromCurrentImageContext()

    // clean up newly created context and return screenshot
    UIGraphicsEndImageContext()
    return screenshot
}

I pieced together this answer from taking the code in the question and following David Rönnqvist's suggestions (thank you for the explanation), with some tweaks.

To include the nav bar and other extras, call this method off of the window instead of the view.

I simply needed a function to get the view's screen capture, so I hope this helps anyone looking for the same

1
  • what is the view ? Commented Oct 23, 2022 at 17:14
4

Swift 5

Captures entire screen (minus status bar) without crashing on newer devices (like iPhone 12 Pro).

extension UIApplication {
    func getScreenshot() -> UIImage? {
        guard let window = keyWindow else { return nil }
        let bounds = UIScreen.main.bounds
        UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)
        window.drawHierarchy(in: bounds, afterScreenUpdates: true)
        guard let image = UIGraphicsGetImageFromCurrentImageContext() else { return nil }
        UIGraphicsEndImageContext()
        return image
    }
}

Previous solutions that use UIApplication.shared.keyWindow?.layer.render(in: context) causes a memory crash on some devices like iPhone 12 Pro.

2
  • I want to take screenshot of gif added image view and save. What I want to achieve take 50 screenshots using timer for gif movement. How can I take screenshots using timer? Commented Feb 17, 2021 at 8:27
  • 1
    where do you get keyWindow? Commented Oct 23, 2022 at 17:06
3

I use this method:

func captureScreen() -> UIImage {
    var window: UIWindow? = UIApplication.sharedApplication().keyWindow
    window = UIApplication.sharedApplication().windows[0] as? UIWindow
    UIGraphicsBeginImageContextWithOptions(window!.frame.size, window!.opaque, 0.0)
    window!.layer.renderInContext(UIGraphicsGetCurrentContext())
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image;
}

It captures all but the status bar, and it doesn't ask for permission to save images in the camera roll.

Hope it helps!

3

Swift 5

If you just need a true snapshot of the screen (with keyboard and status bar) in that instant:

let snap = UIScreen.main.snapshotView(afterScreenUpdates: false)

snap is a UIView with a frame automatically set to the bounds of the screen. Adding it to the view of a view controller will now give you a UI that appears frozen:

view.addSubview(snap)

Update for Scenes

iOS apps have now adopted scenes which renders the above solution ineffective. Instead of snapshotting the main screen, you must snapshot the main window of the active scene.

If your app only has a single scene, the following will work. If, however, you have multiple scenes, then you must first find the active scene (and then its main window).

if let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate,
   let snap = sceneDelegate.window?.snapshotView(afterScreenUpdates: false) {
    view.addSubview(snap)
}

For apps with multiple scenes: https://stackoverflow.com/a/69780721/9086770

3
  • this does not work in case you have a WKWebView that is showing AVPlayer, when swiping an mp4 video for example.
    – zumzum
    Commented Jul 15, 2020 at 17:43
  • Sorry, just tried this in IOS 16; returns a black screen Commented Nov 16, 2022 at 14:59
  • @user3069232 that is because iOS apps now rely on scenes. I've updated my answer.
    – slushy
    Commented Nov 16, 2022 at 18:49
2

Swift 3 Extension for UIWindow

public extension UIWindow {

  func capture() -> UIImage? {

    UIGraphicsBeginImageContextWithOptions(self.frame.size, self.isOpaque, UIScreen.main.scale)
    self.layer.render(in: UIGraphicsGetCurrentContext()!)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()

    return image

  }
}]
2

This is how I do it in Swift 4

let layer = UIApplication.shared.keyWindow!.layer
let scale = UIScreen.main.scale
UIGraphicsBeginImageContextWithOptions(layer.frame.size, false, scale);                 
layer.render(in: UIGraphicsGetCurrentContext()!)
let screenshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()

now screenshot will be type UIImage

1
  // Full Screen Shot function. Hope this will work well in swift.
  func screenShot() -> UIImage {                                                    
    UIGraphicsBeginImageContext(CGSizeMake(frame.size.width, frame.size.height))
    var context:CGContextRef  = UIGraphicsGetCurrentContext()
    self.view?.drawViewHierarchyInRect(frame, afterScreenUpdates: true)
    var screenShot = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext();  
    return screenShot
  }
1

This is similar, hopefully it helps someone in the future.

self.view.image() //returns UIImage

Here's a Swift 3 solution

https://gist.github.com/nitrag/b3117a4b6b8e89fdbc12b98029cf98f8

1

This code is the latest version - 100% works

func getScreenshoot() -> UIImage {
    var window: UIWindow? = UIApplication.shared.keyWindow
    window = UIApplication.shared.windows[0] as? UIWindow
    UIGraphicsBeginImageContextWithOptions(window!.frame.size, window!.isOpaque, 0.0)
    window!.layer.render(in: UIGraphicsGetCurrentContext()!)
    let image = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return image!;
}
1

Try this function() its Works:

extension View {
    func snapshot() -> UIImage {
        let size = CGSizeMake(UIScreen.main.bounds.width,UIScreen.main.bounds.height)
        let controller = UIHostingController(rootView: self.ignoresSafeArea())
        let view = controller.view
        view?.bounds = CGRect(origin: .zero, size: size)
        let renderer = UIGraphicsImageRenderer(size: size)
        return renderer.image { _ in
            view?.drawHierarchy(in:CGRect(origin: .zero, size: size), afterScreenUpdates: true)
        }
    }
}
0
1

In my use case, I will feedback with all scenes. So,

public extension UIApplication {
    @MainActor
    func screenshots() -> [UIImage] {
        let windows = connectedScenes.compactMap({ ($0 as? UIWindowScene)?.keyWindow })
        return windows.map { window in
            let renderer = UIGraphicsImageRenderer(size: window.bounds.size * window.contentScaleFactor)
            return renderer.image { context in
                window.layer.render(in: context.cgContext)
            }
        }
    }
}

And I added an extension of CGSize:

public extension CGSize {
    static func * (lhs: Self, rhs: CGFloat) -> Self {
        return .init(width: lhs.width * rhs, height: lhs.height * rhs)
    }
}
1
0

view.snapshotView(afterScreenUpdates: true)

0

My version also capture a keyboard. Swift 4.2

extension UIApplication {

    var screenshot: UIImage? {
        UIGraphicsBeginImageContextWithOptions(UIScreen.main.bounds.size, false, 0)
        guard let context = UIGraphicsGetCurrentContext() else { return nil }
        for window in windows {
            window.layer.render(in: context)
        }
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return image
    }

}
3
  • But it doesn't capture status bar, any suggestions ?
    – Pavan
    Commented May 2, 2019 at 4:47
  • 1
    @Pavan I did not know about it, because in my current app there is no status bar. Commented May 2, 2019 at 20:59
  • I was trying it on sample code, something like mimicking Screenshot through power+home button, but this code doesn't capture status bar.
    – Pavan
    Commented May 3, 2019 at 5:52
0

Swift 4 or above.

For extension and call by UIView that you capture.

Declaration

extension UIView {

    func viewCapture() -> UIImage? {

        UIGraphicsBeginImageContext(self.frame.size)

        guard let cgContext = UIGraphicsGetCurrentContext() else {
        print("Fail to get CGContext")
        return nil

    }
    self.layer.render(in: cgContext)

    guard let image = UIGraphicsGetImageFromCurrentImageContext() else {
        print("Fail to get Image from current image context")
        return nil
    }
    UIGraphicsEndImageContext()

    return image

    }
}

Usage

var m_image = UIImage()

if let tempCaptureImg = self.m_Capture_View.viewCapture() {
    viewController.m_image = tempCaptureImg
}

// m_Capture_View is type of UIView

0

After iOS 16 you can use ImageRenderer to export bitmap image data from a SwiftUI view.

Just keep in mind, you have to call it on the main thread. I used MainActor here. However, in this example, because it is firing from the Button action which is always on the main thread the MainActor is unnecessary.

struct ContentView: View {
    
    @State private var renderedImage = Image(systemName: "photo.artframe")
    @Environment(\.displayScale) var displayScale

    
    var body: some View {
        VStack(spacing: 30) {
            renderedImage
                .frame(width: 300, height: 300)
                .background(.gray)
            
            Button("Render SampleView") {
                let randomNumber = Int.random(in: 0...100)
                let renderer = ImageRenderer(content: createSampleView(number: randomNumber))
                
                /// The default value of scale property is 1.0
                renderer.scale = displayScale
                
                if let uiImage = renderer.uiImage {
                    renderedImage = Image(uiImage: uiImage)
                }
            }
        }
    }
    
    @MainActor func createSampleView(number: Int) -> some View {
        Text("Random Number: \(number)")
            .font(.title)
            .foregroundColor(.white)
            .padding()
            .background(.blue)
    }
}
0

Since UIApplication.shared.keyWindow is deprecated since iOS 13 here is updated solution for Swift 5 Xcode 14 based on @Imanou Petit's answer

extension UIWindow {

static let keyWindow: UIWindow? = {
    return UIApplication.shared.delegate?.window ?? nil
}()

@discardableResult
static func takeScreenshot(shouldSave: Bool) -> UIImage? {
    var screenshotImage: UIImage?
    guard let layer = UIWindow.keyWindow?.layer else {
        return nil
    }
    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
  }
}

Usage:

let myScreenshotImage = UIWindow.takeScreenshot(shouldSave: false)

Not the answer you're looking for? Browse other questions tagged or ask your own question.