Capturing a long screenshot of a ScrollView in SwiftUI can be challenging, but it's quite feasible with the right approach.
In our case, capturing a screenshot of a scroll view that extends beyond the visible area of the screen requires several steps.
This is the final screenshot that we will obtain:
Let’s start with the UI:
swiftvar body: some View { ZStack { Color.teal.edgesIgnoringSafeArea(.all) ScrollView(showsIndicators: false) { scrollContent } } }
In this article, we'll use some system images and a button placed inside the scroll view (scrollContent). The button will let us capture an image and open an activity controller, allowing us to save the image locally.
swiftprivate var scrollContent: some View { VStack(spacing: 10) { Button("Share") { shareImage() } .padding(.top, 40) ForEach(0..<3) { _ in Image(systemName: "heart") .resizable() .scaledToFit() .frame(width: 200, height: 300) } Image(systemName: "person") .resizable() .scaledToFit() .frame(width: 200, height: 300) } }
The shareImage() function captures an image and attaches it to an UIActivityViewController and presents it.
swiftprivate func shareImage() { guard let screenshotImage = scrollContent.snapshot() else { return } let activityController = UIActivityViewController( activityItems: [screenshotImage], applicationActivities: nil) let vc = UIApplication.shared.windows.first!.rootViewController vc?.present(activityController, animated: true) }
In the code above, we capture an image by calling the snapshot() function on the scrollContent variable. To enable this, we declared scrollContent as a variable outside of the body.
Here is the snapshot function as a UIView extension:
swiftextension View { func snapshot() -> UIImage? { let controller = UIHostingController( rootView: self.ignoresSafeArea().fixedSize(horizontal: true, vertical: true)) guard let view = controller.view else { return nil } let targetSize = view.intrinsicContentSize // to capture entire scroll content if targetSize.width <= 0 || targetSize.height <= 0 { return nil } view.bounds = CGRect(origin: .zero, size: targetSize) view.backgroundColor = .systemTeal // set it to clear if no background color is preffered let renderer = UIGraphicsImageRenderer(size: targetSize) return renderer.image { rendereContext in view.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true) } } }
This is what happens:
- Insert the view that calls the function into a
UIHostingController. - Extract the
UIViewfrom the hosting controller. - Set the bounds to the
intrinsicContentSizeto capture the entire content. - Use
UIGraphicsImageRendererto generate aUIImagethat can be shared.
