PujolDeveloper
PujolDeveloper

Reputation: 85

SwiftUI exporting the content of Canvas

Does anyone know how to export the content of a Canvas into an Image?

With SwiftUI, it is possible to generate an Image from a View with an extension

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

This works great for simple views like Button, but for Canvas it always generates an empty image.

For example, with the following code, the image generated by the button is fine, but the one of the Canvas is always empty.

import SwiftUI

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

struct ContentView: View {
    var textView: some View {
        Text("Hello, SwiftUI")
            .padding()
            .background(Color.green)
            .foregroundColor(.white)
            .clipShape(Capsule())
        
    }
    var canvas: some View {
        Canvas { context, size in
            var path = Path()
            path.move(to: CGPoint(x: 0, y:0))
            path.addLine(to: CGPoint(x: size.width/2, y:0))
            path.addLine(to: CGPoint(x: size.width/2, y:size.height/2))
            path.addLine(to: CGPoint(x: 0, y:size.height/2))
            path.closeSubpath()
            
            context.fill(path, with: .color(.blue))
            
        }
    }
    
    var body: some View {
        VStack {
            textView
            canvas
            
            Button("Save to image: Canvas") {
                if let view = canvas as? Canvas<EmptyView> {
                    let image = view.snapshot()
                    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
                }
            }
            Button("Save to image: Text") {
                if let view = textView as? Text {
                    let image = view.snapshot()
                    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
                }
            }
        }
    }
}

Upvotes: 2

Views: 3357

Answers (2)

Alex Peterson
Alex Peterson

Reputation: 96

Answer is here: Swift UI exporting content of canvas. Apply the frame in the line where you get the snapshot, i.e.

let newImage = canvasView.frame(width: 300, height: 300).snapshot()

Upvotes: 0

thecanteen
thecanteen

Reputation: 784

Apply a frame to the canvas and it should work. E.g.

canvas.frame(width: 300, height: 300)

Upvotes: 1

Related Questions