DragGesture in SwiftUI ScrollView fails

I need a scrolling view to report when it's scroll/drag gesture ends.

Here's a simple SwiftUI example taking a basic approach:
Code Block swift
import SwiftUI
struct CarouselView: View {
  let colors: [Color] = [.red, .green, .blue]
  var drag: some Gesture {
    DragGesture()
      .onChanged { state in
        print("changing")
      }
      .onEnded { state in
        print("ended")
    }
  }
  var body: some View {
    ScrollView(.horizontal, showsIndicators: false) {
      HStack {
        ForEach(0..<10) { i in
          Text("Block \(i)")
            .frame(width: 300, height: 300)
            .background(colors[i % colors.count])
            .id(i)
        }
      }
    }
      .gesture(drag)
  }
}

The gesture's events properly fire when you drag vertically, but when you drag horizontally (matching the direction of the scroll view) only the first onChanged event fires.

I don't want to even begin trying to reimplement a ScrollView in SwiftUI just to get these events, but is there a better way?

Replies

Have you tried using the simultaneousGesture() modifier? I learned about it on Hacking with Swift. Here's the example code that Paul gives:

Code Block swift
struct ContentView: View {
var body: some View {
VStack {
Text("Hello, World!")
.onTapGesture {
print("Text tapped")
}
}
.simultaneousGesture(
TapGesture()
.onEnded { _ in
print("VStack tapped")
}
)
}
}


I don't know if this will help or not, but it might since the ScrollView already has a gesture that seems like it might be overriding yours.
Great idea, but unfortunately it makes no difference. Same results on beta 7
Any updates to this? I'm having the same issue. Xcode 12.2.

My case the ScrollView has a vertical axes and I'm using the .updating gesture modifier to capture changes.

Dragging the View inside the ScrollView horizontally works fine.
Dragging the View inside the ScrollView vertically (same as the axes) calls updating exactly once, then the ScrollView takes over.
Dragging starting horizontally then continuing vertically (like a J gesture) works fine, updating throughout the whole gesture.

I would expect high priority gesture with .gesture mask would be what works, but it doesn't.

I'm not sure how adding it simultaneously can be done without having access to the ScrollView's gesture.


Anyone ever figure this out?

So the problem here seems to be that ScrollView will cancel the DragGesture when ScrollView takes over the movement. This is why we only see the onChanged called once. It seems to be briefly explained in the UIScrollView Overview, specifically this part: "If the user then drags their finger far enough before the timer elapses, the scroll view cancels any tracking in the subview and performs the scrolling itself."

The only solution here that worked for me is to wrap UIKit UIScrollView and then use UIScrollViewDelegate to hook into various drag events.

You can see here an example implementation which is not too hard to implement and extend.

  • Seems like example in link don't work (Xcode 13)

Add a Comment

I have seen same issues on List too. Has someone faced the same issues with other views? and do we have an alternate solution?

I found a solution that ended up fixing the freezing!

TLDR: set the gesture's coordinateSpace to .global when instantiating.

For me the solution was to use .highPriorityGesture(drag) when attaching drag to inner view. I have a joystick inside vertical scroll view. Problem was that joystick gesture was often hijacked by the scrollview when starting the drag gesture in a vertical direction. When it was started in horizontal it was working fine.

  • Revisited as .highPriorityGesture mentioned above wasn't perfect. The approach here: https://www.hackingwithswift.com/forums/swiftui/a-guide-to-delaying-gestures-in-scrollview/6005 solves the problem.

Add a Comment