-
Notifications
You must be signed in to change notification settings - Fork 153
Expand file tree
/
Copy pathSwipeView.swift
More file actions
192 lines (165 loc) · 8.51 KB
/
SwipeView.swift
File metadata and controls
192 lines (165 loc) · 8.51 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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
///
/// MIT License
///
/// Copyright (c) 2020 Mac Gallagher
///
/// Permission is hereby granted, free of charge, to any person obtaining a copy
/// of this software and associated documentation files (the "Software"), to deal
/// in the Software without restriction, including without limitation the rights
/// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
/// copies of the Software, and to permit persons to whom the Software is
/// furnished to do so, subject to the following conditions:
///
/// The above copyright notice and this permission notice shall be included in all
/// copies or substantial portions of the Software.
///
/// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
/// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
/// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
/// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
/// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
/// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
/// SOFTWARE.
///
import UIKit
/// A wrapper over `UIView` which detects customized swipe gestures. The swipe recognition is
/// based on both speed and translation, and can and be set for each direction.
///
/// This view is intended to be subclassed.
open class SwipeView: UIView {
/// The swipe directions to be detected by the view. Set this variable to ignore certain directions.
/// Defaults to `SwipeDirection.allDirections`.
open var swipeDirections = SwipeDirection.allDirections
/// The axis to be animate card in all axis. Set this variable to ignore certain axis.
/// Defaults to `Axis.all`.
open var axis = Axis.all
/// The pan gesture recognizer attached to the view.
public var panGestureRecognizer: UIPanGestureRecognizer {
return internalPanGestureRecognizer
}
private lazy var internalPanGestureRecognizer = PanGestureRecognizer(target: self,
action: #selector(handlePan))
/// The tap gesture recognizer attached to the view.
public var tapGestureRecognizer: UITapGestureRecognizer {
return internalTapGestureRecognizer
}
private lazy var internalTapGestureRecognizer = TapGestureRecognizer(target: self,
action: #selector(didTap))
// MARK: - Initialization
/// Initializes the `SwipeView` with the required gesture recognizers.
/// - Parameter frame: The frame rectangle for the view, measured in points.
override public init(frame: CGRect) {
super.init(frame: frame)
initialize()
}
/// Initializes the `SwipeView` with the required gesture recognizers.
/// - Parameter aDecoder: An unarchiver object.
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initialize()
}
private func initialize() {
addGestureRecognizer(internalPanGestureRecognizer)
addGestureRecognizer(internalTapGestureRecognizer)
}
// MARK: - Swipe Calculations
/// The active swipe direction on the view, if any.
/// - Returns: The member of `swipeDirections` which returns the highest `dragPercentage:`.
public func activeDirection() -> SwipeDirection? {
return swipeDirections.reduce((CGFloat.zero, nil)) { [unowned self] lastResult, direction in
let dragPercentage = self.dragPercentage(on: direction)
return dragPercentage > lastResult.0 ? (dragPercentage, direction) : lastResult
}.1
}
/// The speed of the current drag velocity projected onto the specified direction.
///
/// Expressed in points per second.
/// - Parameter direction: The direction to project the drag onto.
/// - Returns: The speed of the current drag velocity on the specifed direction.
public func dragSpeed(on direction: SwipeDirection) -> CGFloat {
let velocity = panGestureRecognizer.velocity(in: superview)
return abs(direction.vector * CGVector(to: velocity))
}
/// The percentage of `minimumSwipeDistance` the current drag translation attains in the specified direction.
///
/// The provided swipe direction need not be a member of `swipeDirections`.
/// Can return a value greater than 1.
/// - Parameter direction: The swipe direction.
/// - Returns: The percentage of `minimumSwipeDistance` the current drag translation attains in
/// the specified direction.
public func dragPercentage(on direction: SwipeDirection) -> CGFloat {
let translation = CGVector(to: panGestureRecognizer.translation(in: superview))
let scaleFactor = 1 / minimumSwipeDistance(on: direction)
let percentage = scaleFactor * (translation * direction.vector)
return percentage < 0 ? 0 : percentage
}
/// The minimum required speed on the intended direction to trigger a swipe. Subclasses can override
/// this method for custom swipe behavior.
/// - Parameter direction: The swipe direction.
/// - Returns: The minimum speed required to trigger a swipe in the indicated direction, measured in
/// points per second. Defaults to 1100 for each direction.
open func minimumSwipeSpeed(on direction: SwipeDirection) -> CGFloat {
return 1100
}
/// The minimum required drag distance on the intended direction to trigger a swipe, measured from the
/// swipe's initial touch point. Subclasses can override this method for custom swipe behavior.
/// - Parameter direction: The swipe direction.
/// - Returns: The minimum distance required to trigger a swipe in the indicated direction, measured in
/// points. Defaults to 1/4 of the minimum of the screen's width and height.
open func minimumSwipeDistance(on direction: SwipeDirection) -> CGFloat {
return min(UIScreen.main.bounds.width, UIScreen.main.bounds.height) / 4
}
// MARK: - Gesture Recognition
/// This function is called whenever the view is tapped. The default implementation does nothing;
/// subclasses can override this method to perform whatever actions are necessary.
/// - Parameter recognizer: The gesture recognizer associated with the tap.
@objc
open func didTap(_ recognizer: UITapGestureRecognizer) {}
/// This function is called whenever the view recognizes the beginning of a swipe. The default
/// implementation does nothing; subclasses can override this method to perform whatever actions
/// are necessary.
/// - Parameter recognizer: The gesture recognizer associated with the swipe.
open func beginSwiping(_ recognizer: UIPanGestureRecognizer) {}
/// This function is called whenever the view recognizes a change in the active swipe. The default
/// implementation does nothing; subclasses can override this method to perform whatever actions are
/// necessary.
/// - Parameter recognizer: The gesture recognizer associated with the swipe.
open func continueSwiping(_ recognizer: UIPanGestureRecognizer) {}
/// This function is called whenever the view recognizes the end of a swipe, regardless if the swipe
/// was successful or cancelled.
/// - Parameter recognizer: The gesture recognizer associated with the swipe.
open func endSwiping(_ recognizer: UIPanGestureRecognizer) {
if let direction = activeDirection() {
if dragSpeed(on: direction) >= minimumSwipeSpeed(on: direction)
|| dragPercentage(on: direction) >= 1 {
didSwipe(recognizer, with: direction)
return
}
}
didCancelSwipe(recognizer)
}
/// This function is called whenever the view recognizes a swipe. The default implementation does
/// nothing; subclasses can override this method to perform whatever actions are necessary.
/// - Parameters:
/// - recognizer: The gesture recognizer associated with the recognized swipe.
/// - direction: The direction of the swipe.
open func didSwipe(_ recognizer: UIPanGestureRecognizer, with direction: SwipeDirection) {}
/// This function is called whenever the view recognizes a cancelled swipe. The default implementation
/// does nothing; subclasses can override this method to perform whatever actions are necessary.
/// - Parameter recognizer: The gesture recognizer associated with the cancelled swipe.
open func didCancelSwipe(_ recognizer: UIPanGestureRecognizer) {}
// MARK: - Selectors
@objc
private func handlePan(_ recognizer: UIPanGestureRecognizer) {
switch recognizer.state {
case .possible, .began:
beginSwiping(recognizer)
case .changed:
continueSwiping(recognizer)
case .ended, .cancelled:
endSwiping(recognizer)
default:
break
}
}
}