能够在 .onDrag() 结束而没有任何移动时收到通知(即立即和发生任何下降之前)

Bre*_*mbs 7 drag-and-drop gesture ios swift swiftui

I\xe2\x80\x99m 使用 .onDrag 修饰符启用拖/放操作:

\n
struct RootView: View {\n    \n    @State var dragging: Bool = false\n    \n    var body: some View {\n        VStack {\n            Color.red.cornerRadius(.some)\n                .frame(width: 100, height: 100)\n                .onDrag {\n                    dragging = true\n                    return NSItemProvider(object: NSString())\n                }\n        }\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

一旦调用拖动,我就将dragging标志设置为 true。

\n

\xe2\x80\x99s 执行放置没有问题。但是,如果我调用了拖动但在没有任何移动的情况下结束了它,则 I\xe2\x80\x99m 不会被通知,即dragging仍然设置为 true。

\n

我\xe2\x80\x99ve尝试将第二个手势集添加到高优先级或同时拖动手势,该手势在没有任何感知到的移动的情况下结束:

\n
        .gesture(DragGesture(minimumDistance: 0)\n            .onEnded({ value in\n                if value.translation.height == 0 && value.translation.width == 0 {\n                    self.dragging = false\n                }\n            })\n        )\n
Run Code Online (Sandbox Code Playgroud)\n

无论我是否将其放置在 onDrag 修改器之前/之后,它都会阻止 onDrag 触发。

\n

有什么方法可以判断 onDrag 何时结束而没有任何移动,即拖动立即释放?

\n

Bre*_*mbs 2

我向 Apple 提交了无法在 SwiftUI 中实现上述功能的问题,这是我得到的回复:

\n
\n

@GestureState 是取消手势后清理的方法。\nonEnded() 告诉您它何时完成。两者的组合\n应该可以让您实现所描述的内容。

\n
\n

至少根据我对提议内容的理解,这没有帮助。当我添加以下内容时:

\n
@GestureState var tapGesture = false\n
Run Code Online (Sandbox Code Playgroud)\n

和:

\n
.onDrag {\n   handler()\n}\n.simultaneousGesture(DragGesture()\n   .updating($tapGesture) { _,_,_  in\n       print("updating")\n   }\n   .onEnded { _ in\n       print("ended")\n   })\n
Run Code Online (Sandbox Code Playgroud)\n

打印语句从未在拖动时发生。

\n

然而如果我这样做了:

\n
.onDrag {\n     handler()\n }\n .simultaneousGesture(LongPressGesture()\n     .updating($tapGesture) { _,_,_  in\n         print("updating")\n     }\n     .onEnded { _ in\n         print("ended")\n     })\n
Run Code Online (Sandbox Code Playgroud)\n

长按手势会拦截并停止拖动。更改最短持续时间似乎没有产生影响。也不是手势修饰符的排序顺序。或者,如果附加 \xe2\x80\x9cdummy\xe2\x80\x9d 拖动设置为高优先级手势。

\n

所以我最终得到了一个不完美的创造性解决方法。

\n

首先,我在根视图中添加了一个取消委托:

\n
struct RootView: View {\n    \n    static var cancellation = CancellationDelegate()\n
Run Code Online (Sandbox Code Playgroud)\n

...

\n
.onDrop(of: [UTType.text], delegate: Self.cancellation)\n
Run Code Online (Sandbox Code Playgroud)\n

其目的是确保可拖动视图立即被潜在的放置目标包围。我有一个视图模型,用于记录输入放置目标的时间。

\n
    import SwiftUI\nimport UniformTypeIdentifiers\nimport ThirdSwiftLibs\n\nstruct CancellationDelegate: DropDelegate {\n\n    var viewModel: CancellationViewModel = .init()\n    \n    // MARK: - Event handlers\n\n    /// Dragged over a drop target\n    func dropEntered(info: DropInfo) {\n        viewModel.dropLastEntered = .now\n    }\n    \n    /// Dragged away from a drop target\n    func dropExited(info: DropInfo) {\n\n    }\n    \n    /// Drag begins\n    func dropUpdated(info: DropInfo) -> DropProposal? {\n        DropProposal(operation: .move)\n    }\n  \n    // MARK: - Mutators\n    \n    /// The dragged entity is dropped on a target\n    /// - Returns: true if drop was successful\n    func performDrop(info: DropInfo) -> Bool {\n        SchedulerViewModel.shared.reset()\n        return true\n    }\n    \n    // MARK: - Checkers\n\n    /// Checks whether the drag is considered valid based on\n    /// the view it\xe2\x80\x99s attached to\n    /// - Returns: true if view is drag/droppable\n    func validateDrop(info: DropInfo) -> Bool {\n        /// Allow the drop to begin with any String set as the NSItemProvider\n        return info.hasItemsConforming(to: [UTType.text])\n    }\n}\n\nclass CancellationViewModel {\n    var dropLastEntered: Date?\n}\n
Run Code Online (Sandbox Code Playgroud)\n

在 card\xe2\x80\x99s 拖动处理程序中,我在 3 秒后检查卡是否已移动。我可以通过参考cancel\xe2\x80\x99s dropLastEntered值来判断。如果它\xe2\x80\x99s少于3秒,那么\xe2\x80\x99s可以合理地假设它已经移动,所以\xe2\x80\x99s我不需要做任何事情。然而,如果它\xe2\x80\x99s是3或更多,那么它\xe2\x80\x99s同样安全地假设阻力在最后3秒内的某个时刻被释放,所以我应该用它作为提示重置应用程序状态。

\n
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {\n    if let last = RootView.cancellation.viewModel.dropLastEntered {\n        if last.seconds(from: .now) <= -3 {\n            // reset UI because we last moved a card over the dummy drop target 3 or more seconds ago\n        }\n    }\n    else {\n        // reset UI because we\xe2\x80\x99ve never moved any card over the dummy drop target\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这并不完美的原因是 UI 重置不会立即发生。在测试中,由于未检测到移动,系统决定停用视图上的拖动(视图不再以系统最初激活的拖动状态显示),大约需要 3 秒。所以,从这个角度来说,3秒是好的。但是,如果我在 1 秒后没有任何移动地自行释放卡,则 UI 需要再过 2 秒 (3-1) 才能意识到由于拖动被停用而需要重置。

\n

希望这可以帮助。如果有人有更好的解决方案,我\xe2\x80\x99m洗耳恭听!

\n