ios – SwiftUI + PencilKit: Methods to Enable Each Scrolling and Pencil Drawing on an Overlay?

ios – SwiftUI + PencilKit: Methods to Enable Each Scrolling and Pencil Drawing on an Overlay?


I’m engaged on a SwiftUI app that shows a day overview with hourly slots in a scrollable view (ScrollView). The objective is to permit customers to scroll by means of the hours with their finger whereas additionally with the ability to write notes on high of this view utilizing the Apple Pencil.

To realize this, I’ve added a PKCanvasView as an overlay on high of the ScrollView. Nonetheless, I’m operating into a difficulty the place both scrolling works, however Pencil enter doesn’t, or Pencil enter works, however scrolling by means of the day overview doesn’t.

Desired Habits:

  • Finger enter ought to scroll by means of the day overview.
  • Pencil enter must be allowed on the overlay to write down notes.

What I’ve Tried:

  1. Including PKCanvasView as an Overlay:
    Positioned PKCanvasView as an overlay on high of the ScrollView utilizing ZStack.
    Set drawingPolicy to .pencilOnly, in order that solely the Apple Pencil can work together with the PKCanvasView.
    Pencil enter works, but it surely fully blocks the underlying ScrollView from detecting finger touches for scrolling.

  2. Embedding PKCanvasView within the ScrollView:
    Tried including PKCanvasView contained in the scrollable content material, so it will theoretically share the scrollable space.
    esult: Scrolling with a finger works, however I can now not write with the Apple Pencil on the canvas overlay.

  3. Customized Gesture Recognizers:
    Tried utilizing UIGestureRecognizerDelegate to selectively allow contact dealing with solely End result: Both the finger scrolling or the Pencil enter was captured, however not each collectively as wanted.

I attempted to determine a minimal reproducible instance:

import SwiftUI
import PencilKit

struct DayOverviewView: View {
    let hours = Array(0..<24)
    
    @StateObject non-public var canvasViewModel = CanvasViewModel()
    @State non-public var currentHour = Calendar.present.element(.hour, from: Date())

    var physique: some View {
        ZStack {
            // Most important ScrollView content material for day overview
            ScrollViewReader { proxy in
                ScrollView {
                    VStack(spacing: 0) {
                        ForEach(hours, id: .self) { hour in
                            HStack {
                                Textual content("(hour):00")
                                    .body(width: 60, alignment: .main)
                                Divider().body(peak: 50)
                                Spacer()
                            }
                            .padding(.vertical, 20)
                            .background(hour == currentHour ? Colour.blue.opacity(0.2) : Colour.clear) // Spotlight present hour
                            .cornerRadius(8)
                            .id(hour)
                        }
                    }
                }
                .onAppear {
                    withAnimation {
                        proxy.scrollTo(currentHour, anchor: .high) // Scroll to the present hour
                    }
                }
            }

            // PKCanvasView overlay, for Pencil enter solely
            PKCanvasRepresentable(
                canvasView: $canvasViewModel.canvasView,
                toolPicker: $canvasViewModel.toolPicker,
                isToolPickerVisible: $canvasViewModel.isToolPickerVisible
            )
            .background(Colour.clear)
            .edgesIgnoringSafeArea(.all)
        }
    }
}

// CanvasViewModel to handle PKCanvasView state
class CanvasViewModel: ObservableObject {
    @Printed var canvasView = PKCanvasView()
    @Printed var toolPicker = PKToolPicker()
    @Printed var isToolPickerVisible = false

    init() {
        canvasView.drawingPolicy = .pencilOnly // Enable solely Pencil enter
        toolPicker.setVisible(true, forFirstResponder: canvasView)
        canvasView.becomeFirstResponder()
    }
}

// PKCanvasRepresentable to wrap PKCanvasView for SwiftUI
struct PKCanvasRepresentable: UIViewRepresentable {
    @Binding var canvasView: PKCanvasView
    @Binding var toolPicker: PKToolPicker
    @Binding var isToolPickerVisible: Bool

    func makeUIView(context: Context) -> PKCanvasView {
        canvasView.software = PKInkingTool(.pen, colour: .black, width: 5)
        canvasView.isOpaque = false
        canvasView.backgroundColor = .clear
        canvasView.drawingPolicy = .pencilOnly // Pencil-only enter
        return canvasView
    }

    func updateUIView(_ uiView: PKCanvasView, context: Context) {
        if isToolPickerVisible {
            toolPicker.setVisible(true, forFirstResponder: uiView)
            uiView.becomeFirstResponder()
        } else {
            toolPicker.setVisible(false, forFirstResponder: uiView)
            uiView.resignFirstResponder()
        }
    }
}

Any Concepts?

Leave a Reply

Your email address will not be published. Required fields are marked *