How to Take Screenshots of Views in SwiftUI

In this tutorial, you'll learn to convert a SwiftUI view into an UIImage/Image using only a few lines of swift code.

Share:

Mar 19, 2023 512 Words

Read Time: 3 Minutes

A screenshot of code-snippet to use ImageRendere in SwiftUI in iOS

Introduction

In SwiftUI, you can use ImageRenderer1 to take screenshots or images as an UIImage object for any SwiftUI view.

Let’s consider a simple Text view in SwiftUI. The goal is to take a snapshot of it using ImageRenderer object.

import SwiftUI

struct ContentView: View {
	var body: some View {
			VStack {
				 Text("Hello, world!")
			}
	}
}

This is how this renders in the Xcode iOS device simulator

A screenshot of a Xcode iPhone simulator saying hello world

How to use ImageRenderer?

Now, let’s try to create a snapshot of this view using ImageRenderer feature.

import SwiftUI

struct ContentView: View {
    var body: some View {
        VStack {
            Spacer()
            VStack {
                Text("Hello, world!")
            }
            Spacer()
            snapshot().resizable().aspectRatio(contentMode: .fit)
                .frame(maxWidth: 100, maxHeight: 100).border(.red)
            Spacer()
        }
    }
    
    @MainActor func snapshot() -> Image {
        let imagerenderer = ImageRenderer(
            content: VStack {
                Text("Hello, world!")
            }.frame(maxWidth: 100, maxHeight: 100)
        )
        return Image(uiImage: imagerenderer.uiImage!)
    }
}

💡 If the view gets complex, which it inevitably does, it’s better to define it in a function or variable so that it can be reused in snapshot function without too much duplication.

I’ve abstracted the view to be captured, in it’s own variable to clean up duplicate code,

import SwiftUI

struct ContentView: View {
    
    var viewToBeSnapshotted: some View {
        VStack {
            Text("Hello, world!")
        }
    }
    
    var body: some View {
        VStack {
            Spacer()
            VStack {
                Spacer()
                Text("↓ The View").underline()
                Spacer()
                viewToBeSnapshotted
                Spacer()
            }
            Spacer()
            VStack {
                Spacer()
                Text("↓ The Snapshot").underline()
                Spacer()
                snapshot().resizable().aspectRatio(contentMode: .fit)
                    .frame(maxWidth: 100, maxHeight: 100).border(.red)
                Spacer()
            }
           
            Spacer()
        }
    }
    
    @MainActor func snapshot() -> Image {
        let imagerenderer = ImageRenderer(
            content: viewToBeSnapshotted.frame(maxWidth: 100, maxHeight: 100)
        )
        return Image(uiImage: imagerenderer.uiImage!)
    }
}

The modifiers and annotations around the snapshot image is so that the view and the related ‘snapshot’ are presented cleanly in the demonstration

A screenshot of ImageRenderer in SwiftUI working it’s magic

This is the simplest implementation of ImageRenderer in action.

To understand how awesome this is, I would like to demonstrate a cool experiment below.

Code Snippet

Let’s say the view contains a variable that changes in real-time. We want the corresponding snapshot function to also capture the changes in real-time.

Say the the text contains a number variable that can increase/decrease on some trigger

Here’s the code:

import SwiftUI

struct ContentView: View {
    
    @State var number: CGFloat = 0
    
    var viewToBeSnapshotted: some View {
        VStack {
            Text("The number is \(number)")
        }
    }
    
    var body: some View {
        VStack {
            Spacer()
            VStack {
                Spacer()
                Text("↓ The View").underline()
                Spacer()
                viewToBeSnapshotted
                Spacer()
            }
            Spacer()
            VStack {
                Spacer()
                Text("↓ The Snapshot").underline()
                Spacer()
                snapshot().resizable().aspectRatio(contentMode: .fit)
                    .frame(height: 100).border(.red)
                Spacer()
            }
            Spacer()
            HStack {
                Spacer()
                Text("Slide to update number →")
                Slider(
                    value: $number,
                    in: 0...100,
                    step: 1,
                    label: {
                        Text("Number")
                    }
                )
                Spacer()
            }
        }
    }

I’ve added a simple slider2 that updates a @State variable called number from 0 to 100. This number is added part of the Text view.

When the slider updates the number, the view and the corresponding snapshot update in real-time.

Demonstration

A gif image of ImageRenderer working live on a dynamic Text view with variables

In this tutorial, you’ve learnt how to generate a snapshot of SwiftUI view in only a few lines of swift code.

👋 – @TnvMadhav

References