Background Image and canvas with pencilKit Swiftui

3.3k views Asked by At

I'm learning about PencilKit.

I have a canvas, and I want to set a background image that we can draw on it.

When I save my canvas, I want my background image to be visible

But I have an error : Cannot convert value of type 'Image' to expected argument type 'UIImage?'

Image("badmintoncourt") is an image from my assets I can't find out how to solve it, but I maybe not in the right way to add a background image to my canvas

    struct Home : View {
    @State var canvas = PKCanvasView()
    @Environment(\.undoManager) private var undoManager
    @State var showingAlert = false
    var body: some View{
        
        NavigationView{
            MyCanvas(canvasView: canvas)
                .navigationTitle("Drawing")
                .navigationBarTitleDisplayMode(.inline)
        }
    }
  
}


struct MyCanvas: UIViewRepresentable {
    var canvasView: PKCanvasView
    let picker = PKToolPicker.init()
    
    func makeUIView(context: Context) -> PKCanvasView {
        self.canvasView.tool = PKInkingTool(.pen, color: .black, width: 15)
        self.canvasView.isOpaque = false
        self.canvasView.backgroundColor = UIColor.clear
        self.canvasView.becomeFirstResponder()

        let imageView = Image("badmintoncourt")

        let subView = self.canvasView.subviews[0]
            subView.addSubview(imageView)
            subView.sendSubviewToBack(imageView)
        return canvasView
    }
    
    func updateUIView(_ uiView: PKCanvasView, context: Context) {
        picker.addObserver(canvasView)
        picker.setVisible(true, forFirstResponder: uiView)
        DispatchQueue.main.async {
            uiView.becomeFirstResponder()
        }
    }
}

Edit : Here is my code to save image :

func SaveImage(){
    let image = canvas.drawing.image(from: canvas.drawing.bounds, scale: 1)
    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
}
2

There are 2 answers

3
Asperi On BEST ANSWER

The Image is not a UIView, you have to use UIImageView for this case

    let imageView = UIImageView(image: UIImage(named: "badmintoncourt"))

    let subView = self.canvasView.subviews[0]
        subView.addSubview(imageView)
        subView.sendSubviewToBack(imageView)
0
Majid On

This is how I solved this problem to allow drawing over the image and saving both in one final image. This solution makes the canvas for drawing exactly the size of the image.

I add the canvas as an overlay for the Image and store the drawing in seperate image. Once I'm done with my drawing, I merge both images into one.

struct DrawOnImageView: View {

    @Binding var image: UIImage

    let onSave: (UIImage) -> Void

    @State private var drawingOnImage: UIImage = UIImage()
    @State private var canvasView: PKCanvasView = PKCanvasView()

    init(image: Binding<UIImage>, onSave: @escaping (UIImage) -> Void) {
        self.image = image
        self.onSave = onSave
    }

    var body: some View {
        VStack {
            Button(action: { save() }, label: Text("Save"))

            Image(uiImage: self.image)
                .resizable()
                .aspectRatio(contentMode: .fit)
                .edgesIgnoringSafeArea(.all)
                .overlay(CanvasView(canvasView: $canvasView, onSaved: onChanged), alignment: .bottomLeading)
        }
    }

    private func onChanged() -> Void {
        self.drawingOnImage = canvasView.drawing.image(
            from: canvasView.bounds, scale: UIScreen.main.scale)
    }

    private func initCanvas() -> Void {
        self.canvasView = PKCanvasView();
        self.canvasView.isOpaque = false
        self.canvasView.backgroundColor = UIColor.clear
        self.canvasView.becomeFirstResponder()
    }

    private func save() -> Void {
        onSave(self.image.mergeWith(topImage: drawingOnImage))
    }
}

This extension to UIImage will allow you to merge images. I used the code from this answer How to merge two UIImages?

public extension UIImage {
    func mergeWith(topImage: UIImage) -> UIImage {
        let bottomImage = self

        UIGraphicsBeginImageContext(size)


        let areaSize = CGRect(x: 0, y: 0, width: bottomImage.size.width, height: bottomImage.size.height)
        bottomImage.draw(in: areaSize)

        topImage.draw(in: areaSize, blendMode: .normal, alpha: 1.0)

        let mergedImage = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return mergedImage
    }
}

Finally, this is my canvas view, even though I think MyCanvas in your code should work just fine. My view is based from this PencilKit tutorial.

struct CanvasView {
    @Binding var canvasView: PKCanvasView
    let onSaved: () -> Void

    @State var toolPicker = PKToolPicker()
}

extension CanvasView: UIViewRepresentable {
    func makeUIView(context: Context) -> PKCanvasView {
        canvasView.tool = PKInkingTool(.pen, color: .gray, width: 10)
        #if targetEnvironment(simulator)
        canvasView.drawingPolicy = .anyInput
        #endif
        canvasView.delegate = context.coordinator
        showToolPicker()
        return canvasView
    }

    func updateUIView(_ uiView: PKCanvasView, context: Context) {}

    func makeCoordinator() -> Coordinator {
        Coordinator(canvasView: $canvasView, onSaved: onSaved)
    }
}

private extension CanvasView {
    func showToolPicker() {
        toolPicker.setVisible(true, forFirstResponder: canvasView)
        toolPicker.addObserver(canvasView)
        canvasView.becomeFirstResponder()
    }
}

class Coordinator: NSObject {
    var canvasView: Binding<PKCanvasView>
    let onSaved: () -> Void

    init(canvasView: Binding<PKCanvasView>, onSaved: @escaping () -> Void) {
        self.canvasView = canvasView
        self.onSaved = onSaved
    }
}

extension Coordinator: PKCanvasViewDelegate {
    func canvasViewDrawingDidChange(_ canvasView: PKCanvasView) {
        if !canvasView.drawing.bounds.isEmpty {
            onSaved()
        }
    }
}