首页 > 解决方案 > 系统未一致调用 SwiftUI 拖放项提供程序

问题描述

我正在使用 DropDelegate 在 iOS 上的 SwiftUI 中处理拖放操作。我在这里有一个简化的示例应用程序:

https://gist.github.com/jhaungs/6ac2c3a34309b7115eef40925c27b1ab

问题似乎是SwiftUI 框架和我的代码之间的交互。如果您尝试在dropEntereddropUpdated调用中从DropInfo加载itemProvider,则系统永远不会在模型上调用loadData函数;但不知何故,当从performDrop 调用相同的代码时,它可以正常工作。

如果在getItem中设置断点或者添加打印语句,可以看到itemProviders永远不会为空;它只有一个正确类型的项目提供者(utf8 文本)。但是,当从 performDrop调用getItem时,对其调用loadObject只会调用loadData 。所以有一些不透明的内部状态调解数据传输。

任何想法为什么这不一致?这对我来说似乎是一个错误。

dropEntereddropUpdated 中,我想知道我在拖动什么,这样我就可以做一些事情,比如突出显示它,或者动画它,或者甚至禁止或取消放置,但是没有反馈。

我也可能做错了。我花了很多时间在它上面,这是我最接近它的工作。尚不清楚每个项目一个 DropDelegate 是否正确或明智。文档非常缺乏,尤其是工作示例。

在另一个 StackOverflow 问题上,有人建议将当前项目分配给 onDrag 中的 viewModel 变量,但这会产生变量未一致设置或清除的竞争条件。

SwiftUI | 使用 onDrag 和 onDrop 在一个 LazyGrid 中重新排序项目?

标签: iosswiftuidrag-and-dropdelegates

解决方案


我有点不清楚你在问什么,但如果你需要的只是在 SwiftUI 中工作的拖放,这里有一个基于你链接的相同示例的建议:

import SwiftUI
import UniformTypeIdentifiers

struct GridData: Identifiable, Equatable {
    let id: String
}

//MARK: - Model

class Model: ObservableObject {
    @Published var data: [GridData]

    let columns = [
        GridItem(.flexible(minimum: 60, maximum: 60))
    ]

    init() {
        data = Array(repeating: GridData(id: "0"), count: 50)
        for i in 0..<data.count {
            data[i] = GridData(id: String("\(i)"))
        }
    }
}

//MARK: - Grid

struct DemoDragRelocateView: View {
    @StateObject private var model = Model()

    @State private var dragging: GridData? // I can't reset this when user drops view ins ame location as drag started
    @State private var changedView: Bool = false

    var body: some View {
        VStack {
            ScrollView(.vertical) {
               LazyVGrid(columns: model.columns, spacing: 5) {
                    ForEach(model.data) { d in
                        GridItemView(d: d)
                            .opacity(dragging?.id == d.id && changedView ? 0 : 1)
                            .onDrag {
                                self.dragging = d
                                changedView = false
                                return NSItemProvider(object: String(d.id) as NSString)
                            }
                            .onDrop(of: [UTType.text], delegate: DragRelocateDelegate(item: d, listData: $model.data, current: $dragging, changedView: $changedView))
                            
                    }
                }.animation(.default, value: model.data)
            }
        }
        .frame(maxWidth:.infinity, maxHeight: .infinity)
        .background(Color.gray.edgesIgnoringSafeArea(.all))
        .onDrop(of: [UTType.text], delegate: DropOutsideDelegate(current: $dragging, changedView: $changedView))
    }
}

struct DragRelocateDelegate: DropDelegate {
    let item: GridData
    @Binding var listData: [GridData]
    @Binding var current: GridData?
    @Binding var changedView: Bool
    
    func dropEntered(info: DropInfo) {
        
        if current == nil { current = item }
        
        changedView = true
        
        if item != current {
            let from = listData.firstIndex(of: current!)!
            let to = listData.firstIndex(of: item)!
            if listData[to].id != current!.id {
                listData.move(fromOffsets: IndexSet(integer: from),
                    toOffset: to > from ? to + 1 : to)
            }
        }
    }

    func dropUpdated(info: DropInfo) -> DropProposal? {
        return DropProposal(operation: .move)
    }

    func performDrop(info: DropInfo) -> Bool {
        changedView = false
        self.current = nil
        return true
    }
    
}

struct DropOutsideDelegate: DropDelegate {
    @Binding var current: GridData?
    @Binding var changedView: Bool
        
    func dropEntered(info: DropInfo) {
        changedView = true
    }
    func performDrop(info: DropInfo) -> Bool {
        changedView = false
        current = nil
        return true
    }
}

//MARK: - GridItem

struct GridItemView: View {
    var d: GridData

    var body: some View {
        VStack {
            Text(String(d.id))
                .font(.headline)
                .foregroundColor(.white)
        }
        .frame(width: 60, height: 60)
        .background(Circle().fill(Color.green))
    }
}
```

推荐阅读