← All repositories

listless

A simple list app for Apple platforms

git clone https://code.inqk.net/listless.git


commit 03fc0ea2ba94d05daca8b79e54100f70b8602fb1
parent 22a8bebf4768a804fd312c5459db4607435997d3
Author: Michael Camilleri <[email protected]>
Date:   Fri, 20 Mar 2026 17:48:04 +0900

Make scroll/swipe heuristic more robust

This is an attempt to make the decision about whether to recognise a
horizontal swipe gesture when a scroll gesture has begun more robust.
Previously, the heuristic merely looked at whether the scroll phase was
`.idle` or not. This checks the direction of the gesture.

Co-Authored-By: Claude 4.6 Opus <[email protected]>

Diffstat

MListlessiOS/Helpers/TaskRowSwipeGesture.swift | 27++++++++++++++++++++++++++-

1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/ListlessiOS/Helpers/TaskRowSwipeGesture.swift b/ListlessiOS/Helpers/TaskRowSwipeGesture.swift

@@ -36,6 +36,7 @@ struct TaskRowSwipeGesture: ViewModifier {
     let onDelete: () -> Void
 
     @State private var hapticTrigger = false
+    @State private var activeGestureAxis: ActiveGestureAxis = .undecided
 
     enum SwipeDirection: Equatable {
         case left
@@ -43,6 +44,12 @@ struct TaskRowSwipeGesture: ViewModifier {
         case none
     }
 
+    private enum ActiveGestureAxis {
+        case undecided
+        case horizontal
+        case vertical
+    }
+
     private let completeThreshold: CGFloat = 40
     private let deleteThreshold: CGFloat = 80
     private let horizontalBufferPt: CGFloat = 10
@@ -63,7 +70,12 @@ struct TaskRowSwipeGesture: ViewModifier {
         .applySwipeGesture(
             isDragging: isDragging,
             onChanged: { translation in
-                guard !isDragging, !isScrolling else { return }
+                guard !isDragging else { return }
+                updateActiveGestureAxis(
+                    horizontalTranslation: translation.width,
+                    verticalTranslation: abs(translation.height)
+                )
+                guard activeGestureAxis == .horizontal else { return }
                 handleDragChanged(
                     horizontalTranslation: translation.width,
                     verticalTranslation: abs(translation.height)
@@ -123,6 +135,8 @@ struct TaskRowSwipeGesture: ViewModifier {
     }
 
     private func handleDragEnded() {
+        defer { activeGestureAxis = .undecided }
+
         guard !isDragging else {
             // A drag-reorder started during or after this swipe — spring back, no action.
             withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
@@ -162,12 +176,23 @@ struct TaskRowSwipeGesture: ViewModifier {
         swipeOffset = 0
         swipeDirection = .none
         isTriggered = false
+        activeGestureAxis = .undecided
     }
 
     private func backgroundOpacity(offset: CGFloat) -> CGFloat {
         let threshold = offset >= 0 ? completeThreshold : deleteThreshold
         return min(abs(offset) / threshold, 1.0)
     }
+
+    private func updateActiveGestureAxis(horizontalTranslation: CGFloat, verticalTranslation: CGFloat) {
+        guard activeGestureAxis == .undecided else { return }
+
+        if abs(horizontalTranslation) > verticalTranslation + horizontalBufferPt {
+            activeGestureAxis = .horizontal
+        } else if verticalTranslation > abs(horizontalTranslation) + horizontalBufferPt {
+            activeGestureAxis = .vertical
+        }
+    }
 }
 
 // MARK: - iOS 26 workaround: UIGestureRecognizerRepresentable