So I have a ScrollView holding a set of views:
ScrollView {
ForEach(cities) { city in
NavigationLink(destination: ...) {
CityRow(city: city)
}
.buttonStyle(BackgroundButtonStyle())
}
}
In every view I have a drag gesture:
let drag = DragGesture()
.updating($gestureState) { value, gestureState, _ in
// ...
}
.onEnded { value in
// ...
}
Which I assign to a part of the view:
ZStack(alignment: .leading) {
HStack {
// ...
}
HStack {
// ...
}
.gesture(drag)
}
As soon as I attach the gesture, the ScrollView stop scrolling. The only way to make it scroll it to start scrolling from a part of it which has no gesture attached. How can I avoid it and make both work together. In UIKit is was as simple as specifying true in shouldRecognizeSimultaneouslyWith method. How can I have the same in SwiftUI?
In SwiftUI I've tried attaching a gesture using .simultaneousGesture(drag) and .highPriorityGesture(drag) – they all work the same as .gesture(drag). I've also tried providing all possible static GestureMask values for including: parameter – I have either scroll working or my drag gesture working. Never both of them.
I believe what you want is
simultaneousGesture(_:including:), which is documented here.
HStack {
// ...
}
.simultaneousGesture(drag)
I had a similar problem with dragging a slider at:
This is the working answer code, with the "trick" of the "DispatchQueue.main.asyncAfter"
Maybe you could try something similar for your ScrollView.
struct ContentView: View {
@State var pos = CGSize.zero
@State var prev = CGSize.zero
@State var value = 0.0
@State var flag = true
var body: some View {
let drag = DragGesture()
.onChanged { value in
self.pos = CGSize(width: value.translation.width + self.prev.width, height: value.translation.height + self.prev.height)
}
.onEnded { value in
self.pos = CGSize(width: value.translation.width + self.prev.width, height: value.translation.height + self.prev.height)
self.prev = self.pos
}
return VStack {
Slider(value: $value, in: 0...100, step: 1) { _ in
self.flag = false
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.flag = true
}
}
}
.frame(width: 250, height: 40, alignment: .center)
.overlay(RoundedRectangle(cornerRadius: 25).stroke(lineWidth: 2).foregroundColor(Color.black))
.offset(x: self.pos.width, y: self.pos.height)
.gesture(flag ? drag : nil)
}
}
I can't find a pure SwiftUI solution to this so I used a UIViewRepresentable as a work around. In the meantime, I've submitted a bug to Apple. Basically, I've created a clear view with a pan gesture on it which I will present over any SwiftUI view I want to add the gesture to. It's not a perfect solution, but maybe it's good enough for you.
public struct ClearDragGestureView: UIViewRepresentable {
public let onChanged: (ClearDragGestureView.Value) -> Void
public let onEnded: (ClearDragGestureView.Value) -> Void
/// This API is meant to mirror DragGesture,.Value as that has no accessible initializers
public struct Value {
/// The time associated with the current event.
public let time: Date
/// The location of the current event.
public let location: CGPoint
/// The location of the first event.
public let startLocation: CGPoint
public let velocity: CGPoint
/// The total translation from the first event to the current
/// event. Equivalent to `location.{x,y} -
/// startLocation.{x,y}`.
public var translation: CGSize {
return CGSize(width: location.x - startLocation.x, height: location.y - startLocation.y)
}
/// A prediction of where the final location would be if
/// dragging stopped now, based on the current drag velocity.
public var predictedEndLocation: CGPoint {
let endTranslation = predictedEndTranslation
return CGPoint(x: location.x + endTranslation.width, y: location.y + endTranslation.height)
}
public var predictedEndTranslation: CGSize {
return CGSize(width: estimatedTranslation(fromVelocity: velocity.x), height: estimatedTranslation(fromVelocity: velocity.y))
}
private func estimatedTranslation(fromVelocity velocity: CGFloat) -> CGFloat {
// This is a guess. I couldn't find any documentation anywhere on what this should be
let acceleration: CGFloat = 500
let timeToStop = velocity / acceleration
return velocity * timeToStop / 2
}
}
public class Coordinator: NSObject, UIGestureRecognizerDelegate {
let onChanged: (ClearDragGestureView.Value) -> Void
let onEnded: (ClearDragGestureView.Value) -> Void
private var startLocation = CGPoint.zero
init(onChanged: @escaping (ClearDragGestureView.Value) -> Void, onEnded: @escaping (ClearDragGestureView.Value) -> Void) {
self.onChanged = onChanged
self.onEnded = onEnded
}
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
@objc func gestureRecognizerPanned(_ gesture: UIPanGestureRecognizer) {
guard let view = gesture.view else {
Log.assertFailure("Missing view on gesture")
return
}
switch gesture.state {
case .possible, .cancelled, .failed:
break
case .began:
startLocation = gesture.location(in: view)
case .changed:
let value = ClearDragGestureView.Value(time: Date(),
location: gesture.location(in: view),
startLocation: startLocation,
velocity: gesture.velocity(in: view))
onChanged(value)
case .ended:
let value = ClearDragGestureView.Value(time: Date(),
location: gesture.location(in: view),
startLocation: startLocation,
velocity: gesture.velocity(in: view))
onEnded(value)
@unknown default:
break
}
}
}
public func makeCoordinator() -> ClearDragGestureView.Coordinator {
return Coordinator(onChanged: onChanged, onEnded: onEnded)
}
public func makeUIView(context: UIViewRepresentableContext<ClearDragGestureView>) -> UIView {
let view = UIView()
view.backgroundColor = .clear
let drag = UIPanGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.gestureRecognizerPanned))
drag.delegate = context.coordinator
view.addGestureRecognizer(drag)
return view
}
public func updateUIView(_ uiView: UIView,
context: UIViewRepresentableContext<ClearDragGestureView>) {
}
}
来源:https://stackoverflow.com/questions/57700396/adding-a-drag-gesture-in-swiftui-to-a-view-inside-a-scrollview-blocks-the-scroll
