Take a screenshot in SwiftUI

Last updated 9 October 2021

By default, SwiftUI does not have built-in functions to take a screenshot, but with only a few lines of code we can implement a function to render a view as an image. The idea is to wrap the view inside a UIHostingController, and then render it into a UIGraphicsImageRenderer.

There are many ways to write this function, but it is better to be written using extension on View so you can call it naturally. Extensions allow us to add new functionality to an existing class, structure, enumeration, or protocol type. This includes the ability to extend types for which you do not have access to the original source code (known as retroactive modeling).

The first step is to wrap the view in a hosting controller, and then adjust the size of the hosting controller's view to be the intrinsic content size of the SwiftUI view, or you can provide the size by passing origin: CGPoint and size: CGSize. Next, we need to clear any background colour to keep the rendered image clean. And finally, we need to render the view into an image and return it from the function.

extension View {
    func snapshot(origin: CGPoint = .zero, size: CGSize = .zero) -> UIImage {
        let controller = UIHostingController(rootView: self.environmentObject(AppState()))
        let view = controller.view

        let targetSize = size == .zero ? controller.view.intrinsicContentSize : size
        view?.backgroundColor = .clear
        view?.bounds = CGRect(origin: origin, size: targetSize)

        let renderer = UIGraphicsImageRenderer(size: targetSize)

        return renderer.image { _ in
            view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
        }
    }
}

To use that extension in SwiftUI, you can call .snapshot() function in any views that you want to capture. For example, the following code captures a screen, then passes it to the UIActivityViewController to be shared to social media.

struct SnapshotView: View {
    var body: some View {
        GeometryReader { geometry in
            Button(action: {
                let image = self.snapshot(origin: geometry.frame(in: .global).origin, size: geometry.size)
                let activityVC = UIActivityViewController(activityItems: [image], applicationActivities: nil)
                UIApplication.shared.windows.first?.rootViewController?.present(activityVC, animated: true, completion: nil)
            }) {
                Text("Hello, World!")
            }
        }
    }
}

In order to be able to save the image, you must add NSPhotoLibraryAddUsageDescription key to your Info.plist and give a description why you want to save images to photo library. If you do not do this, the app will crash when you attempt to save the image from UIActivityViewController.