2

There a PKCanvasView setup like this:

struct CanvasView {
  @Binding var canvasView: PKCanvasView
  let onSaved: () -> Void
  @State var toolPicker = PKToolPicker()
}


extension CanvasView: UIViewRepresentable {
  func makeUIView(context: Context) -> PKCanvasView {
      canvasView.tool = PKInkingTool(.pencil, color: .gray, width: 20)
//    #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)
  }
}

And then I use it in SwiftUI Like this:

        VStack {
            Spacer()
            CanvasView(canvasView: $canvasView, onSaved: saveDrawing)
                .aspectRatio(1.0, contentMode: .fit)
            Spacer()
        }

But the second I put it in the ScrollView to add the pan and zoom ability like this

ScrollView {    //prevents drawing with finger
        VStack {
            Spacer()
            CanvasView(canvasView: $canvasView, onSaved: saveDrawing)
                .aspectRatio(1.0, contentMode: .fit)
            Spacer()
        }
}

It stops allowing me to draw with finger, presuming that it's trying to scroll instead. I'm looking for a behaviour like the Apple's freeform app. Finger to draw and pinch to zoom, 2 fingers to pan.

erotsppa
  • 14,248
  • 33
  • 123
  • 181

1 Answers1

1

Updated

After some clarification, I've included the ability to shrink the entire drawing area smaller than the screen size.

Pencil Kit's canvas is robust with built-in zooming and panning. So there's no need to add scroll views or pan gestures, just set the canvas content's size, zoom scales and insets.

For scrolling and panning, set a large canvas content size for the drawing area:

canvasView.contentSize = CGSize(width: 1500, height: 1000)

For zooming, set the desired zoom scales:

canvasView.minimumZoomScale = 0.2
canvasView.maximumZoomScale = 4.0

For allowing the full drawing area to shrink smaller than the scrollview's frame size, add content insets:

canvasView.contentInset = UIEdgeInsets(top: 500, left: 500, bottom: 500, right: 500)

The following is a basic SwiftUI pencil app with toolpicker. This has the pinch-to-zoom, two-finger panning and finger-to-draw:

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

Note: The gif below was created using the code above on an iPad with the toolpicker moved to the side. The background color(white) was manually painted on the drawing area with the toolpicker's marker to better demonstrate the zooming and panning. Setting the scollview's background color independently of the drawing area is a needed enhancement.

The resulting app looks like this:

enter image description here

Marcy
  • 4,611
  • 2
  • 34
  • 52
  • This is pretty close, happy to award you the points. But I guess what I originally imagined is a small size canvas window with an outside grey area. You then pan the canvas within this grey plane. See this video for example: https://imgur.com/u9e6fKF do you know how to do that? It's hard to get the SwiftUI just right for the grey plane and then a pannable UIKit window inside. – erotsppa Mar 21 '23 at 02:18
  • Ah, ok. Setting the canvasView.contentInset to override the default will greatly help with that. The insets can be used to give that nice floaty look in the center of the screen. By default, the content is being pushed to the top left. Since PKCanvasView is a UIScrollView subclass, all of this should be done there I believe with SwiftUI just used to display the resulting View. – Marcy Mar 21 '23 at 04:45
  • 1
    do you mind updating the answer with that implementation? If you wish for me to create another question I can – erotsppa Mar 21 '23 at 04:53
  • The answer has been updated. Hopefully this resolves all the mechanics of the drawing canvas that is needed. The background colors might be another question but I'll take a look when I have a little more time. – Marcy Mar 21 '23 at 06:19
  • That is perfect, do you mind sharing the final code for that? As a project or updated answer? I've tried to replicate what you have, I couldn't. I've tried setting the background of CanvasView() in swiftui, didn't work. I've tried setting backgroundColor of UIView and that didn't work either. – erotsppa Mar 21 '23 at 13:40
  • With the code above, the drawing's background was manually painted white before creating the gif. By default, canvasView.drawing is nil. To programmatically achieve the white drawing background independently of the canvasView's background is to initialize the drawing's background with PKDrawing's init(data:). That requires using a previously saved PKDrawing in Data format. – Marcy Mar 21 '23 at 16:59
  • Another question may be best for confirming how to set the PKDrawing's background color as it seems somewhat involved. There may be a better way that is unknown to me. I will make an attempt to do it programmatically. – Marcy Mar 21 '23 at 17:11
  • 1
    I've created a new question here https://stackoverflow.com/questions/75804683/how-to-make-pkcanvasview-float-pan-in-the-middle-of-a-fixed-background would love to you the code you have that produced your gif – erotsppa Mar 21 '23 at 17:43