Here is some of my code
//
// StrokeGestureRecognizer.swift
// Animate3D
//
// Created by Georg Graf on 07.07.23.
//
import UIKit
class StrokeGestureRecognizer: UIGestureRecognizer {
// MARK: - Configuration
var collectsCoalescedTouches = true
var usesPredictedSamples = true
var coordinateSpaceView: UIView?
var ensuredReferenceView: UIView {
if let view = coordinateSpaceView {
return view
} else {
return view!
}
}
/// A Boolean value that determines whether the gesture recognizer tracks Apple Pencil or finger touches.
/// - Tag: isForPencil
var isForPencil: Bool = true {
didSet {
if isForPencil {
allowedTouchTypes = [UITouch.TouchType.pencil.rawValue as NSNumber]
} else {
allowedTouchTypes = [UITouch.TouchType.direct.rawValue as NSNumber]
}
}
}
// MARK: - State
var trackedTouch: UITouch?
var collectForce = true
var stroke = Stroke()
// MARK: - Line data collection
/// Appends touch data to the stroke sample.
/// - Tag: appendTouches
func append(touches: Set<UITouch>, event: UIEvent?) -> Bool {
// Check that we have a touch to append, and that touches
// doesn't contain it.
guard let touchToAppend = trackedTouch, touches.contains(touchToAppend) == true
else { return false }
if collectsCoalescedTouches {
if let event = event {
let coalescedTouches = event.coalescedTouches(for: touchToAppend)!
let lastIndex = coalescedTouches.count - 1
for index in 0..<lastIndex {
let location = coalescedTouches[index].preciseLocation(in: ensuredReferenceView)
stroke.saveStrokeSample(coordinates: location, force: getForceRatio(touch: coalescedTouches[index]))
}
let location = coalescedTouches[lastIndex].preciseLocation(in: ensuredReferenceView)
stroke.saveStrokeSample(coordinates: location, force: getForceRatio(touch: coalescedTouches[lastIndex]))
}
} else {
let location = touchToAppend.preciseLocation(in: ensuredReferenceView)
stroke.saveStrokeSample(coordinates: location, force: getForceRatio(touch: touchToAppend))
}
if usesPredictedSamples && stroke.state == .active {
if let predictedTouches = event?.predictedTouches(for: touchToAppend) {
for touch in predictedTouches {
// do nothing for now
}
}
}
return true
}
private func getForceRatio(touch: UITouch) -> CGFloat {
print("touch.force: \(touch.force), maxPossible: \(touch.maximumPossibleForce)")
return touch.force / touch.maximumPossibleForce
}
// MARK: - Touch handling methods
/// A set of functions that track touches.
/// - Tag: HandleTouches
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
stroke = Stroke()
if trackedTouch == nil {
trackedTouch = touches.first
collectForce = trackedTouch!.type == .pencil
// || view?.traitCollection.forceTouchCapability == .available
print("collectForce: \(collectForce)")
}
if append(touches: touches, event: event) {
if isForPencil {
state = .began
}
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
if append(touches: touches, event: event) {
if state == .began {
state = .changed
}
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touchesEnded")
if append(touches: touches, event: event) {
stroke.state = .done
state = .ended
}
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
print("touchesCancelled")
if append(touches: touches, event: event) {
stroke.state = .cancelled
state = .failed
}
}
override func reset() {
print("reset")
AppState.shared.currentLine = nil
trackedTouch = nil
super.reset()
}
}