'Force a custom gesture to end in SwiftUI
I've implemented a custom gesture via the Gesture protocol and it is mostly working. The one thing I have yet to figure out is how to declare that the gesture has completed.
Here is my current code.
import Foundation
import SwiftUI
import CoreGraphics
struct WiggleGesture: Gesture {
struct WiggleState {
var startTime: Date?
var bounces: Int = 0
var lastLocation = CGPoint.zero
var lastBounceLocation = CGPoint.zero
}
struct WiggleValue {
var bounces: Int
var elapsedTime: TimeInterval?
}
typealias Value = WiggleValue
@GestureState var wiggleState = WiggleState()
let distanceThreshold: CGFloat
let angleThreshold: CGFloat
init(distanceThreshold: CGFloat = 15, angleThreshold: CGFloat = 120) {
self.distanceThreshold = distanceThreshold
self.angleThreshold = angleThreshold
}
var body: AnyGesture<Value> {
AnyGesture (
DragGesture(minimumDistance: distanceThreshold, coordinateSpace: .global)
.updating($wiggleState) { (value, state, _) in
if state.startTime == nil {
state.startTime = Date.now
}
let currentSwipe = value.translation.asVector() - state.lastBounceLocation.asVector() // The current broad motion, from the last bounce location.
let currentTrajectory = value.translation.asVector() - state.lastLocation.asVector() // The current immediate motion, from the last tick.
//TODO: Consider if there should be a minimum translation before counting a bounce?
let trajectoryDelta = abs(currentSwipe.angleTo(other: currentTrajectory))
if abs(trajectoryDelta) > angleThreshold {
state.bounces += 1
state.lastBounceLocation = value.translation.asPoint()
}
state.lastLocation = value.translation.asPoint()
}
.map { _ in
var elapsedTime: TimeInterval?
if let startTime = wiggleState.startTime {
elapsedTime = Date.now.timeIntervalSince(startTime)
}
return WiggleValue(bounces: wiggleState.bounces, elapsedTime: elapsedTime)
}
)
}
}
extension View {
func onWiggle(distanceThreshold: CGFloat = 15, angleThreshold: CGFloat = 120, perform action: @escaping (WiggleGesture.Value) -> Void) -> some View {
gesture(
WiggleGesture(distanceThreshold: distanceThreshold, angleThreshold: angleThreshold)
.onEnded { value in
action(value)
}
)
}
}
Currently, .onEnded is still the one that comes from DragGesture and triggers when the user releases their touch.
Ideally, what I want is to specify a "bounces required" and "timeout" to the gesture so that when it has detected n number of bounces within the timeout it will trigger the .onEnded call and the gesture will stop evaluating.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
