我使用。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())
}
}
}
}
一旦拖动被调用,我将dragging
标志设置为true。
执行掉落没有问题。但是,如果我调用了一个拖动操作,但是没有任何移动就结束了,我不会得到通知,即dragging
仍然设置为true。
我已经尝试添加第二个手势集highPriority或同时拖动手势结束没有任何感知运动:
.gesture(DragGesture(minimumDistance: 0)
.onEnded({ value in
if value.translation.height == 0 && value.translation.width == 0 {
self.dragging = false
}
})
)
不管我是把它放在onDrag修饰符之前还是之后,它都会阻止onDrag触发。
是否有任何方法能够告诉当一个onDrag结束没有任何移动,即拖动立即释放?
我向Apple提交了无法在SwiftUI中实现上述功能的问题,我得到的回复如下:
@GestureState是在取消手势后清理的方式。onEnded()告诉您何时完成。两者的结合应该让你实现所描述的东西。
这没有帮助,至少根据我对提议的理解。当我添加以下内容时:
@GestureState var tapGesture = false
:
.onDrag {
handler()
}
.simultaneousGesture(DragGesture()
.updating($tapGesture) { _,_,_ in
print("updating")
}
.onEnded { _ in
print("ended")
})
在拖放操作中没有print语句。
然而,如果我做了:
.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秒后检查卡片是否已经移动。我可以通过参考取消的dropLastEntered值来判断。如果它的时间小于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秒很好。然而,如果我自己在1秒后不做任何移动而释放卡片,那么在UI意识到它需要重置之前,它将是另外2秒(3-1),因为拖动被取消激活。
希望这对你有帮助。如果有人有更好的解决办法,我洗耳恭听!