Files
omnivore/apple/OmnivoreKit/Sources/Views/SwiftUIDelayedGesture.swift

106 lines
3.4 KiB
Swift

//
// FROM: https://github.com/ciaranrobrien/SwiftUIDelayedGesture/tree/main
//
import SwiftUI
public extension View {
/// Sequences a gesture with a long press and attaches the result to the view,
/// which results in the gesture only receiving events after the long press
/// succeeds.
///
/// Use this view modifier *instead* of `.gesture` to delay a gesture:
///
/// ScrollView {
/// FooView()
/// .delayedGesture(someGesture, delay: 0.2)
/// }
///
/// - Parameters:
/// - gesture: A gesture to attach to the view.
/// - mask: A value that controls how adding this gesture to the view
/// affects other gestures recognized by the view and its subviews.
/// - delay: A value that controls the duration of the long press that
/// must elapse before the gesture can be recognized by the view.
/// - action: An action to perform if a tap gesture is recognized
/// before the long press can be recognized by the view.
func delayedGesture<T: Gesture>(_ gesture: T,
including mask: GestureMask = .all,
delay: TimeInterval = 0.25,
onTapGesture action: @escaping () -> Void = {}) -> some View
{
modifier(DelaysTouches(duration: delay, action: action))
.gesture(gesture, including: mask)
}
/// Attaches a long press gesture to the view, which results in gestures with a
/// lower precedence only receiving events after the long press succeeds.
///
/// Use this view modifier *before* `.gesture` to delay a gesture:
///
/// ScrollView {
/// FooView()
/// .delayedInput(delay: 0.2)
/// .gesture(someGesture)
/// }
///
/// - Parameters:
/// - delay: A value that controls the duration of the long press that
/// must elapse before lower precedence gestures can be recognized by
/// the view.
/// - action: An action to perform if a tap gesture is recognized
/// before the long press can be recognized by the view.
func delayedInput(delay: TimeInterval = 0.25,
onTapGesture action: @escaping () -> Void = {}) -> some View
{
modifier(DelaysTouches(duration: delay, action: action))
}
}
private struct DelaysTouches: ViewModifier {
@State private var disabled = false
@State private var touchDownDate: Date? = nil
var duration: TimeInterval
var action: () -> Void
func body(content: Content) -> some View {
Button(action: action) {
content
}
.buttonStyle(DelaysTouchesButtonStyle(disabled: $disabled, duration: duration, touchDownDate: $touchDownDate))
.disabled(disabled)
}
}
private struct DelaysTouchesButtonStyle: ButtonStyle {
@Binding var disabled: Bool
var duration: TimeInterval
@Binding var touchDownDate: Date?
func makeBody(configuration: Configuration) -> some View {
configuration.label
.onChange(of: configuration.isPressed, perform: handleIsPressed)
}
private func handleIsPressed(isPressed: Bool) {
if isPressed {
let date = Date()
touchDownDate = date
DispatchQueue.main.asyncAfter(deadline: .now() + max(duration, 0)) {
if date == touchDownDate {
disabled = true
DispatchQueue.main.async {
disabled = false
}
}
}
} else {
touchDownDate = nil
disabled = false
}
}
}