在 SwiftUI 中检测 DragGesture 取消

Detect DragGesture cancelation in SwiftUI

所以我有一个添加了 DragGesture 的矩形,我想跟踪手势的开始、变化和结束。问题是当我在执行手势时将另一根手指放在 Rectangle 上时,第一个手势停止调用 onChange 处理程序并且不会触发 onEnded 处理程序。 此外,处理程序不会为第二根手指开火。

但是,如果我在没有移开前两根手指的情况下放置第三根手指,该手势的处理程序就会开始触发(以此类推,偶数按下会抵消奇数)

这是一个错误吗?有没有办法检测到第一个手势被取消了?

Rectangle()
  .fill(Color.purple)
  .gesture(
    DragGesture(minimumDistance: 0, coordinateSpace: .local)
      .onChanged() { event in
        self.debugLabelText = "changed \(event)"
      }
      .onEnded() { event in
        self.debugLabelText = "ended \(event)"
      }
  )

感谢@krjw 手指数偶数的提示

这似乎是 Gesture 框架中的一个问题,它试图检测一堆手势,即使我们没有指定它应该监听它们。

由于文档非常稀疏,我们只能真正猜测这里的预期行为和生命周期是什么(恕我直言 - 这似乎是一个错误) - 但它可以解决。

定义一个类似

的结构方法
func onDragEnded() {
  // set state, process the last drag position we saw, etc
}

然后将几个手势组合成一个来覆盖我们没有指定的基础

let drag = DragGesture(minimumDistance: 0)
      .onChanged({ drag in
       // Do stuff with the drag - maybe record what the value is in case things get lost later on 
      })
      .onEnded({ drag in
        self.onDragEnded()
      })

     let hackyPinch = MagnificationGesture(minimumScaleDelta: 0.0)
      .onChanged({ delta in
        self.onDragEnded()
      })
      .onEnded({ delta in
        self.onDragEnded()
      })

    let hackyRotation = RotationGesture(minimumAngleDelta: Angle(degrees: 0.0))
      .onChanged({ delta in
        self.onDragEnded()
      })
      .onEnded({ delta in
        self.onDragEnded()
      })

    let hackyPress = LongPressGesture(minimumDuration: 0.0, maximumDistance: 0.0)
      .onChanged({ _ in
        self.onDragEnded()
      })
      .onEnded({ delta in
        self.onDragEnded()
      })

    let combinedGesture = drag
      .simultaneously(with: hackyPinch)      
      .simultaneously(with: hackyRotation)
      .exclusively(before: hackyPress)

/// The pinch and rotation may not be needed - in my case I don't but 
///   obviously this might be very dependent on what you want to achieve

simultaneouslyexclusively 可能有更好的组合,但至少对于我的用例(类似于操纵杆的东西)来说,这似乎可以完成工作

还有一个 GestureMask 类型可能已经完成了这项工作,但没有关于它如何工作的文档。

一种解决方案是使用 @GestureState 属性 来跟踪拖动当前是否为 运行。当手势取消时,状态将自动重置为 false。

struct DragSampleView: View {
    @GestureState private var dragGestureActive: Bool = false
    @State var dragOffset: CGSize = .zero

    var draggingView: some View {
        Text("DRAG ME").padding(50).background(.red)
    }

    var body: some View {
        ZStack {
            Color.blue.ignoresSafeArea()
            draggingView
                .offset(dragOffset)
                .gesture(DragGesture()
                    .updating($dragGestureActive) { value, state, transaction in
                        state = true
                    }
                    .onChanged { value in
                        print("onChanged")
                        dragOffset = value.translation
                    }.onEnded { value in
                        print("onEnded")
                        dragOffset = .zero
                    })
                .onChange(of: dragGestureActive) { newIsActiveValue in
                    if newIsActiveValue == false {
                        dragCancelled()
                    }
                }
        }
    }

    private func dragCancelled() {
        print("dragCancelled")
        dragOffset = .zero
    }
}

struct DragV_PreviewProvider: PreviewProvider {
    static var previews: some View {
        DragSampleView()
    }
}

https://developer.apple.com/documentation/swiftui/draggesture/updating(_:body:)