swiftuiscreenshotscenekitscnscenesceneview

How to screenshot a SCNScene or SceneView?


Goal: Taking screenshot of a .usdz file displayed in SceneView

Issue: Empty/Blank screenshot image

Full Context: I have been stuck at this problem for a couple of days now, have tried every solution I can find out there, tried to devise my own as well but nothing has worked so far. My goal is a simple one: Taking screenshot of a .usdz file being displayed in a SwiftUI project. I am loading up the .usdz file in SCNScene and displaying that scene in a SceneView, when I try to take screenshot of that SCNScene directly or via any of its hierarchal containers, screenshot ends up empty/blank. Whereas same code for taking screenshot works on any other view in the app.

Here is my the code of my file that I cleaned up to make it relevant to the problem:

import SwiftUI
import SceneKit

struct USDZModelView: View {
    
    @State private var scene: SCNScene?
    @State var takeSnapshot: Bool = false
    @State var savedImage: UIImage?
    
    init(url: URL) {
        do {
            self._scene = State(initialValue: try SCNScene(url: url, options: nil))
            self.scene?.background.contents = UIColor.gray
        } catch {
            
        }
    }
    
    var body: some View {
        NavigationStack {
            let myView = VStack {
                if let scene = scene {
                    SceneView(
                        scene: scene,
                        options: [.allowsCameraControl, .autoenablesDefaultLighting]
                    )
                } else {
                    
                }
                
                Button("Take SS") {
                    takeSnapshot = true
                }
            }
            myView
                .onChange(of: takeSnapshot) {
                    let img = myView.snapshot()
                    savedImage = img
                    // View the image however, upload on firestore and view, show in another view via state variable etc etc to view the image, all show the same result
                    // Firestore upload manager code was here - removed it for the question
                }
            
            if let img = savedImage {
                Image(uiImage: img)
                    .border(Color.red)
                    .background(.purple)
            }
        }
        .navigationBarBackButtonHidden(true)
    }
}

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)
        }
    }
}

USDZModelView is displayed from another view before it via .fullScreenCover

Here is a link to a sample .usdz file I found on the internet if any1 is interested in trying the code with the .usdz file in order to find a solution.


Solution

  • I resolved my issue by implementing the same functionality in Swift/UIKit. SCNView in UIKit has a built in snapshot functionality that works perfectly. Implemented this functionality in a UIViewController and integrated that in my SwiftUI app.