J.Wennergren
J.Wennergren

Reputation: 145

SwiftUI ImageRenderer, render Path to image

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?

Upvotes: 2

Views: 1033

Answers (2)

Daniel Galasko
Daniel Galasko

Reputation: 24247

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

Upvotes: 0

matyasl
matyasl

Reputation: 519

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

Upvotes: 0

Related Questions