I’m working on an app in SwiftUI, in which I want to support drop file functionality to handle any files. While I try implement this, I am trying accept any files dropped into a custom drop area and display their MIME type. I also provide a fallback file chooser NSOpenPanel. This part works.
However, the drop functionality fails when handling any file.
My HandleDrop code:
private func handleDrop(providers: [NSItemProvider]) -> Bool {
var success = false
for provider in providers {
print("Checking provider: \(provider)")
// Handle public.file-url
if provider.hasItemConformingToTypeIdentifier("public.file-url") {
print("Provider conforms to public.file-url")
provider.loadItem(forTypeIdentifier: "public.file-url", options: nil) { item, error in
if let url = item as? URL {
print("Dropped file URL: \(url)")
DispatchQueue.main.async {
droppedFileType = "MIME Type: \(url.mimeType ?? "Unknown")"
}
success = true
} else {
print("Failed to cast item to URL for public.file-url")
}
}
}
// Handle public.data
else if provider.hasItemConformingToTypeIdentifier("public.data") {
print("Provider conforms to public.data")
provider.loadItem(forTypeIdentifier: "public.data", options: nil) { item, error in
if let data = item as? Data {
let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString).appendingPathExtension("pdf")
do {
try data.write(to: tempURL)
print("Saved file to temporary URL: \(tempURL)")
DispatchQueue.main.async {
droppedFileType = "MIME Type: \(tempURL.mimeType ?? "Unknown")"
}
success = true
} catch {
print("Failed to write data to temporary file")
}
} else {
print("Failed to decode item as public.data")
}
}
}
// Unsupported type
else {
print("Provider does NOT conform to public.file-url or public.data")
}
}
return success
}
As mentioned above the File Chooser/NSOpenPanel, works perfectly for all file types. The MIME type is detected correctly and displayed. However, Drag & Drop provider while does conforms to public.data, the decoding as Data fails. My Logs show it Failed to decode item as public.data. I tried with file types like .png or .txt which all fails.
My .entitlement:
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
With some simple debugging, you can see that the type of item
is actually NSURL?
, even in the "public.data"
branch.
print(type(of: item!)) // NSURL
This is because loadItem(forTypeIdentifier:)
is kind of broken in Swift.
Since you want a URL
anyway, you don't need to write to a temporary file anymore. Just cast item
to URL
and it's basically the same as the "public.file-url"
branch.
Also consider using loadFileRepresentation
, which directly gives you a URL
. Or, use the Transferable
-based dropDestination
modifier like this:
.dropDestination(for: URL.self) { urls, location in
print(urls.map(\.mimeType))
return true
}
The way that you are modifying and returning success
doesn't make much sense and is not concurrency-safe. You should set success
to true outside of the completion handlers. Keep in mind that the completion handlers can be executed concurrently with the code in handleDrop
, on a different thread.