How to drag an item using NSItemProviderWriting using async

Scenario: The following Swift code uses NSItemProviderWriting to load a file from some data source async (The code actually just uses a for loop to simulate the time it needs to load the file). It returns the file url then. This code is used for drag & drop behavior in a macOS app from an app to another app. As the other app does only support file URLs as drop types, I can't send over the data itself.

class AnyFile: NSObject, NSItemProviderWriting {
    let url: URL
    init(url: URL) {
        self.url = url
    }
    
    static var writableTypeIdentifiersForItemProvider: [String] {
        return [UTType.fileURL.identifier]
    }
    
    func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping @Sendable (Data?, Error?) -> Void) -> Progress? {
        let count = 100000
        let progress = Progress(totalUnitCount: Int64(count))
        print("a")
        Task {
            print("b")
            for i in 0...100000 {
                progress.completedUnitCount = Int64(i)
                print(i)
            }
            print("c")
            completionHandler(url.dataRepresentation, nil)
            print("d")
        }
        print("e")
        return progress
    }
}

Problem: Drag & drop works, but when I start to drag the view (SwiftUI view using .onDrag), the view gets stuck as long as the loop is running. It is not clear to me why this happens as the loop us running inside a task. The console output also shows that progress is returned before the loop as finished. But still, it is stuck until the end of the loop. So the user experience is like not using async and the task.

Log output (shortened)

a
e
b
0
...
100000
c
d

Actual Question: Why is this Swift code for dragging files not behaving asynchronously even though it uses Task, completionHandler, and async/await constructs?

Alternatives tried: I'm not bound to NSItemProviderWriting in particular. I have tried other ways, like Transferable, .draggable(), and NSItemProvider.registerFileRepresentation. But either they won't return a URL, or do not allow async at all. I also tried AppKit instead of SwiftUI with similar issues.

Replies

The console output also shows that progress is returned before the loop as finished.

That is the correct behavior. The function exits (printing "e") before the task is even scheduled for execution (the order here is arbitrary — you can't rely on it either way).

the loop us running inside a task

It sounds like the loop is actually running on the main thread, which would certainly block your drag for as long as the loop is executing. I can't really say why, since it depends on some larger contextual information, but at least you might investigate what thread the loop is running on.

In this case, if you want a task that really does not run on the main thread, consider using Task.detached instead of Task.

Thanks for the idea with Task.detached. It made no change however. Please check the video below. You can see that when I start dragging the text, the loop prints the numbers in the background. Only when this is finished I can see the drag preview and the + sign to indicate open-in-place. When I drop before this preview is shown nothing happens. When it is shown (in other words, the loop has finished), then it works.

In the end, what I want to achieve is that I can drag a URL promise to another application that only accepts file URLs as drop type. As long as the file does not exist (shall be created either when I start dragging, or when dropped to the target application), I want to show a some kind of loading indicator in my app.

I have uploaded the code of my drag test application here: https://github.com/sarensw/DragTest. It contains all variants that I have tried so far (nothing works as of now). The one that I asked about in this post is UsingNSItemProvider1. I'm currently playing around with UsingNSViewRepresentable