当.ondrag()结束而没有任何动作的能力(即

发布于 2025-01-24 09:35:37 字数 958 浏览 0 评论 0原文

我正在使用.ondrag修饰符启用拖放操作:

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

一旦调用拖动,我将draging标志设置为真。

进行下降没有问题。但是,如果我调用了一个拖动但没有任何动作而结束它,则我没有通知,即拖动仍将设置为true。

我尝试将第二个手势设置添加到没有任何感知运动的情况下结束的拖曳手势的高价或同时的手势:

        .gesture(DragGesture(minimumDistance: 0)
            .onEnded({ value in
                if value.translation.height == 0 && value.translation.width == 0 {
                    self.dragging = false
                }
            })
        )

不管我是否将其放置在Ondrag修饰符之前/之后,它都会阻止Ondrag触发。

有什么办法能够分辨出何时结束而没有任何动作,即立即释放阻力?

I’m using the .onDrag modifier to enable a drag/drop operation:

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

As soon as a drag is invoked, I set the dragging flag to be true.

There’s no issue with performing a drop. However, if I invoked a drag but ended it without any movement, I’m not informed, i.e. dragging remains set to true.

I’ve tried adding a second gesture set to either highPriority or simultaneous of a drag gesture that ends without any perceived movement:

        .gesture(DragGesture(minimumDistance: 0)
            .onEnded({ value in
                if value.translation.height == 0 && value.translation.width == 0 {
                    self.dragging = false
                }
            })
        )

Regardless of whether I place it before/after the onDrag modifier, it stops the onDrag from triggering.

Is there any way to be able to tell when an onDrag ends without any movement, i.e. drag released immediately?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

只想待在家 2025-01-31 09:35:37

我无法与Apple一起在Swiftui中实现上述问题,这是我得到的回应:

@gesturestate是在取消手势后清理的方法。
Onedend()告诉您何时完成。两者的结合
应该让您实施所描述的内容。

这无用,至少是基于我对提议的理解的理解。当我添加以下内容时:

@GestureState var tapGesture = false

和:

.onDrag {
   handler()
}
.simultaneousGesture(DragGesture()
   .updating($tapGesture) { _,_,_  in
       print("updating")
   }
   .onEnded { _ in
       print("ended")
   })

打印语句从未发生在阻力上。

但是,如果我做到了:

.onDrag {
     handler()
 }
 .simultaneousGesture(LongPressGesture()
     .updating($tapGesture) { _,_,_  in
         print("updating")
     }
     .onEnded { _ in
         print("ended")
     })

长媒体手势截距并停止阻力。更改最小持续时间似乎没有改变。也没有测序手势修饰符的顺序。或者如果设置了其他“虚拟”阻力为高优先级手势。

因此,我最终得到了不完美的创意解决方法。

首先,在根视图中,我添加了取消代表:

struct RootView: View {
    
    static var cancellation = CancellationDelegate()

...

.onDrop(of: [UTType.text], delegate: Self.cancellation)

其目的是确保可拖动视图立即被潜在的落下目标包围。我有一个视图模型,可以保留何时输入下降目标的记录。

    import SwiftUI
import UniformTypeIdentifiers
import ThirdSwiftLibs

struct CancellationDelegate: DropDelegate {

    var viewModel: CancellationViewModel = .init()
    
    // MARK: - Event handlers

    /// Dragged over a drop target
    func dropEntered(info: DropInfo) {
        viewModel.dropLastEntered = .now
    }
    
    /// Dragged away from a drop target
    func dropExited(info: DropInfo) {

    }
    
    /// Drag begins
    func dropUpdated(info: DropInfo) -> DropProposal? {
        DropProposal(operation: .move)
    }
  
    // MARK: - Mutators
    
    /// The dragged entity is dropped on a target
    /// - Returns: true if drop was successful
    func performDrop(info: DropInfo) -> Bool {
        SchedulerViewModel.shared.reset()
        return true
    }
    
    // MARK: - Checkers

    /// Checks whether the drag is considered valid based on
    /// the view it’s attached to
    /// - Returns: true if view is drag/droppable
    func validateDrop(info: DropInfo) -> Bool {
        /// Allow the drop to begin with any String set as the NSItemProvider
        return info.hasItemsConforming(to: [UTType.text])
    }
}

class CancellationViewModel {
    var dropLastEntered: Date?
}

在卡的拖放处理程序中,我检查了3秒后检查卡是否已移动。我可以通过参考取消倾向的值的值来说明。如果它不到3秒钟,那么可以合理地假设它已经移动了,所以我无需做任何事情。但是,如果它是3个或更多,那么可以安全地假设该阻力是在过去3秒内在某个时刻发布的,因此我应该将其用作提示重置应用程序状态的提示。

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    if let last = RootView.cancellation.viewModel.dropLastEntered {
        if last.seconds(from: .now) <= -3 {
            // reset UI because we last moved a card over the dummy drop target 3 or more seconds ago
        }
    }
    else {
        // reset UI because we’ve never moved any card over the dummy drop target
    }
}

这不是完美的原因是因为UI重置不会立即发生。在测试中,当系统决定由于未检测到的运动而决定将视图上的阻力驱动时,似乎存在3秒钟(该视图不再出现在系统最初被激活的阻力状态中)。因此,从这个角度来看,3秒钟很好。但是,如果我要在没有任何动作的情况下自己释放卡片,那么在UI意识到由于被驱动而需要重置的2秒钟(3-1),它将是2秒(3-1)。

希望这会有所帮助。如果有人有更好的解决方案,我全都是耳朵!

I filed the inability to achieve the above in SwiftUI with Apple, and this is the response I got:

@GestureState is the way to clean up after cancelled gestures.
onEnded() tells you when it completes. The combination of the two
should let you implement the things described.

This wasn't helpful, at least based on my understanding on what was proposed. When I added the following:

@GestureState var tapGesture = false

And:

.onDrag {
   handler()
}
.simultaneousGesture(DragGesture()
   .updating($tapGesture) { _,_,_  in
       print("updating")
   }
   .onEnded { _ in
       print("ended")
   })

The print statements never occurred on a drag.

If however I did:

.onDrag {
     handler()
 }
 .simultaneousGesture(LongPressGesture()
     .updating($tapGesture) { _,_,_  in
         print("updating")
     }
     .onEnded { _ in
         print("ended")
     })

The long press gesture intercepts and stops the drag. Changing the minimum duration didn’t seem to make a difference. Nor the order in which the gesture modifiers are sequenced. Or if the additional “dummy” drag is set to be a high priority gesture.

So I ended up with a not-perfect creative workaround.

Firstly, to the root view I added a cancellation delegate:

struct RootView: View {
    
    static var cancellation = CancellationDelegate()

...

.onDrop(of: [UTType.text], delegate: Self.cancellation)

Its purpose is to ensure that the draggable view is immediately surrounded by a potential drop target. I have a view model for it that keeps a record for when a drop target has been entered.

    import SwiftUI
import UniformTypeIdentifiers
import ThirdSwiftLibs

struct CancellationDelegate: DropDelegate {

    var viewModel: CancellationViewModel = .init()
    
    // MARK: - Event handlers

    /// Dragged over a drop target
    func dropEntered(info: DropInfo) {
        viewModel.dropLastEntered = .now
    }
    
    /// Dragged away from a drop target
    func dropExited(info: DropInfo) {

    }
    
    /// Drag begins
    func dropUpdated(info: DropInfo) -> DropProposal? {
        DropProposal(operation: .move)
    }
  
    // MARK: - Mutators
    
    /// The dragged entity is dropped on a target
    /// - Returns: true if drop was successful
    func performDrop(info: DropInfo) -> Bool {
        SchedulerViewModel.shared.reset()
        return true
    }
    
    // MARK: - Checkers

    /// Checks whether the drag is considered valid based on
    /// the view it’s attached to
    /// - Returns: true if view is drag/droppable
    func validateDrop(info: DropInfo) -> Bool {
        /// Allow the drop to begin with any String set as the NSItemProvider
        return info.hasItemsConforming(to: [UTType.text])
    }
}

class CancellationViewModel {
    var dropLastEntered: Date?
}

In the card’s drag handler, I check after 3 seconds whether the card has moved. I can tell by referring to the cancellation’s dropLastEntered value. If it’s been less than 3 seconds, then it’s reasonable to assume it has moved and so there’s nothing I need to do. However if it’s been 3 or more, then it’s equally safe to assume that the drag was released at some point within the last 3 seconds, and so I should use this as a cue to reset the app state.

DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    if let last = RootView.cancellation.viewModel.dropLastEntered {
        if last.seconds(from: .now) <= -3 {
            // reset UI because we last moved a card over the dummy drop target 3 or more seconds ago
        }
    }
    else {
        // reset UI because we’ve never moved any card over the dummy drop target
    }
}

The reason this isn’t perfect is because the UI reset doesn’t happen immediately. In tests, 3 seconds appears to be around when the system decides to de-activate the drag on the view due to no movement detected (the view no longer appears in the drag state the system originally activated). So, from this perspective 3 seconds is good. However, if I was to release the card myself after 1 second without any movement, then it will be another 2 seconds (3-1) before the UI realises it needs to reset due to the drag being de-activated.

Hope this helps. If someone has a better solution, I’m all ears!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文