74 lines
2.5 KiB
Swift
74 lines
2.5 KiB
Swift
// 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<UITouch>, with event: UIEvent?) {
|
|
guard let touch = touches.first, touch.type == .pencil else { return }
|
|
onStrokeBegan?()
|
|
emitSamples(touch: touch, event: event)
|
|
}
|
|
|
|
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
guard let touch = touches.first, touch.type == .pencil else { return }
|
|
emitSamples(touch: touch, event: event)
|
|
}
|
|
|
|
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
guard let touch = touches.first, touch.type == .pencil else { return }
|
|
emitSamples(touch: touch, event: event)
|
|
onStrokeEnded?()
|
|
}
|
|
|
|
override func touchesCancelled(_ touches: Set<UITouch>, 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>(Float(loc.x), Float(loc.y)),
|
|
pressure: pressure,
|
|
altitude: Float(ct.altitudeAngle),
|
|
azimuth: Float(ct.azimuthAngle(in: self)),
|
|
timestamp: ct.timestamp
|
|
)
|
|
onSample?(sample)
|
|
}
|
|
}
|
|
}
|