Adding a high priority drag gesture to a scrollable view in iOS 18 causes the scrollable view to become unscrollable
Asked Answered
B

5

5

I'm trying to add a drag gesture over a vertically scrollable view like List (mainly to detect horizontal drag only). I want the drag gesture to work in higher priority than the scrollable view scrolling behavior. On iOS 17 or prior, the below code works, however on iOS 18 beta, I'm facing that the scrollable view becomes unscrollable.

I found a similar topic in the developer forum as well. https://forums.developer.apple.com/forums/thread/760551

This is the minimum reproducible example:

struct ContentView: View {
    var body: some View {
        List {
            ForEach(0..<100) {
                Text("\($0)")
            }
        }
        .highPriorityGesture(
            DragGesture()
                .onChanged({ value in
                    print("onChanged \(value)")
                })
                .onEnded({ value in
                    print("onEnded \(value)")
                })
        )
    }
}

Any workaround?

Blighter answered 26/8 at 4:18 Comment(3)
Perhaps you can elaborate more on why you need the drag gesture? Another way to detect scrolling would be to use a GeometryReader in the background of a list section.Grannia
I want another view (hidden by offset) to appear from out of the screen when I horizontally drag the whole screen.Blighter
I've noticed that in the iOS 18 simulator (only), highPriorityGestures aren't actually usurping any underlying regular gestures. E.g., if you have a high priority tap on top of a regular tap, both will execute simultaneously. I believe it's a simulator problem only, but obviously this is very inconvenient for development.Chyack
B
5

The issue looks solved if I set 15 to the minimumDistance of the DragGesture. (FYI: The default value is 10. It doesn't work with 14 so 15 seems to be the threshold)

.highPriorityGesture(
    DragGesture(minimumDistance: 15)
    ...
)

Note: I tried with iOS 18 beta 6


Update: I noticed the issue does not reproduce in a real iPhone device (iOS 18 beta) in the first place :(

Blighter answered 2/9 at 5:4 Comment(6)
I have same issue but set minimumDistance to 15 is not working for iOS 18.0 public.Siltstone
You probably better to raise a new question with the minimum reproducible code. I'll up vote it.Blighter
I had a highPriorityGesture horizontal drag gesture added via a view extension that worked fine prior to iOS 18, but stopped working in iOS 18 if the vertical scrolling was initiated on the area of the view with the high priority drag gesture. The only fix was to change it to .simultaneousGesture(.Verlaverlee
I get the same thing, if the vertical scroll is initiated on the area of the view with the high priority gesture, it doesnt scroll. And simultaneousGesture fixes that but has side effects as super jumpy and glitchy horizontal scroll... Were you able to improve this? Thanks!Transpicuous
@AndreiG. My finding is the same as you, however it won't work if your dragable view contains a button, whose tap gesture will also be triggered when you initiate dragging on top of that button.Guileful
@EricYuan I had posted a method that worked for me as a solution to another question, though I am not sure if it was the same exact issue as the one here. Take a look and see if you get anywhere with it.Verlaverlee
S
1

You could try this approach using simultaneousGesture instead. This will action your drag gesture as well as the scrolling of the List which I think is what you are asking for.

For iOS-18.0

struct ContentView: View {
    
    var drag: some Gesture {
        DragGesture()
            .onChanged({ value in
                print("--> onChanged \(value)")
            })
            .onEnded({ value in
                print("----> onEnded \(value)")
            })
    }
    
    var body: some View {
        List {
            ForEach(0..<100) {
                Text("\($0)")
            }
        }
        .simultaneousGesture(drag)
    }
}

Note you can also try using a mask such as:

.simultaneousGesture(drag, including: .gesture)
Stanislas answered 26/8 at 6:21 Comment(2)
Thank you for your support. With the suggested implementation, the scrollable view becomes scrollable, but what I exactly want is: "I want the drag gesture to work in higher priority than the scrollable view scrolling behavior" , which means I don't want scrollable view to scroll when the drag gesture is being detected.Blighter
If you don't want other gestures to work when the high priority gesture is detected, have you tried the including: .gesture parameter? It allows to "Enable the added gesture but disable all gestures in the subview hierarchy.".Verlaverlee
P
1

For me .highPriorityGesture(DragGesture(minimumDistance: 15)...) works unstable, .simultaneousGesture(DragGesture()..., including: .gesture) produces an undesirable side effect.

I ended up using .gesture(DragGesture()..., including: .gesture).
For me, it works both on iOS 17 and 18 in the same way as .highPriorityGesture does on iOS 17.

Pig answered 10/10 at 6:2 Comment(1)
This isn't working for me on iOS 18. The scroll view won't scroll, and dragging from a button subview also doesn't work.Guileful
R
0

I had a similar issue and wasn't able to find a fix using just SwiftUI, so resorted to implementing a custom gesture.

struct PanGesture: UIGestureRecognizerRepresentable {
    var handle: (UIPanGestureRecognizer) -> Void
    
    func makeCoordinator(converter: CoordinateSpaceConverter) -> Coordinator { .init() }
    
    func makeUIGestureRecognizer(context: Context) -> UIPanGestureRecognizer {
        let gesture = UIPanGestureRecognizer()
        gesture.delegate = context.coordinator
        gesture.isEnabled = true
        return gesture
    }
    
    func handleUIGestureRecognizerAction(_ recognizer: UIPanGestureRecognizer, context: Context) {
        handle(recognizer)
    }
    
    class Coordinator: NSObject, UIGestureRecognizerDelegate {
        func gestureRecognizer(
            _ gestureRecognizer: UIGestureRecognizer,
            shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer
        ) -> Bool {
            return false
        }
        
        func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
            guard let panRecognizer = gestureRecognizer as? UIPanGestureRecognizer else { return false }

            let velocity = panRecognizer.velocity(in: gestureRecognizer.view)
            // Only recognize horizontal gestures
            return abs(velocity.y) < abs(velocity.x)
        }
    }
}
Rectangle()
    .gesture(
        PanGesture { recognizer in
            self.dragPosition = recognizer.translation(in: recognizer.view).x
        }
    )
Rambow answered 17/9 at 10:49 Comment(0)
W
0

I was having the same issue.

This was my code:

}.highPriorityGesture(DragGesture().onChanged { _ in
    withAnimation {
        endTextEditing()
    }
})

I solved it by changing highPriorityGesture for simultaneousGesture like this:

}.simultaneousGesture(DragGesture().onChanged { _ in
    withAnimation {
        endTextEditing()
    }
})
Wavemeter answered 6/11 at 18:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.