带有点击和拖动手势的 SwiftUI ScrollView

leo*_*oe1 1 ios swift swiftui

我正在尝试使用可以点击和拖动的元素来实现 ScrollView。它应该按以下方式工作:

  1. ScrollView 应该可以正常工作,因此向上/向下滑动不应干扰手势。
  2. 点击一个条目应该运行一些代码。如果有一个“敲击指示器”,那就太好了,这样用户就知道敲击已被注册(使这变得困难的是,敲击指示器应该在触摸触发,而不是在触摸触发,并且应该处于活动状态直到手指松开)。
  3. 长按条目应激活拖动手势,以便可以四处移动项目。

下面的代码涵盖了所有这些要求(抽头指示器除外)。但是,我不确定它为什么起作用,具体来说,为什么我需要使用 .highPriorityGesture 并且例如不能对 Tap Gesture 和 DragGesture 进行排序.sequenced(before: ...)(这会阻止滚动)。

另外,我希望在触地事件时收到通知(不是触地,参见 2.)。我尝试使用 LongPressGesture() 而不是 TapGesture(),但这也会阻止 ScrollView 滚动,之后甚至不会触发 DragGesture。

有人知道这是如何实现的吗?或者这就是 SwiftUI 的极限?如果是这样,是否有可能移植 UIKit 的东西来实现这一点(我也已经尝试过,但没有成功,ScrollView 的内容也应该是动态的,因此移植整个 ScrollView 可能很困难)?

谢谢你的协助!

struct ContentView: View {
   
    var body: some View {
        ScrollView() {
            ForEach(0..<5, id: \.self) { i in
                ListElem()
                    .highPriorityGesture(TapGesture().onEnded({print("tapped!")}))
                    .frame(maxWidth: .infinity)
            }
        }
    }
}

struct ListElem: View {
    @GestureState var dragging = CGSize.zero
    
    var body: some View {
        Circle()
        .frame(width: 100, height: 100)
            .gesture(DragGesture(minimumDistance: 0, coordinateSpace: .global)
                .updating($dragging, body: {t, state, _ in
                    state = t.translation
            }))
        .offset(dragging)

    }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*ner 6

我尝试了几个选项,我认为组合sequencedsimultaneously允许两个手势同时运行。为了实现 onTouchDown,我使用了DragGesture最小距离为 0 的 。

struct ContentView: View {
    
    var body: some View {
        ScrollView() {
            ForEach(0..<5, id: \.self) { i in
                ListElem()
                    .frame(maxWidth: .infinity)
            }
        }
    }
}

struct ListElem: View {
    
    @State private var offset = CGSize.zero
    @State private var isDragging = false
    @GestureState var isTapping = false
    
    var body: some View {
        
        // Gets triggered immediately because a drag of 0 distance starts already when touching down.  
        let tapGesture = DragGesture(minimumDistance: 0)
            .updating($isTapping) {_, isTapping, _ in
                isTapping = true
            }

        // minimumDistance here is mainly relevant to change to red before the drag
        let dragGesture = DragGesture(minimumDistance: 0)
            .onChanged { offset = $0.translation }
            .onEnded { _ in
                withAnimation {
                    offset = .zero
                    isDragging = false
                }
            }
        
        let pressGesture = LongPressGesture(minimumDuration: 1.0)
            .onEnded { value in
                withAnimation {
                    isDragging = true
                }
            }
        
        // The dragGesture will wait until the pressGesture has triggered after minimumDuration 1.0 seconds.
        let combined = pressGesture.sequenced(before: dragGesture)
        
        // The new combined gesture is set to run together with the tapGesture.
        let simultaneously = tapGesture.simultaneously(with: combined)
        
        return Circle()
            .overlay(isTapping ? Circle().stroke(Color.red, lineWidth: 5) : nil) //listening to the isTapping state
            .frame(width: 100, height: 100)
            .foregroundColor(isDragging ? Color.red : Color.black) // listening to the isDragging state.
            .offset(offset)
            .gesture(simultaneously)
        
    }
}
Run Code Online (Sandbox Code Playgroud)

归功于

/sf/answers/4192859121/ http://developer.apple.com/documentation/swiftui/composing-swiftui-gestures https://www.hackingwithswift.com/books/ios-swiftui/how-在swiftui中使用手势

  • 谢谢!但是,这种方法有一个警告:ScrollView 的滚动会被阻止。ScrollView 确实很烦人。我想我将在几周内从头开始重新实现它。 (2认同)