Skip to content

Commit 80cf463

Browse files
authored
Merge pull request #78 from mkko/feature/improved-position-handling
Improve the pan gesture handling
2 parents c6d299c + 6f2f3d6 commit 80cf463

1 file changed

Lines changed: 69 additions & 12 deletions

File tree

DrawerView/DrawerView.swift

Lines changed: 69 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,14 @@ fileprivate extension DrawerPosition {
5454
]
5555
}
5656

57-
let kVelocityTreshold: CGFloat = 0
57+
// Minimum velocity (pts/sec) to advance position when prediction is ambiguous
58+
let kVelocityTreshold: CGFloat = 800
59+
60+
// Minimum drag distance (ratio of distance between positions) to allow position change
61+
let kMinimumDragDistanceRatio: CGFloat = 0.25
62+
63+
// Time horizon (seconds) for projecting target position based on current velocity
64+
let kVelocityProjectionTime: CGFloat = 0.08
5865

5966
// Vertical leeway is used to cover the bottom with springy animations.
6067
let kVerticalLeeway: CGFloat = 10.0
@@ -263,8 +270,8 @@ private struct ChildScrollViewInfo {
263270
panGestureRecognizer.minimumNumberOfTouches = 1
264271
return panGestureRecognizer
265272
}()
266-
267-
/// Damping ratio of the spring animation when opening or closing the drawer
273+
274+
/// Damping ratio of the spring animation when opening or closing the drawer
268275
public var animationSpringDampingRatio: CGFloat = 0.8
269276

270277
/// Boolean indicating if the activity drawer should dismiss when you scroll down
@@ -908,20 +915,45 @@ private struct ChildScrollViewInfo {
908915
// Let it scroll.
909916
log("Let child view scroll.")
910917
} else if drawerPanStarted {
918+
log("drawerPanStarted")
911919
self.delegate?.drawerWillEndDragging?(self)
912920

913-
// Check velocity and snap position separately:
914-
// 1) A treshold for velocity that makes drawer slide to the next state
915-
// 2) A prediction that estimates the next position based on target offset.
916-
// If 2 doesn't evaluate to the current position, use that.
917-
let targetOffset = self.frame.origin.y + velocity.y / 100
921+
// Determine next position based on:
922+
// 1) Minimum drag distance - require meaningful movement before changing position
923+
// 2) Velocity threshold - fast swipes can advance to next position
924+
// 3) Position prediction - estimate target based on velocity and current offset
925+
926+
guard let superview = superview else {
927+
return
928+
}
929+
930+
// Measure actual drag distance from gesture start
931+
let dragDistance = abs(self.frame.origin.y - self.panOrigin)
932+
933+
// Project target position based on current velocity
934+
let projectedDistance = velocity.y * kVelocityProjectionTime
935+
let targetOffset = self.frame.origin.y + projectedDistance
918936
let targetPosition = positionFor(offset: targetOffset)
919937

920-
// The positions are reversed, reverse the sign.
921-
let advancement = velocity.y > 0 ? -1 : 1
938+
// Check if drag meets minimum threshold for position change
939+
let hasMinimumDragDistance: Bool
940+
if targetPosition != self.position {
941+
let currentPositionSnapOffset = self.snapPosition(for: self.position, inSuperView: superview)
942+
let targetPositionSnapOffset = self.snapPosition(for: targetPosition, inSuperView: superview)
943+
let distanceBetweenPositions = abs(targetPositionSnapOffset - currentPositionSnapOffset)
944+
let minimumDragDistance = distanceBetweenPositions * kMinimumDragDistanceRatio
945+
hasMinimumDragDistance = dragDistance >= minimumDragDistance
946+
} else {
947+
hasMinimumDragDistance = true
948+
}
922949

950+
// Determine next position: insufficient drag returns to current, high velocity
951+
// advances to next, otherwise use velocity-projected target
952+
let advancement = velocity.y > 0 ? -1 : 1
923953
let nextPosition: DrawerPosition
924-
if targetPosition == self.position && abs(velocity.y) > kVelocityTreshold,
954+
if !hasMinimumDragDistance {
955+
nextPosition = self.position
956+
} else if targetPosition == self.position && abs(velocity.y) > kVelocityTreshold,
925957
let advanced = self.snapPositionsDescending.advance(from: targetPosition, offset: advancement) {
926958
nextPosition = advanced
927959
} else {
@@ -1368,9 +1400,34 @@ private struct ChildScrollViewInfo {
13681400
extension DrawerView: UIGestureRecognizerDelegate {
13691401

13701402
override public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
1371-
if gestureRecognizer === panGestureRecognizer || gestureRecognizer === overlayTapRecognizer {
1403+
if gestureRecognizer === panGestureRecognizer {
1404+
guard enabled else { return false }
1405+
1406+
// Check if the pan gesture is primarily horizontal (like swipe-to-delete)
1407+
// If so, don't begin to allow those gestures to work
1408+
let translation = panGestureRecognizer.translation(in: self)
1409+
let velocity = panGestureRecognizer.velocity(in: self)
1410+
1411+
// If there's meaningful translation/velocity, check direction
1412+
if abs(translation.x) > 0 || abs(translation.y) > 0 {
1413+
let isHorizontal = abs(translation.x) > abs(translation.y)
1414+
if isHorizontal {
1415+
return false
1416+
}
1417+
} else if abs(velocity.x) > 0 || abs(velocity.y) > 0 {
1418+
let isHorizontal = abs(velocity.x) > abs(velocity.y)
1419+
if isHorizontal {
1420+
return false
1421+
}
1422+
}
1423+
1424+
return true
1425+
}
1426+
1427+
if gestureRecognizer === overlayTapRecognizer {
13721428
return enabled
13731429
}
1430+
13741431
return true
13751432
}
13761433

0 commit comments

Comments
 (0)