99import UIKit
1010
1111
12- /// GestureRecognizer that recognizes a pan gesture without any delay
13- public final class PanGestureRecognizer : UIGestureRecognizer {
12+ /// Set of methods implemented by both UIPanGestureRecognizer and our own NoDelayPanGestureRecognizer
13+ @objc
14+ protocol PanGestureRecognizer : AnyObject {
15+
16+ // We need the @objc annotations because of `typealias PanOrScrollGestureRecognizer = UIGestureRecognizer & PanGestureRecognizer` in PanelGestures
17+ // which makes the methods in our `NoDelayPanGestureRecognizer` to be called via objc bridging. If we don't have these annotations, we get crashes in runtime.
18+ @objc ( translationInView: )
19+ func translation( in view: UIView ? ) -> CGPoint
20+ @objc ( setTranslation: inView: )
21+ func setTranslation( _ translation: CGPoint , in view: UIView ? )
22+ @objc ( velocityInView: )
23+ func velocity( in view: UIView ? ) -> CGPoint
24+ }
25+
26+ extension UIPanGestureRecognizer : PanGestureRecognizer { }
27+
28+
29+ // MARK: - PointerScrollGestureRecognizer
30+
31+ /// A UIPanGestureRecognizer subclass that recognizes only pointer scroll gestures
32+ /// We won't need this after UIGestureRecognizer subclasses will be able to detect pointer scrolls (FB7733482)
33+ public final class PointerScrollGestureRecognizer : UIPanGestureRecognizer {
34+
35+ // MARK: - Lifecycle
1436
15- enum StartMode : Equatable {
16- case onFixedArea
17- case onVerticallyScrollableArea ( competingScrollView : UIScrollView )
37+ /// As a non-failable initializer cannot be overwritten by a failable initializer, we mark this initializer as 'private' and use make() factory method from outside
38+ private override init ( target : Any ? , action : Selector ? ) {
39+ super . init ( target : target , action : action )
1840 }
41+ }
42+
43+ public extension PointerScrollGestureRecognizer {
44+
45+ static func make( withTarget target: Any ? , action: Selector ? ) -> PointerScrollGestureRecognizer ? {
46+ guard #available( iOS 13 . 4 , * ) , NSClassFromString ( " UIPointerInteraction " ) != nil else { return nil }
47+
48+ let gesture = PointerScrollGestureRecognizer ( target: target, action: action)
49+ gesture. allowedScrollTypesMask = . continuous
50+ return gesture
51+ }
52+ }
53+
54+ // MARK: - UIGestureRecognizer+Subclass
55+
56+ public extension PointerScrollGestureRecognizer {
57+
58+ @available ( iOS 13 . 4 , * )
59+ override func shouldReceive( _ event: UIEvent ) -> Bool {
60+ guard event. type == . scroll else { return false }
61+
62+ return super. shouldReceive ( event)
63+ }
64+ }
65+
66+
67+ // MARK: - NoDelayPanGestureRecognizer
68+
69+ /// A UIGestureRecognizer subclass that recognizes pan gestures without any delay but doesn't recognize scrolling with pointer
70+ public final class NoDelayPanGestureRecognizer : UIGestureRecognizer {
1971
2072 private lazy var panForVelocity : UIPanGestureRecognizer = self . makeVelocityPan ( )
21- private var initialPoint : CGPoint ?
2273 private var lastPoint : CGPoint ?
2374 private var currentPoint : CGPoint ?
24-
25- // MARK: Properties
26-
27- private( set) var didPan : Bool = false
28- var startMode : StartMode = . onFixedArea
2975}
3076
3177// MARK: - UIGestureRecognizer+Subclass
3278
33- public extension PanGestureRecognizer {
79+ public extension NoDelayPanGestureRecognizer {
3480
3581 override func touchesBegan( _ touches: Set < UITouch > , with event: UIEvent ) {
3682 super. touchesBegan ( touches, with: event)
@@ -50,11 +96,9 @@ public extension PanGestureRecognizer {
5096 guard let touch = touches. first else { return }
5197
5298 let location = touch. location ( in: self . view? . window)
53- self . initialPoint = location
5499 self . lastPoint = location
55100 self . currentPoint = location
56101 self . state = . began
57- self . didPan = false
58102 }
59103
60104 override func touchesMoved( _ touches: Set < UITouch > , with event: UIEvent ) {
@@ -65,17 +109,6 @@ public extension PanGestureRecognizer {
65109
66110 let currentPoint = touch. location ( in: self . view? . window)
67111 self . currentPoint = currentPoint
68-
69- if self . totalTranslation. hypotenuse ( ) > Constants . minTranslation && self . didPan == false {
70- self . didPan = true
71-
72- // if we recognized a pan, make sure it can be considered a vertical pan
73- guard self . totalTranslation. direction ( ) == . vertical else {
74- self . state = . cancelled
75- return
76- }
77- }
78-
79112 self . state = . changed
80113 }
81114
@@ -96,41 +129,36 @@ public extension PanGestureRecognizer {
96129 override func reset( ) {
97130 super. reset ( )
98131
99- self . didPan = false
100- self . startMode = . onFixedArea
101132 self . panForVelocity = self . makeVelocityPan ( )
102133 }
103134}
104135
105136// MARK: - PanGestureRecognizer
106137
107- extension PanGestureRecognizer {
138+ extension NoDelayPanGestureRecognizer : PanGestureRecognizer {
108139
109- func translation( in view: UIView ) -> CGPoint {
140+ func translation( in view: UIView ? ) -> CGPoint {
110141 guard let lastPoint = self . lastPoint else { return . zero }
111142 guard let currentPoint = self . currentPoint else { return . zero }
143+ guard let view = view ?? self . view? . window else { return . zero }
112144
113145 return self . translation ( from: lastPoint, to: currentPoint, in: view)
114146 }
115147
116- func setTranslation( _ translation: CGPoint , in view: UIView ) {
148+ func setTranslation( _ translation: CGPoint , in view: UIView ? ) {
117149 guard let currentPoint = self . currentPoint else { return }
118150
119151 self . lastPoint = currentPoint. applying ( . init( translationX: - translation. x, y: - translation. y) )
120152 }
121153
122- func velocity( in view: UIView ) -> CGPoint {
154+ func velocity( in view: UIView ? ) -> CGPoint {
123155 return self . panForVelocity. velocity ( in: view)
124156 }
125157}
126158
127159// MARK: - Private
128160
129- private extension PanGestureRecognizer {
130-
131- struct Constants {
132- static let minTranslation : CGFloat = 5.0
133- }
161+ private extension NoDelayPanGestureRecognizer {
134162
135163 func makeVelocityPan( ) -> UIPanGestureRecognizer {
136164 let pan = UIPanGestureRecognizer ( target: nil , action: nil )
@@ -140,39 +168,12 @@ private extension PanGestureRecognizer {
140168 return pan
141169 }
142170
143- var totalTranslation : CGPoint {
144- guard let view = self . view else { return . zero }
145- guard let initialPoint = self . initialPoint else { return . zero }
146- guard let currentPoint = self . currentPoint else { return . zero }
147-
148- return self . translation ( from: initialPoint, to: currentPoint, in: view)
149- }
150-
151171 func translation( from startPoint: CGPoint , to endPoint: CGPoint , in view: UIView ) -> CGPoint {
152- guard let window = self . view? . window else { return . zero }
172+ guard let window = view. window else { return . zero }
153173
154174 let startPointInView = window. convert ( startPoint, to: view)
155175 let endPointInView = window. convert ( endPoint, to: view)
156176
157177 return CGPoint ( x: endPointInView. x - startPointInView. x, y: endPointInView. y - startPointInView. y)
158178 }
159179}
160-
161- private extension CGPoint {
162-
163- enum Direction {
164- case horizontal
165- case vertical
166- }
167-
168- func hypotenuse( ) -> CGFloat {
169- return sqrt ( self . x * self . x + self . y * self . y)
170- }
171-
172- func direction( ) -> Direction {
173- let horizontalDiff = self . x * self . x
174- let verticalDiff = self . y * self . y
175-
176- return horizontalDiff > verticalDiff ? . horizontal : . vertical
177- }
178- }
0 commit comments