2

In a canvasview setup, I was not able to create a pannable white background canvas on top of a grey background. See the behaviour desired:

https://i.stack.imgur.com/fYyCW.jpg

import SwiftUI
import PencilKit

struct ContentView: View {
    var body: some View {
        CanvasView()
    }
}

struct CanvasView {
    @State var canvasView: PKCanvasView = PKCanvasView()
    @State var toolPicker = PKToolPicker()
}

extension CanvasView: UIViewRepresentable {
    func makeUIView(context: Context) -> PKCanvasView {
            // canvas
        canvasView.contentSize = CGSize(width: 1500, height: 1000)
        canvasView.drawingPolicy = .anyInput
        canvasView.minimumZoomScale = 0.2
        canvasView.maximumZoomScale = 4.0
        canvasView.backgroundColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0)
        canvasView.contentInset = UIEdgeInsets(top: 500, left: 500, bottom: 500, right: 500)
        canvasView.becomeFirstResponder()
        
            //toolpicker
        toolPicker.setVisible(true, forFirstResponder: canvasView)
        toolPicker.addObserver(canvasView)
        
        return canvasView
    }
    
    func updateUIView(_ uiView: PKCanvasView, context: Context) {}
}

I have attempted

canvasView.backgroundColor = .red

and also

struct ContentView: View {
    var body: some View {
        CanvasView()
          .background {Color.grey}
    }
}

None of them produces the result desired show in the gif. Further more, the zooming right now in the code given seems to push the canvas towards the corner and it doesn't float in the middle.

erotsppa
  • 14,248
  • 33
  • 123
  • 181

2 Answers2

0

For the zooming problem you probably want to the the center of the canvas canvasView.center = // where you want the center to be

I haven't worked with PencilKit so I can't really help there, but I'd assume that CanvasView will by default take all the available space. Again, this is just a guess, but I hope it helps :)

0

After adding contentMode and scrollsToTop settings along with changes to the insets, I'm no longer seeing the drawing pulling to the top-left:

canvasView.contentMode = .center
canvasView.scrollsToTop = false

The drawing area in Pencil Kit is transparent. No setting exists for background color at this time apparently. Using PKDrawing(data:) appears to be the only way to create a background color as part of the PKDrawing. PKDrawing(data:) is used to initialize the drawing area with a previously created drawing. Thus, a drawing can be created with the code above and saved into a file(Data format) to be used as a background.

The following code is an example of saving a PKDrawing to a Data file by providing a file name:

    func saveDrawing(_ fileName: String) {
        
        let data = canvasView.drawing.dataRepresentation()
        
        let fileURL = try! FileManager.default
            .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
            .appendingPathComponent(fileName)
        
        print(fileURL)
        
        do {
            try data.write(to: fileURL, options: .atomic)
        } catch {
            print(error)
        }
    }
}

Usage:

saveDrawing("whiteBg")

Once the file(drawing) is saved look for the file name in the simulator's document directory. Use the printed fileURL to locate the file. The created file can then be dragged into the project's asset catalog and used as a background image to initialize new drawings.

The following code can be used to import the Data file of the drawing into a new PKDrawing:

func importDrawing(_ fileName: String) -> PKDrawing? {
    
    guard let data = NSDataAsset(name: fileName)?.data else { return nil }

    if let drawing = try? PKDrawing(data: data) {
        return drawing
    } else {
        return nil
    }
} 

Usage:

if let background = importDrawing("whiteBg") {
    canvasView.drawing = background
}

Result:

enter image description here

The complete updated code:

import SwiftUI
import PencilKit

struct ContentView: View {
    
    @State var canvasView = PKCanvasView()
    
    var body: some View {
        VStack {
            Button("SAVE") {
                saveDrawing("whiteBg")
            }
            .frame(height: 50)
            
            CanvasView(canvasView: $canvasView)
        }
    }
    
    
    func saveDrawing(_ fileName: String) {
        
        let data = canvasView.drawing.dataRepresentation()
        
        let fileURL = try! FileManager.default
            .url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
            .appendingPathComponent(fileName)
        
        print(fileURL)
        
        do {
            try data.write(to: fileURL, options: .atomic)
        } catch {
            print(error)
        }
    }
}

struct CanvasView {
    @Binding var canvasView: PKCanvasView
    @State var toolPicker = PKToolPicker()
}

extension CanvasView: UIViewRepresentable {
    func makeUIView(context: Context) -> PKCanvasView {

            // canvas
        canvasView.contentSize = CGSize(width: 1500, height: 1000)
        canvasView.drawingPolicy = .anyInput
        canvasView.minimumZoomScale = 0.2
        canvasView.maximumZoomScale = 4.0
        canvasView.backgroundColor  = UIColor(red : 0.9, green : 0.9, blue : 0.9, alpha : 1.0)
        canvasView.contentInset = UIEdgeInsets(top: 280, left: 400, bottom: 280, right: 400)
        canvasView.contentMode = .center
        canvasView.scrollsToTop = false
        
        if let whiteBackground = importDrawing("whiteBg") {
            canvasView.drawing = whiteBackground
        }
        
        canvasView.becomeFirstResponder()

            //toolpicker
        toolPicker.setVisible(true, forFirstResponder: canvasView)
        toolPicker.addObserver(canvasView)

        return canvasView
    }
    
    func importDrawing(_ fileName: String) -> PKDrawing? {
        
        guard let data = NSDataAsset(name: fileName)?.data else { return nil }
    
        if let drawing = try? PKDrawing(data: data) {
            return drawing
        } else {
            return nil
        }
    }

    func updateUIView(_ uiView: PKCanvasView, context: Context) {}
}
Marcy
  • 4,611
  • 2
  • 34
  • 52
  • Hi this seems like a creative..but odd implementation. I suppose the only way to have the background zoom and pan correctly you have to use PKDrawing? Is it not possible to have some kind of UIImage subview behind that? And if so, how did you get the white image to generate? Did you literally have to draw white line by line to fill the page and can you share the white background file? – erotsppa Apr 03 '23 at 03:28
  • I've done hours of research at this point I dont think there is a solution. This is even more problematic when I need to load a UIImage into the background and it not being just wait. If we revisit this old thread https://stackoverflow.com/questions/61696722/zoomable-pkcanvasview-with-underlaying-background-image we can see that someone came close with this solution https://github.com/codelynx/PKCanvasViewTester but I tested it and it works up to a certain zoom level but not over that. Can you adapt that solution to make it work fully? – erotsppa Apr 03 '23 at 05:26