Adding a drag gesture in SwiftUI to a View inside a ScrollView blocks the scrolling

孤者浪人 提交于 2019-12-04 05:31:24

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:

stackoverflow question

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>) {
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!