Snapshot of a SwiftUI View

I'm wondering if anyone has figured out how to convert a SwiftUI View into an image?


I've tried encapsulating a SwiftUI View into a UIHostingController and using the following code:


import SwiftUI

extension UIView {
    
    func image() -> UIImage {
        let renderer = UIGraphicsImageRenderer(size: self.bounds.size)
        let image = renderer.image { context in
            self.drawHierarchy(in: self.bounds, afterScreenUpdates: true)
        }
        return image
    }
}

extension UIHostingController {
    
    func image() -> Image? {

        guard let view = self.view else {
            return nil
        }
        
        let image = view.image()
        return Image(uiImage: image)
    }
}

struct RasterizedView<Content: View>: View {
    var controller: UIHostingController<Content>
    
    init(_ content: Content) {
        self.controller = UIHostingController(rootView: content)
    }
    
    var body: some View {
        controller.image()
    }
}

#if DEBUG
struct RasterizedViewTest_Previews: PreviewProvider {
    static var previews: some View {
        RasterizedView(Text("TEST"))
    }
}
#endif


This code displays nothing. I went into the debugger and looked at the UIHostingController. The view always has width of 0 and a height of 0.


I forced the view to load by adding self.loadView() as a first step in UIHostingController.image(), which results in the view having a non-zero size. However, I encountered a new problem: during runtime I receive an error that looks like this:


2019-08-17 17:41:08.737684-0400 Form[88020:27394836] [Snapshotting] View (0x7feb3200a420, UIView) drawing with afterScreenUpdates:YES inside CoreAnimation commit is not supported.


Does anyone have any experience or advice that might help me solve this problem?

I'm struggling with this too. I've almost managed to get it working but the rendered image is incorrect.


The NavigationView items bleed over into the below view (i.e. they are correct in the app but the rendered image has the navigation title and barbutton items shifted down and overapping the view just below the navigation view.


In addition if I try to capture the whole screen and extract the specific subview, the reported view size is incorrect - i.e. the attached GeomertyReader provides the size of the view however when that size is used, the extracted image also includes a region around the view.

I battled this for a while. I have a specific view that I needed to capture which was sometimes larger than the screen.


My workaround in the end was to put the view I wanted to capture in a UIHostingController which I then put in the main view of a UIScrollView (this gives me flexibility on size)


then



func image(with view: UIView) -> UIImage? {

UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, 0.0)

defer { UIGraphicsEndImageContext() }

if let context = UIGraphicsGetCurrentContext() {

view.layer.render(in: context)

if let image = UIGraphicsGetImageFromCurrentImageContext() {

return image

}

return nil

}

return nil

}

Can you share how you have managed to capture a SwiftUI view? I have tried some solutions but none of them worked.

Could you share your code?

How to take a screenshot of List in a ScrollView of SwiftUI?
I'd recommend combining these two approaches, worked like magic for me.

type the following in youtube search(Apple doesn't let me post the youtube link here):
"Hack screen capture to take photo of web info easily on your app | SwiftUI"
Use the background modifier shown in the video, it's a great idea.

Use the View and UIView extensions from this source:
https://gist.github.com/SatoTakeshiX/39e111aa93f896e58c74fdc13d19ba0a


Have you solved this? I'm trying to get it work with WebView which is causing a little bit problems because I think it should wait the view to load before taking the picture. Right now it's just white. Please let me know if you have solution.
Code Block
// Convert any SwiftUI View to UIImage
import SwiftUI
struct ContentView: View {
let imageSize: CGSize = CGSize(width: 1000, height: 1000)
var body: some View {
testView
.frame(width: 300, height: 300)
.onTapGesture {
// Capture high res image of swiftUI view
let highresImage = testView.asImage(size: imageSize)
// Now you can do what ever with the highresImage
}
}
var testView: some View {
ZStack {
Color.blue
Circle()
.fill(Color.red)
}
}
}
extension UIView {
func asImage() -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = 1
return UIGraphicsImageRenderer(size: self.layer.frame.size, format: format).image { context in
self.drawHierarchy(in: self.layer.bounds, afterScreenUpdates: true)
}
}
}
extension View {
func asImage(size: CGSize) -> UIImage {
let controller = UIHostingController(rootView: self)
controller.view.bounds = CGRect(origin: .zero, size: size)
let image = controller.view.asImage()
return image
}
}


Simple. Add an extension to view.

Code Block Swift
extension View {
func snapshot() -> UIImage {
let controller = UIHostingController(rootView: self)
let view = controller.view
let targetSize = controller.view.intrinsicContentSize
view?.bounds = CGRect(origin: .zero, size: targetSize)
view?.backgroundColor = .clear
let renderer = UIGraphicsImageRenderer(size: targetSize)
return renderer.image { _ in
view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
}
}
}

Then use body.snapshot() (assuming you use var body: Some View for your View) to take a UIImage snapshot of the View.

Code Block Swift
struct ContentView: View {
var textView: some View {
Text("Hello, SwiftUI")
.padding()
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Capsule())
}
var body: some View {
VStack {
textView
Button("Save to image") {
let image = textView.snapshot()
UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
}
}
}


Code Block extension View {
  func snapshot() -> UIImage {
    let controller = UIHostingController(rootView: self)
    let view = controller.view
    let targetSize = controller.view.intrinsicContentSize
    view?.bounds = CGRect(origin: .zero, size: targetSize)
    view?.backgroundColor = .systemBackground
    let renderer = UIGraphicsImageRenderer(size: targetSize)
    return renderer.image { _ in
      view?.drawHierarchy(in: controller.view.bounds, afterScreenUpdates: true)
    }
  }
}

I think it can help I use this in swiftui it can be used in swift too

edit: I read all replies after posting

is there a way to do the same on watchOS?

Snapshot of a SwiftUI View
 
 
Q