3

I am dragging and dropping views using a DropDelegate in SwiftUI. I'm successfully wrapping my data in an NSItemProvider using the .onDrag View Modifier, and I even have .onDrop working if I am able to have my drop data be one of the stored properties of my DropDelegate.

I'm trying to allow for decoding my drop data using the provided DropInfo. I update my view in the dropEntered(info:) delegate method, so that the user can have a preview of their drop before it occurs. When I write

info.itemProviders.first?.loadObject(...) { reading, error in
...
}

The completion handler is not called. If I were to instead write this closure in the performDrop(info:) delegate method, the completion handler would be called. Why is the completion handler only called in performDrop(info:)? Is there a way to have drag & drop previews while performing the necessary changes in real time while not having the data model changed in dropEntered(info:)?

I don't love that I edit my data model in dropEntered(info:), and I haven't gotten to how it would work if the user were to cancel the drag and drop somehow... Perhaps there's a better way to go about this that will allow me to edit my data model in performDrop(info:)?

Thank you!

Edit

Here's the code to reproduce the bug:

struct ReorderableForEach<Content: View, Item: Identifiable>: View {
    let items: [Item]
    let content: (Item) -> Content
    
    var body: some View {
        ForEach(items) { item in
            content(item)
                .onDrag {
                    return NSItemProvider(object: "\(item.id)" as NSString)
                }
                .onDrop(
                    of: [.text],
                    delegate: DragRelocateDelegate()
                )
        }
    }
}

struct DragRelocateDelegate: DropDelegate {
    func dropEntered(info: DropInfo) {
        let _ = info.itemProviders(for: [.text]).first?.loadObject(ofClass: String.self) { item, error in
            print("dropEntered: \(item)") // Won't trigger this
        }
    }
    
    func performDrop(info: DropInfo) -> Bool {
        let _ = info.itemProviders(for: [.text]).first?.loadObject(ofClass: String.self) { item, error in
            print("performDrop: \(item)") // Will trigger this
        }
        
        return true
    }
}
ramzesenok
  • 5,469
  • 4
  • 30
  • 41
Hunter Meyer
  • 314
  • 1
  • 10
  • Needed minimal reproducible example. – Asperi Apr 28 '21 at 05:24
  • That's odd. I decode and update UI from dropEntered on macOS successfully (using loadItem). Can you show your NSItemProvider registration/decoding and dropEntered body? – Ryan Apr 30 '21 at 22:15
  • @Asperi hi, I have the same issue and I edited the post with the bug reproducible code – ramzesenok Aug 31 '21 at 13:47
  • Does this answer your question https://stackoverflow.com/a/63438481/12299030? – Asperi Aug 31 '21 at 14:36
  • @Asperi this is the answer I originally took solution from and it works great, thanks! The I needed to restrict some items to be reorderable. Here the performDrop doesn’t get called when you release the finger above the item that does not move and therefore the item you just moved remains semi-transparent. Trying to find the solution brought me to not using a separate variable for currently dragged item and the bug with loadObject on dropEntered brought me here – ramzesenok Sep 01 '21 at 05:18
  • @ramzesenok Have you ever found a solution for this? Currently having the same issue on iOS, while the same code works without problems on macOS. – viedev May 03 '23 at 07:11
  • 1
    @viedev no, unfortunately not – ramzesenok May 04 '23 at 09:56

0 Answers0