How do I take a full screen Screenshot in Swift?
Asked Answered
O

22

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?

Obla answered 22/8, 2014 at 14:3 Comment(4)
Do you know how to take full screen screenshot in objective C?Actin
yeah, but i don't know the relative code.Obla
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 swiftActin
thanks, but there's not the StatusBar.Obla
E
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.

Ellsworthellwood answered 22/8, 2014 at 14:26 Comment(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?Trinary
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.Sanferd
Thanks David. Indeed setting the scale factor to 0 worked.Trinary
I know this is an old thread, but is there a way to capture everything that's rendered on the screen, including an alert?Madera
@DavidRönnqvist , please tell me, is it really possible to capture a keyboard on screenshot?Peacetime
@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).Census
G
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.
Gemma answered 23/8, 2014 at 0:13 Comment(9)
Great method ! It captures the image in full retina resolution thanks to the scale. Thanks very much for this code ! Yan-Moldboard
Great solution working in iOS, but could you please translate this solution for an OSX app?Sandie
@Imanou Petit All I get with this screen shot code is an all black image. Can you think what I might be doing wrong? I'm running the exact code from touches ended function of a SKSceneConductance
unfortunately this simply DOES NOT include any CATransform3D you have applied to any layers throughout the image.Wildlife
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.plistMichaeu
How to capture videos? It capture images but when i capture videos using the same code it returns black screen!Storied
how to include status bar too in screenshot ?Jocko
not really correct as you cannot return nil if the type is optional your line "guard let context = UIGraphicsGetCurrentContext() else {return nil}" is incorrectAlonsoalonzo
For Swift 5, simply replace open with public, other than that the code for Swift 4 works like a charm.Narah
T
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!
Tarsuss answered 3/12, 2016 at 22:9 Comment(3)
What if i want to capture custom appearance of AVPlayerViewController along with UIView? Any suggestion pls!Storied
I tried this code but it doesn't capture keyboard, right?Peacetime
swift 5 returns nilParaldehyde
L
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)
Leninist answered 14/9, 2016 at 21:11 Comment(1)
That's taking the screenshot of a specific view, not of the entire screen.Finely
R
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....

Roughneck answered 17/4, 2015 at 14:39 Comment(0)
J
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
    }
}
Jezabelle answered 18/4, 2017 at 8:40 Comment(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.Aubreyaubrie
R
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

Redstart answered 5/11, 2019 at 22:10 Comment(1)
what is the view ?Paraldehyde
C
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.

Capsulate answered 5/2, 2021 at 23:39 Comment(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?Ravine
where do you get keyWindow?Paraldehyde
J
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!

Johnsonian answered 12/4, 2015 at 17:25 Comment(0)
A
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://mcmap.net/q/204730/-uiapplication-shared-delegate-equivalent-for-scenedelegate-xcode11

Abbie answered 3/1, 2020 at 16:55 Comment(3)
this does not work in case you have a WKWebView that is showing AVPlayer, when swiping an mp4 video for example.Tumor
Sorry, just tried this in IOS 16; returns a black screenMichaeu
@Michaeu that is because iOS apps now rely on scenes. I've updated my answer.Abbie
S
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

  }
}]
Seaton answered 25/10, 2016 at 23:10 Comment(0)
M
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

Minimal answered 11/5, 2019 at 13:0 Comment(0)
S
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
  }
Seduce answered 31/7, 2015 at 7:22 Comment(0)
P
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

Pollak answered 21/11, 2016 at 5:23 Comment(0)
B
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!;
}
Bergman answered 24/11, 2019 at 9:2 Comment(0)
M
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)
        }
    }
}
Mackmackay answered 15/2, 2023 at 13:28 Comment(0)
C
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)
    }
}
Canonical answered 11/5 at 5:44 Comment(1)
Working like a charm.Chaetopod
L
0

view.snapshotView(afterScreenUpdates: true)

Logarithm answered 8/9, 2016 at 15:20 Comment(0)
P
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
    }

}
Peacetime answered 24/10, 2018 at 10:47 Comment(3)
But it doesn't capture status bar, any suggestions ?Responsible
@Responsible I did not know about it, because in my current app there is no status bar.Peacetime
I was trying it on sample code, something like mimicking Screenshot through power+home button, but this code doesn't capture status bar.Responsible
R
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

Rewarding answered 7/3, 2019 at 12:15 Comment(0)
J
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)
    }
}
Jumpoff answered 10/12, 2022 at 21:2 Comment(0)
S
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)
Simpleton answered 16/1, 2023 at 20:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.