// PencilCaptureView.swift // UIViewRepresentable overlay that captures Apple Pencil touches // with full pressure/tilt/azimuth fidelity and forwards them // as PencilSample values to a callback. import SwiftUI import UIKit struct PencilCaptureOverlay: UIViewRepresentable { let onStrokeBegan: () -> Void let onSample: (PencilSample) -> Void let onStrokeEnded: () -> Void func makeUIView(context: Context) -> PencilTouchView { let view = PencilTouchView() view.onStrokeBegan = onStrokeBegan view.onSample = onSample view.onStrokeEnded = onStrokeEnded view.backgroundColor = .clear view.isMultipleTouchEnabled = false return view } func updateUIView(_ uiView: PencilTouchView, context: Context) {} } /// Raw UIView that intercepts pencil touches. final class PencilTouchView: UIView { var onStrokeBegan: (() -> Void)? var onSample: ((PencilSample) -> Void)? var onStrokeEnded: (() -> Void)? override func touchesBegan(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first, touch.type == .pencil else { return } onStrokeBegan?() emitSamples(touch: touch, event: event) } override func touchesMoved(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first, touch.type == .pencil else { return } emitSamples(touch: touch, event: event) } override func touchesEnded(_ touches: Set, with event: UIEvent?) { guard let touch = touches.first, touch.type == .pencil else { return } emitSamples(touch: touch, event: event) onStrokeEnded?() } override func touchesCancelled(_ touches: Set, with event: UIEvent?) { onStrokeEnded?() } private func emitSamples(touch: UITouch, event: UIEvent?) { // Coalesced touches give us sub-frame samples for smooth strokes let coalescedTouches = event?.coalescedTouches(for: touch) ?? [touch] for ct in coalescedTouches { let loc = ct.preciseLocation(in: self) let pressure = ct.maximumPossibleForce > 0 ? Float(ct.force / ct.maximumPossibleForce) : 0.5 let sample = PencilSample( screenPosition: SIMD2(Float(loc.x), Float(loc.y)), pressure: pressure, altitude: Float(ct.altitudeAngle), azimuth: Float(ct.azimuthAngle(in: self)), timestamp: ct.timestamp ) onSample?(sample) } } }