0

I've this View with which I want to let user draw over an image, and then save the image + drawing as Image in Photos.

import SwiftUI

struct DrawView2: View {
    var image: UIImage
    @State var points: [CGPoint] = []
    var body: some View {
        Image(uiImage: image)
            .resizable()
            .scaledToFit()
            .gesture(
                DragGesture()
                    .onChanged { value in
                        self.addNewPoint(value)
                    }
            )
            .overlay () {
                DrawShape(points: points)
                    .stroke(lineWidth: 50) // here you put width of lines
                    .foregroundColor(.green)
                    .clipped()
            }
            .clipShape(Rectangle())
            .contentShape(Rectangle())
        
    }
    
    private func addNewPoint(_ value: DragGesture.Value) {
        // here you can make some calculations based on previous points
        points.append(value.location)
    }
}

struct DrawShape: Shape {
    
    var points: [CGPoint]
    
    // drawing is happening here
    func path(in rect: CGRect) -> Path {
        var path = Path()
        guard let firstPoint = points.first else { return path }
        
        path.move(to: firstPoint)
        for pointIndex in 1..<points.count {
            path.addLine(to: points[pointIndex])
            
        }
        return path
    }
}

struct DrawHelper: View {
    var image: UIImage
    var body: some View {
        GeometryReader { proxy in
            VStack {
                
                let drawView = DrawView2(image: image)
                    .aspectRatio(contentMode: .fit)
                    .scaledToFit()
                    .frame(width: proxy.size.width, 
                    height: proxy.size.height / 2, alignment: .center)
                
                drawView
                    .cornerRadius(UIConstants.standardCornerRadius)
                
                Button  {
                    let renderer = ImageRenderer(content: drawView)
                    if let image = renderer.uiImage {
                        UIImageWriteToSavedPhotosAlbum (image, nil, nil, nil)
                    }
                } label: {
                    Text("Save Mask")
                }
                
            }
        }
    }
}

What's happening is: I'm able to draw on the image correctly but it saves only original image. no DrawingShape included in the final saved image.

Screenshot from the Simulator: enter image description here

Saved Image from the Photos: enter image description here

Instead of ImageRenderer, I used following method to save the view as Image but it

extension View {
    func snapshot() -> UIImage {
        let controller = UIHostingController(rootView: self)
        let view = controller.view
        
        let targetSize = controller.view.intrinsicContentSize
        //let targetSize = CGSize(width: 512, height: 384)
        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)
        }
    }
}

I tried another approach of using Canvas View but found it had the same problem.

struct DrawView3: View {
    var image: UIImage
    
    @State var points: [CGPoint] = []
    
    var body: some View {
        Canvas { context, size in
            
            context.draw(Image(uiImage: image).resizable(), in: CGRect(origin: .zero, size: size))
            
            context.stroke(
                DrawShape(points: points).path(in: CGRect(origin: .zero, size: size)),
                with: .color(.green),
                lineWidth: 50
                
            )
        }
        .gesture(
            DragGesture()
                .onChanged { value in
                    self.addNewPoint(value)
                }
                .onEnded { value in
                    // here you perform what you need at the end
                    
                }
        )
    }
    
    private func addNewPoint(_ value: DragGesture.Value) {
        // here you can make some calculations based on previous points
        points.append(value.location)
    }
    
}

1 Answers1

1

I realized that I have been snapshotting the original state of the View with ImageRenderer. When the View gets updated with the drawing of the shape, the new state was never passed to ImageRenderer.

Fixed this issue by @Binding the points var instead of defining it as @State inside the view.

struct DrawView2: View {
    var image: UIImage
    @Binding var points: [CGPoint]
    var body: some View {
...
    }
}


struct DrawingHelper: View {
...
...

    var body: some View {
     ...
    let drawView = DrawView2(image: image, points: $points)
     ...
    } 


struct DrawHelper: View {
   @State var points: [CGPoint] = []
    var image: UIImage
    var body: some View {
        GeometryReader { proxy in
            VStack {
                
                let drawView = DrawView2(image: image, points: $points)
                    .aspectRatio(contentMode: .fit)
                    .scaledToFit()
                    .frame(width: proxy.size.width, 
                    height: proxy.size.height / 2, alignment: .center)
                
                drawView
                    .cornerRadius(UIConstants.standardCornerRadius)
                
                Button  {
                    let renderer = ImageRenderer(content: drawView)
                    if let image = renderer.uiImage {
                        UIImageWriteToSavedPhotosAlbum (image, nil, nil, nil)
                    }
                } label: {
                    Text("Save")
                }
                
            }
        }
    }
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848