11.7 C
New York
Tuesday, November 5, 2024

ios – SwiftUI + PencilKit: Learn how 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 purpose is to permit customers to scroll by means of the hours with their finger whereas additionally having the ability to write notes on prime of this view utilizing the Apple Pencil.

To realize this, I’ve added a PKCanvasView as an overlay on prime of the ScrollView. Nevertheless, 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 Conduct:

  • Finger enter ought to scroll by means of the day overview.
  • Pencil enter ought to 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 prime 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, however it utterly 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 not write with the Apple Pencil on the canvas overlay.

  3. Customized Gesture Recognizers:
    Tried utilizing UIGestureRecognizerDelegate to selectively allow contact dealing with solely Outcome: 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 personal var canvasViewModel = CanvasViewModel()
    @State personal var currentHour = Calendar.present.element(.hour, from: Date())

    var physique: some View {
        ZStack {
            // Major 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(top: 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: .prime) // 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 {
    @Revealed var canvasView = PKCanvasView()
    @Revealed var toolPicker = PKToolPicker()
    @Revealed 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, shade: .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?

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Latest Articles