@@ -15,6 +15,49 @@ struct LongPressWithFeedback: ViewModifier {
1515    private  let  feedbackGenerator   =  UIImpactFeedbackGenerator ( style:  . heavy) 
1616
1717    func  body( content:  Content )  ->  some  View  { 
18+         if  #available( iOS 18 ,  * )  { 
19+             mainContent ( content:  content) 
20+                 . gesture ( LongPressGestureRepresentable  {  gesture in 
21+                     switch  gesture. state { 
22+                     case  . ended,  . cancelled,  . failed: 
23+                         handleLongPress ( isPressing:  false ) 
24+                     default : 
25+                         handleLongPress ( isPressing:  true ) 
26+                     } 
27+                 } ) 
28+         }  else  { 
29+             mainContent ( content:  content) 
30+                 . onLongPressGesture ( minimumDuration:  0.25 )  {  }  onPressingChanged:  {  isPressing in 
31+                     handleLongPress ( isPressing:  isPressing) 
32+                 } 
33+         } 
34+     } 
35+     
36+     // The gesture's minimum duration doesn't actually invoke the perform block when elapsed (thus
37+     // the implementation below) but it does cancel other system gestures e.g. swipe to reply
38+     private  func  handleLongPress( isPressing:  Bool )  { 
39+         isLongPressing =  isPressing
40+         
41+         guard  isLongPressing else  { 
42+             triggerTask? . cancel ( ) 
43+             return 
44+         } 
45+         
46+         feedbackGenerator. prepare ( ) 
47+         
48+         triggerTask =  Task  { 
49+             // The wait time needs to be at least 0.5 seconds or the long press gesture will take precedence over long pressing links.
50+             try ?   await  Task . sleep ( for:  . seconds( 0.5 ) ) 
51+             
52+             if  Task . isCancelled {  return  } 
53+             
54+             action ( ) 
55+             feedbackGenerator. impactOccurred ( ) 
56+         } 
57+     } 
58+     
59+     @ViewBuilder  
60+     private  func  mainContent( content:  Content )  ->  some  View  { 
1861        content
1962            . compositingGroup ( )  // Apply the shadow to the view as a whole.
2063            . shadow ( color:  . black. opacity ( isLongPressing ?  0.2  :  0.0 ) ,  radius:  isLongPressing ?  12  :  0 ) 
@@ -23,28 +66,6 @@ struct LongPressWithFeedback: ViewModifier {
2366                         y:  isLongPressing ?  1.05  :  1 ) 
2467            . animation ( . spring( response:  0.7 ) . delay ( isLongPressing ?  0.1  :  0 ) . disabledDuringTests ( ) , 
2568                       value:  isLongPressing) 
26-             // The minimum duration here doesn't actually invoke the perform block when elapsed (thus
27-             // the implementation below) but it does cancel other system gestures e.g. swipe to reply
28-             . onLongPressGesture ( minimumDuration:  0.25 )  {  }  onPressingChanged:  {  isPressing in 
29-                 isLongPressing =  isPressing
30-                 
31-                 guard  isPressing else  { 
32-                     triggerTask? . cancel ( ) 
33-                     return 
34-                 } 
35-                 
36-                 feedbackGenerator. prepare ( ) 
37-                 
38-                 triggerTask =  Task  { 
39-                     // The wait time needs to be at least 0.5 seconds or the long press gesture will take precedence over long pressing links.
40-                     try ?   await  Task . sleep ( for:  . seconds( 0.5 ) ) 
41-                     
42-                     if  Task . isCancelled {  return  } 
43- 
44-                     action ( ) 
45-                     feedbackGenerator. impactOccurred ( ) 
46-                 } 
47-             } 
4869    } 
4970} 
5071
@@ -101,3 +122,34 @@ struct LongPressWithFeedback_Previews: PreviewProvider, TestablePreview {
101122        } 
102123    } 
103124} 
125+ 
126+ // Fixes the issue on iOS 18 where LongPress conflicts with the scroll view
127+ // https://github.com/feedback-assistant/reports/issues/542#issuecomment-2581322968
128+ private  struct  LongPressGestureRepresentable :  UIGestureRecognizerRepresentable  { 
129+     var  handle :  ( UILongPressGestureRecognizer )  ->  Void 
130+     
131+     func  makeCoordinator( converter:  CoordinateSpaceConverter )  ->  Coordinator  {  . init( )  } 
132+     
133+     func  makeUIGestureRecognizer( context:  Context )  ->  UILongPressGestureRecognizer  { 
134+         let  gesture  =  UILongPressGestureRecognizer ( ) 
135+         gesture. minimumPressDuration =  0.25 
136+         gesture. delegate =  context. coordinator
137+         gesture. isEnabled =  true 
138+         return  gesture
139+     } 
140+     
141+     func  handleUIGestureRecognizerAction( _ recognizer:  UILongPressGestureRecognizer ,  context:  Context )  { 
142+         handle ( recognizer) 
143+     } 
144+         
145+     class  Coordinator :  NSObject ,  UIGestureRecognizerDelegate  { 
146+         func  gestureRecognizer( _ gestureRecognizer:  UIGestureRecognizer , 
147+                                shouldRecognizeSimultaneouslyWith otherGestureRecognizer:  UIGestureRecognizer )  ->  Bool  { 
148+             false 
149+         } 
150+         
151+         func  gestureRecognizerShouldBegin( _ gestureRecognizer:  UIGestureRecognizer )  ->  Bool  { 
152+             true 
153+         } 
154+     } 
155+ } 
0 commit comments