SwiftUI 拖放重新排序 - 检测对象释放

SwiftUI Drag and Drop reorder - detect object release

我实现了一个简单的拖放操作,用于在 VStack/Scrollview 中重新排序项目 根据 this Solution

我将当前拖动的项目存储在名为 draggingItem 的 属性 中,并将不透明度设置为 0,具体取决于它是否为零。 当调用 DropDelegate 中的 performDrop 时,我将 draggingItem 设置回 nil 以使相应的项目再次可见。

有两种情况似乎没有调用 performDrop:

  1. 当项目被拖拽然后在没有移动的情况下释放到位。

  2. 当物品确实被释放时,实际的掉落区域会稍微偏移。

这导致项目不再可见,因为 draggingItem 没有再次设置为 nil。

关于将 draggingItem 设置回零的更好位置的任何想法?

查看:

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:

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)
    }
}

测试项目:

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

我调查了一个问题 1) 并在

中提出了解决方案

问题 2) 可以在自定义重写项提供程序和 deinit 上的操作的帮助下解决,`因为取消拖动会话时提供程序被销毁。

测试 Xcode 13.4 / iOS 15.5

主要部分:

    // 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