Files
yattee/Yattee/Views/Components/SwipeGesture.swift
2026-02-08 18:33:56 +01:00

78 lines
2.4 KiB
Swift

//
// SwipeGesture.swift
// Yattee
//
// Custom pan gesture that filters horizontal swipes to avoid conflicts with ScrollView.
//
import SwiftUI
#if os(iOS)
/// Value passed to gesture callbacks containing translation and velocity.
struct SwipeGestureValue {
var translation: CGSize = .zero
var velocity: CGSize = .zero
}
/// Custom pan gesture recognizer that only begins when horizontal velocity exceeds vertical.
/// This prevents conflicts with ScrollView vertical scrolling.
@available(iOS 18, *)
struct SwipeGesture: UIGestureRecognizerRepresentable {
var onBegan: () -> Void
var onChange: (SwipeGestureValue) -> Void
var onEnded: (SwipeGestureValue) -> Void
func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator {
Coordinator()
}
func makeUIGestureRecognizer(context: Context) -> UIPanGestureRecognizer {
let gesture = UIPanGestureRecognizer()
gesture.delegate = context.coordinator
return gesture
}
func updateUIGestureRecognizer(_ recognizer: UIPanGestureRecognizer, context: Context) {}
func handleUIGestureRecognizerAction(_ recognizer: UIPanGestureRecognizer, context: Context) {
let state = recognizer.state
let translation = recognizer.translation(in: recognizer.view).toSize
let velocity = recognizer.velocity(in: recognizer.view).toSize
let gestureValue = SwipeGestureValue(translation: translation, velocity: velocity)
switch state {
case .began:
onBegan()
case .changed:
onChange(gestureValue)
case .ended, .cancelled:
onEnded(gestureValue)
default:
break
}
}
class Coordinator: NSObject, UIGestureRecognizerDelegate {
/// Only begin the gesture when horizontal velocity exceeds vertical velocity.
/// This allows ScrollView to handle vertical scrolling while we handle horizontal swipes.
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
guard let panGesture = gestureRecognizer as? UIPanGestureRecognizer else {
return false
}
let velocity = panGesture.velocity(in: panGesture.view)
return abs(velocity.x) > abs(velocity.y)
}
}
}
private extension CGPoint {
var toSize: CGSize {
CGSize(width: x, height: y)
}
}
#endif