SwiftUI ImageRenderer, render Path to image

557 Views Asked by At

I'm trying to make a simple drawing app in swift, iOS.

So i've managed to draw using Path(). Now i want to save an image of what i've drawn.

I have found on apple docs that ImageRenderer() is the thing to use nowadays. But i can't seem to make it work with Path().

This renders just fine:

 let renderer = ImageRenderer(content: Text("3"))

This doesn't:

     let renderer = ImageRenderer(content: Path { path in
       path.addLines(lines)
     }.stroke(lineWidth: 5.0))

I've spent 3 days now with different methods and hacks, but nothing really works?

Why is it that cant render a path, which is a View with swifts ImageRenderer?

2

There are 2 best solutions below

0
On

You can very well draw a Path instance. Your problem is that you are not actually stroking the path and so it looks empty. Let's fix that and also make sure that the path we draw fits nicely inside an image.

func generateImage(from path: Path) -> UIImage? {
    let originalRect = path.boundingRect
    let newPath = path.offsetBy(dx: -originalRect.minX, dy: -originalRect.minY)
    let boundingRect = newPath.boundingRect
    
    let contentToDraw = newPath
        .stroke(Color.red, lineWidth: 2)
        .frame(width: boundingRect.width, height: boundingRect.height)
        .padding()// feel free to disregard padding depending on your use case

    let renderer = ImageRenderer(content: contentToDraw)
    renderer.scale = 2 // choose a scale you prefer
    renderer.proposedSize = .init(boundingRect.size)
    let image = renderer.uiImage
    return image
}

Testing this out we can draw a shape and then preview it as an image:

let size = CGSize(width: 100, height: 100)
let pathToDraw = Path { path in
    path.move(to: CGPoint(x: 0, y: 0))
    path.addLine(to: CGPoint(x: size.width, y: 0))
    path.addLine(to: CGPoint(x: size.width, y: size.height))
    path.addLine(to: CGPoint(x: 0, y: size.height))
    path.addLine(to: CGPoint(x: 0, y: 0))
}

let image = generateImage(from pathToDraw)

resulting image

0
On

I found a working solution. It was suggested by Fault. I wrapped all the Path parts in ZStack and gave the ZStack a frame:

    struct ContentView: View {
    
    @State private var imgData:Data?
    
    var body: some View {
        Grid(alignment: .center) {
            GridRow {
                Rectangle()
                    .foregroundColor(Color.green)
                Rectangle()
                    .foregroundColor(Color.red)
                Rectangle()
                    .foregroundColor(Color.yellow)
            }
            GridRow {
                Rectangle()
                    .foregroundColor(Color.orange)
                GeometryReader { geo in
                    ViewToRender(size: geo.size)
                        .onAppear {
                            saveview(ViewToRender(size: geo.size))
                        }
                }
                Rectangle()
                    .foregroundColor(Color.blue)
            }
            GridRow {
                Rectangle()
                    .foregroundColor(Color.gray)
                Rectangle()
                    .foregroundColor(Color.brown)
                if let d=imgData, let img=UIImage(data: d) {
                    Image(uiImage: img)
                        .resizable()
                        .scaledToFit()
                }else{
                    Rectangle()
                        .foregroundColor(Color.cyan)
                }
            }
        }
    }
    
    @MainActor func saveview(_ view:ViewToRender) {
        if let data=ImageRenderer(content: view).uiImage?.pngData() {
            print("\(data)")
            imgData=data
        }
    }
    
}

struct ViewToRender:View {
    let size:CGSize
    
    init(size:CGSize) {
        self.size=size
    }
    
    var body: some View {
        ZStack {
            Path {path in
                path.move(to: CGPoint(x: 0, y: 0))
                path.addLine(to: CGPoint(x: size.width, y: 0))
                path.addLine(to: CGPoint(x: size.width, y: size.height))
                path.addLine(to: CGPoint(x: 0, y: size.height))
                path.addLine(to: CGPoint(x: 0, y: 0))
                
            }.stroke(lineWidth: 2)
                .foregroundColor(Color.red)
        }.frame(width: size.width, height: size.height)
    }
    
}