简体   繁体   English

SwiftUI 拖放重新排序 - 检测 object 释放

[英]SwiftUI Drag and Drop reorder - detect object release

I implemented a simple drag and drop for reordering items in a VStack/Scrollview according to this Solution根据此解决方案,我实现了一个简单的拖放操作以重新排序 VStack/Scrollview 中的项目

I store the currently dragged item in a property called draggingItem and set the opacity to 0 depending if it is nil or not.我将当前拖动的项目存储在一个名为draggingItem的属性中,并将不透明度设置为 0,具体取决于它是否为 nil。 When performDrop in the DropDelegate gets called I set draggingItem back to nil to make the corresponding item visible again.当 DropDelegate 中的 performDrop 被调用时,我将draggingItem设置回 nil 以使相应的项目再次可见。

There are two scenarios where performDrop seems not to get called:有两种情况下 performDrop 似乎没有被调用:

  1. When the item was onDrag and then released in place without moving.当该项目被 onDrag 然后释放到位而不移动。

  2. When the item does get released slightly offset the actual droparea.当项目确实被释放时,会稍微偏移实际的放置区域。

This is causing that the item does not get visible again because draggingItem does not get set to nil again.这导致该项目不再可见,因为draggingItem没有再次设置为 nil。

Any Ideas for a better place for setting draggingItem back to nil?有什么想法可以更好地将draggingItem设置回 nil 吗?

在此处输入图像描述

View:看法:

struct ReorderingTestsView: View {
    
    @State var draggingItem: BookItem?
    @State var items: [BookItem] = [
        BookItem(name: "Harry Potter"),
        BookItem(name: "Lord of the Rings"),
        BookItem(name: "War and Peace"),
        BookItem(name: "Peter Pane")
    ]
    
    var body: some View {
        VStack{
            ScrollView{
                VStack(spacing: 10){
                    ForEach(items){ item in
                        VStack{
                            Text(item.name)
                                .padding(8)
                                .frame(maxWidth: .infinity)
                        }
                        .background(Color.gray)
                        .cornerRadius(8)
                        .opacity(item.id == draggingItem?.id ? 0.01 : 1) // <- HERE
                        .onDrag {
                            draggingItem = item
                            return NSItemProvider(contentsOf: URL(string: "\(item.id)"))!
                        }
                        .onDrop(of: [.item], delegate: DropViewDelegate(currentItem: item, items: $items, draggingItem: $draggingItem))
                    }
                }
                .animation(.default, value: items)
            }
        }
        .padding(.horizontal)
    }
}

DropViewDelegate: DropViewDelegate:

struct DropViewDelegate: DropDelegate {
    
    var currentItem: BookItem
    var items: Binding<[BookItem]>
    var draggingItem: Binding<BookItem?>

    func performDrop(info: DropInfo) -> Bool {
        draggingItem.wrappedValue = nil // <- HERE
        return true
    }
    
    func dropEntered(info: DropInfo) {
        if currentItem.id != draggingItem.wrappedValue?.id {
            let from = items.wrappedValue.firstIndex(of: draggingItem.wrappedValue!)!
            let to = items.wrappedValue.firstIndex(of: currentItem)!
            if items[to].id != draggingItem.wrappedValue?.id {
                items.wrappedValue.move(fromOffsets: IndexSet(integer: from),
                    toOffset: to > from ? to + 1 : to)
            }
        }
    }
    
    func dropUpdated(info: DropInfo) -> DropProposal? {
       return DropProposal(operation: .move)
    }
}

TestItem:测试项目:

struct BookItem: Identifiable, Equatable {
    var id = UUID()
    var name: String
}

I investigated a problem 1) and proposed solution in https://stackoverflow.com/a/72181964/12299030我调查了一个问题 1) 并在https://stackoverflow.com/a/72181964/12299030中提出了解决方案

The problem 2) can be solved with help of custom overridden item provider and action on deinit , `cause provider is destroyed when drag session is canceled.问题 2) 可以通过自定义覆盖的项目提供程序和deinit上的操作来解决,因为取消拖动会话时提供程序被破坏。

Tested with Xcode 13.4 / iOS 15.5使用 Xcode 13.4 / iOS 15.5 测试

演示

Main part:主要部分:

    // for demo simplicity, a convenient init can be created instead
    class MYItemProvider: NSItemProvider {
        var didEnd: (() -> Void)?
        deinit {
            didEnd?()     // << here !!
        }
    }

// ...

    let provider = MYItemProvider(contentsOf: URL(string: "\(item.id)"))!
    provider.didEnd = {
        DispatchQueue.main.async {
            draggingItem = nil      // << here !!
        }
    }

Complete test module is here 完整的测试模块在这里

For iOS you can add .onDrop to the fullscreen view and catch performDrop there.对于 iOS,您可以将.onDrop添加到全屏视图并在那里捕获performDrop

For macOS I could not find any solution with DropDelegate.对于 macOS,我找不到 DropDelegate 的任何解决方案。 For that reason you can use NSApplication.shared.currentEvent?.type == .leftMouseUp something like this出于这个原因,您可以使用类似这样NSApplication.shared.currentEvent?.type == .leftMouseUp

Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { timer in
    if (NSApplication.shared.currentEvent?.type == .leftMouseUp) {
       //perform your endDrop action                
       timer.invalidate()
    }
}

The undetected object-release, when the item is "onDrag", is caused by the dragged view's opacity being set to 0. SwiftUI ignores interaction with views with opacity of 0.当项目为“onDrag”时,未检测到的对象释放是由拖动视图的不透明度设置为 0 引起的。SwiftUI 忽略与不透明度为 0 的视图的交互。

When you drag over a another draggable item, dropEntered of that item is called and the reordering takes place.当您拖动另一个可拖动项目时,将调用该项目的 dropEntered 并进行重新排序。 After the reordering, the drag is now over "itself".重新排序后,拖动现在超过了“自身”。 But since the opacity is set to 0, SwiftUI ignores the view, hence the drag no longer is over a drop-target.但由于不透明度设置为 0,SwiftUI 会忽略视图,因此拖动不再位于放置目标上方。 Due to that, on touch-up, the drop is canceled and performDrop is not being called.因此,在修饰时,删除被取消并且 performDrop 没有被调用。

If you still want the item to be "invisible", you can use a very low non-zero-value for opacity, like 0.001 and it will work.如果您仍然希望该项目“不可见”,您可以使用非常低的非零值来表示不透明度,例如 0.001,它会起作用。 I found it looked quite nice, when using an opacity of 0.3 or 0.5.当使用 0.3 或 0.5 的不透明度时,我发现它看起来相当不错。

I had same issues and here is my example with solution how to resolve it:我遇到了同样的问题,这是我的示例以及如何解决它:

https://github.com/kvyat/DragAndDrop https://github.com/kvyat/DragAndDrop

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM