mirror of
https://github.com/yattee/yattee.git
synced 2026-02-21 02:09:46 +00:00
Yattee v2 rewrite
This commit is contained in:
117
Yattee/Views/Player/Gestures/OverscrollGestureHandler.swift
Normal file
117
Yattee/Views/Player/Gestures/OverscrollGestureHandler.swift
Normal file
@@ -0,0 +1,117 @@
|
||||
//
|
||||
// OverscrollGestureHandler.swift
|
||||
// Yattee
|
||||
//
|
||||
// UIKit gesture handler for detecting overscroll pull-down gestures on UIScrollView.
|
||||
// When user pulls down at scroll top, disables bounce and forwards drag events for smooth
|
||||
// panel collapse animation.
|
||||
//
|
||||
|
||||
#if os(iOS)
|
||||
import UIKit
|
||||
|
||||
/// Coordinates overscroll detection on a UIScrollView, calling back during pull-down gestures
|
||||
/// when the scroll is at top. Disables bounce during the gesture to allow smooth animation.
|
||||
final class OverscrollGestureHandler: NSObject, UIGestureRecognizerDelegate {
|
||||
// MARK: - Properties
|
||||
|
||||
weak var scrollView: UIScrollView?
|
||||
var onDragChanged: ((CGFloat) -> Void)?
|
||||
var onDragEnded: ((CGFloat, CGFloat) -> Void)?
|
||||
|
||||
/// Whether we're currently tracking an overscroll gesture
|
||||
private var isTracking = false
|
||||
|
||||
/// The pan gesture recognizer we add to the scroll view
|
||||
private var panRecognizer: UIPanGestureRecognizer?
|
||||
|
||||
// MARK: - Setup
|
||||
|
||||
/// Attaches the pan gesture recognizer to the scroll view.
|
||||
func attach(to scrollView: UIScrollView) {
|
||||
detach()
|
||||
|
||||
self.scrollView = scrollView
|
||||
|
||||
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
|
||||
pan.delegate = self
|
||||
scrollView.addGestureRecognizer(pan)
|
||||
panRecognizer = pan
|
||||
}
|
||||
|
||||
/// Removes the pan gesture recognizer from the scroll view.
|
||||
func detach() {
|
||||
if let recognizer = panRecognizer, let view = recognizer.view {
|
||||
view.removeGestureRecognizer(recognizer)
|
||||
}
|
||||
panRecognizer = nil
|
||||
scrollView = nil
|
||||
isTracking = false
|
||||
}
|
||||
|
||||
// MARK: - Gesture Handling
|
||||
|
||||
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
|
||||
guard let scrollView else { return }
|
||||
|
||||
switch gesture.state {
|
||||
case .began:
|
||||
// Start tracking - disable bounce so we can control the movement
|
||||
isTracking = true
|
||||
scrollView.bounces = false
|
||||
|
||||
case .changed:
|
||||
let translation = gesture.translation(in: gesture.view)
|
||||
// Only forward positive (pull down) translations
|
||||
if translation.y > 0 {
|
||||
onDragChanged?(translation.y)
|
||||
}
|
||||
|
||||
case .ended, .cancelled:
|
||||
// Re-enable bounce
|
||||
scrollView.bounces = true
|
||||
isTracking = false
|
||||
|
||||
let translation = gesture.translation(in: gesture.view)
|
||||
let velocity = gesture.velocity(in: gesture.view)
|
||||
// Calculate predicted end position
|
||||
let decelerationTime: CGFloat = 0.3
|
||||
let predicted = translation.y + velocity.y * decelerationTime
|
||||
|
||||
onDragEnded?(translation.y, predicted)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - UIGestureRecognizerDelegate
|
||||
|
||||
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
guard let pan = gestureRecognizer as? UIPanGestureRecognizer,
|
||||
let scrollView else {
|
||||
return false
|
||||
}
|
||||
|
||||
let velocity = pan.velocity(in: pan.view)
|
||||
|
||||
// Only begin if:
|
||||
// 1. Scroll view is at top (contentOffset.y <= 0)
|
||||
// 2. User is pulling down (velocity.y > 0)
|
||||
// 3. Vertical movement is dominant (to not interfere with horizontal scrolling)
|
||||
let isAtTop = scrollView.contentOffset.y <= 0
|
||||
let isPullingDown = velocity.y > 0
|
||||
let isVerticalDominant = abs(velocity.y) > abs(velocity.x)
|
||||
|
||||
return isAtTop && isPullingDown && isVerticalDominant
|
||||
}
|
||||
|
||||
func gestureRecognizer(
|
||||
_ gestureRecognizer: UIGestureRecognizer,
|
||||
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
|
||||
) -> Bool {
|
||||
// Don't allow simultaneous recognition - we take over when overscrolling
|
||||
false
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user