listless

A simple list app for Apple platforms
Log | Files | Refs | README | LICENSE

ItemRowDragGesture.swift (3234B)


      1 import SwiftUI
      2 
      3 extension View {
      4     func itemDragGesture(
      5         isActive: Bool,
      6         itemID: UUID,
      7         onDragStart: @escaping (CGFloat) -> Void,
      8         onDragChanged: @escaping (CGPoint) -> Void,
      9         onDragEnded: @escaping () -> Void
     10     ) -> some View {
     11         self.modifier(
     12             ItemRowDragGesture(
     13                 isActive: isActive,
     14                 itemID: itemID,
     15                 onDragStart: onDragStart,
     16                 onDragChanged: onDragChanged,
     17                 onDragEnded: onDragEnded
     18             ))
     19     }
     20 }
     21 
     22 struct ItemRowDragGesture: ViewModifier {
     23     let isActive: Bool
     24     let itemID: UUID
     25     let onDragStart: (CGFloat) -> Void
     26     let onDragChanged: (CGPoint) -> Void
     27     let onDragEnded: () -> Void
     28 
     29     func body(content: Content) -> some View {
     30         content
     31             .gesture(
     32                 SimultaneousDragGesture(
     33                     isActive: isActive,
     34                     onDragStart: onDragStart,
     35                     onDragChanged: onDragChanged,
     36                     onDragEnded: onDragEnded
     37                 )
     38             )
     39     }
     40 }
     41 
     42 // MARK: - iOS 26 workaround
     43 
     44 /// Uses UILongPressGestureRecognizer (minimumPressDuration: 0.4) via
     45 /// UIGestureRecognizerRepresentable to avoid iOS 26's child-gesture-blocks-
     46 /// ancestor issue. The delegate returns shouldRecognizeSimultaneouslyWith:true
     47 /// so the ScrollView's pan gesture is preserved.
     48 private struct SimultaneousDragGesture: UIGestureRecognizerRepresentable {
     49     let isActive: Bool
     50     let onDragStart: (CGFloat) -> Void
     51     let onDragChanged: (CGPoint) -> Void
     52     let onDragEnded: () -> Void
     53 
     54     func makeUIGestureRecognizer(context: Context) -> UILongPressGestureRecognizer {
     55         let recognizer = UILongPressGestureRecognizer()
     56         recognizer.minimumPressDuration = 0.4
     57         recognizer.delegate = context.coordinator
     58         recognizer.isEnabled = isActive
     59         return recognizer
     60     }
     61 
     62     func updateUIGestureRecognizer(_ recognizer: UILongPressGestureRecognizer, context: Context) {
     63         recognizer.isEnabled = isActive
     64     }
     65 
     66     func handleUIGestureRecognizerAction(
     67         _ recognizer: UILongPressGestureRecognizer, context: Context
     68     ) {
     69         switch recognizer.state {
     70         case .began:
     71             let width = recognizer.view?.bounds.width ?? 0
     72             onDragStart(width)
     73 
     74         case .changed:
     75             let location = recognizer.location(in: recognizer.view?.window)
     76             onDragChanged(location)
     77 
     78         case .ended, .cancelled:
     79             onDragEnded()
     80 
     81         default:
     82             break
     83         }
     84     }
     85 
     86     func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator {
     87         Coordinator()
     88     }
     89 
     90     class Coordinator: NSObject, UIGestureRecognizerDelegate {
     91         func gestureRecognizer(
     92             _ gestureRecognizer: UIGestureRecognizer,
     93             shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
     94         ) -> Bool {
     95             true
     96         }
     97 
     98         func gestureRecognizer(
     99             _ gestureRecognizer: UIGestureRecognizer,
    100             shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer
    101         ) -> Bool {
    102             otherGestureRecognizer.view is UITextView
    103         }
    104     }
    105 }