-
Notifications
You must be signed in to change notification settings - Fork 465
Expand file tree
/
Copy pathPagerScrollDelegate.swift
More file actions
132 lines (108 loc) · 5.74 KB
/
PagerScrollDelegate.swift
File metadata and controls
132 lines (108 loc) · 5.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import UIKit
/**
Scroll delegate used to control underlying TabView's collection view.
*/
class PagerScrollDelegate: NSObject, UIScrollViewDelegate, UICollectionViewDelegate, UIGestureRecognizerDelegate {
weak var originalDelegate: UICollectionViewDelegate?
weak var delegate: PagerViewProviderDelegate?
var orientation: UICollectionView.ScrollDirection = .horizontal
weak var collectionView: UICollectionView?
var currentPage: Int = 0
var layoutDirection: PagerLayoutDirection = .ltr
var scrollEnabled: Bool = true
private let handledSelectors: Set<Selector> = [
#selector(scrollViewDidScroll(_:)),
#selector(scrollViewWillBeginDragging(_:)),
#selector(scrollViewWillBeginDecelerating(_:)),
#selector(scrollViewDidEndDecelerating(_:)),
#selector(scrollViewDidEndScrollingAnimation(_:)),
#selector(scrollViewDidEndDragging(_:willDecelerate:)),
#selector(collectionView(_:didEndDisplaying:forItemAt:)),
#selector(collectionView(_:willDisplay:forItemAt:))
]
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let isHorizontal = orientation == .horizontal
let pageSize = isHorizontal ? scrollView.frame.width : scrollView.frame.height
let contentOffset = isHorizontal ? scrollView.contentOffset.x : scrollView.contentOffset.y
guard pageSize > 0 else { return }
let offset = contentOffset.truncatingRemainder(dividingBy: pageSize) / pageSize
let position = round(contentOffset / pageSize - offset)
let eventData = OnPageScrollEventData(position: position, offset: offset)
delegate?.onPageScroll(data: eventData)
originalDelegate?.scrollViewDidScroll?(scrollView)
}
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
delegate?.onPageScrollStateChanged(state: .dragging)
originalDelegate?.scrollViewWillBeginDragging?(scrollView)
}
func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
delegate?.onPageScrollStateChanged(state: .settling)
originalDelegate?.scrollViewWillBeginDecelerating?(scrollView)
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
delegate?.onPageScrollStateChanged(state: .idle)
// Re-enable pan gesture recognizer after decelerating ends
if let collectionView = collectionView, !collectionView.panGestureRecognizer.isEnabled {
collectionView.panGestureRecognizer.isEnabled = scrollEnabled
}
originalDelegate?.scrollViewDidEndDecelerating?(scrollView)
}
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
delegate?.onPageScrollStateChanged(state: .idle)
originalDelegate?.scrollViewDidEndScrollingAnimation?(scrollView)
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
delegate?.onPageScrollStateChanged(state: .idle)
}
// Re-enable pan gesture recognizer after dragging ends
if let collectionView = collectionView, !collectionView.panGestureRecognizer.isEnabled {
collectionView.panGestureRecognizer.isEnabled = scrollEnabled
}
originalDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate)
}
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
originalDelegate?.collectionView?(collectionView, didEndDisplaying: cell, forItemAt: indexPath)
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
originalDelegate?.collectionView?(collectionView, willDisplay: cell, forItemAt: indexPath)
}
override func responds(to aSelector: Selector!) -> Bool {
handledSelectors.contains(aSelector) || (originalDelegate?.responds(to: aSelector) ?? false)
}
override func forwardingTarget(for aSelector: Selector!) -> Any? {
handledSelectors.contains(aSelector) ? nil : originalDelegate
}
// MARK: - UIGestureRecognizerDelegate
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
// iOS 26+ full-screen back gesture (interactiveContentPopGestureRecognizer)
if #available(iOS 18.2, *) {
guard let collectionView = collectionView,
let viewController = collectionView.next as? UIViewController,
let navigationController = viewController.navigationController else {
return false
}
// Check if otherGestureRecognizer is the new iOS 26 interactiveContentPopGestureRecognizer
// NOTE: Using KVC to access private API 'interactiveContentPopGestureRecognizer' which is not
// yet publicly documented. The responds(to:) check ensures this won't crash if the API changes.
if gestureRecognizer == collectionView.panGestureRecognizer,
otherGestureRecognizer == navigationController.interactivePopGestureRecognizer ||
(navigationController.responds(to: Selector(("interactiveContentPopGestureRecognizer"))) &&
otherGestureRecognizer == navigationController.value(forKey: "interactiveContentPopGestureRecognizer") as? UIGestureRecognizer) {
guard let panGestureRecognizer = gestureRecognizer as? UIPanGestureRecognizer else {
return false
}
let velocity = panGestureRecognizer.velocity(in: collectionView)
let isLTR = layoutDirection == .ltr
let isBackGesture = (isLTR && velocity.x > 0) || (!isLTR && velocity.x < 0)
if currentPage == 0 && isBackGesture {
collectionView.panGestureRecognizer.isEnabled = false
} else {
collectionView.panGestureRecognizer.isEnabled = scrollEnabled
}
return true
}
}
return false
}
}