Receiving File Drops on macOS (and iOS) with SwiftUI

A complete example of the code to be used on iOS and macOS is available on GitHub here.

Starting Point

Example of the drop behaviour on iOS/iPadOS

While porting a pure SwiftUI app, I noticed that the onDrop file import handler used for the iOS app does not work as expected on macOS:

macOS app receiving a URL with a different path & content than the dragged file
providers.first!.loadFileRepresentation(forTypeIdentifier: UTType.item.identifier) {
  url, _ in
    message = describeDroppedURL(url!)
}

At first I thought it was just an issue with file names being erased / hidden, e.g. .com.apple.Foundation.NSItemProvider.MpBDqm.tmp instead of notes.txt, but upon closer inspection the file contents were also different. Instead of the expected data, the file read from the initial URL contained another URL in the form of file:///.file/id=6571367.2773272/.

Adaptation for macOS

Eventually I came to find out that these are known as File reference URLs, and this post offered a good reference on how they had to be handled historically (with NSURL), and that they should work out of the box with Swift’s URL.

In order to avoid having the system to create temporary files which then need to be read & referenced to get to the final file, I switched the macOS implementation to read the dropped file URL from the NSPasteboard item itself:

providers.first!.loadObject(ofClass: NSPasteboard.PasteboardType.self) {
  pasteboardItem, _ in
    message = describeDroppedURL(URL(string: pasteboardItem!.rawValue)!)
}
macOS app after the updated: Being able to read the correct file name and contents

Bonus

One last finding I made during testing was that the dropped data was different from whether the item was dragged from within the Finder’s tree view compared to when it was dragged from the title bar 🤔