I'm building a SwiftUI view with draggable content, with a custom NSItemProviderWriting
class to provide one type of data for when dragged into another part of the app, and another type of data when dragged into Finder. I'm having the hardest time setting up the NSItemProviderWriting
in order to drag to the Finder, though.
First, here's the pertinent part of the view:
myView
.onDrag {
NSItemProvider(object: MyDragProvider("dragged text"))
}
Simple enough. MyDragProvider
is where the work needs to get done for now:
class TestDragProvider: NSObject, NSItemProviderWriting {
var stringValue: String
init(_ value: String) {
self.stringValue = value
}
static var writableTypeIdentifiersForItemProvider: [String] {
let utTypes: [UTType] = [
myCustomUTType,
.fileUrl
]
return utTypes.map { $0.identifier }
}
func loadData(
withTypeIdentifier typeIdentifier: String,
forItemProviderCompletionHandler completionHandler: @escaping @Sendable (Data?, Error?) -> Void
) -> Progress? {
let progress = Progress(totalUnitCount: 1)
var data: Data? = nil
print("Loading data for type: \(typeIdentifier)")
if let utType = UTType(typeIdentifier) {
if utType == myCustomUTType {
data = stringValue.data(using: .utf8)
} else if utType == .fileUrl {
let urlOfSomeFile = FileManager
.default
.urls(for: .cachesDirectory, in: .userDomainMask)[0]
.appendingPathComponent("fileToDrag.txt")
.absoluteURL
data = try! NSKeyedArchiver.archivedData(withRootObject: (urlOfSomeFile as NSURL), requiringSecureCoding: false)
}
}
progress.completedUnitCount = data != nil ? 1 : 0
completionHandler(data, nil)
return progress
}
This works fine when dragging items into the other part of the app that receives drags of this type (the print statement outputs Loading data for type: myCustomUTType
), but when I try to drag outside of the app, the console puts out this:
NSURLs written to the pasteboard via NSPasteboardWriting must be absolute URLs. NSURL 'bplist00%C3%94%01%02%03%04%05%06%07%0AX$versionY$archiverT$topX$objects%12%00%01%C2%86%C2%A0_%10%0FNSKeyedArchiver%C3%91 ... 00%01%1B' is not an absolute URL.
[sandbox] CreateSandboxExtensionData failed: urlData: 0x600000be9640 length: 0 (-1 means null)
[general] Sandbox extension data required immediately for flavor public.file-url, but failed to obtain.
[sandbox] CreateSandboxExtensionData failed: urlData: 0x600000be9640 length: 0 (-1 means null)
[sandbox] Failed to get a sandbox extension
I guess the [sandbox]
and [general]
errors are about permissions, but the first error about absolute URLs is confounding. I've tried several variations for that, including adding or removing the .absoluteURL
on the urlOfSomeFile
, and switching the NSKeyedArchiver
to a JSONEnconder
.
Anybody else know how to get past this?
Have you tried asking the URL
for its dataRepresentation
?
data = urlOfSomeFile.dataRepresentation
I tested the following Xcode Playground and dragging to the Finder does what I expect (moves fileToDrag.txt
to the desktop):
import SwiftUI
import UniformTypeIdentifiers
class MyDragProvider: NSObject, NSItemProviderWriting {
static var writableTypeIdentifiersForItemProvider: [String] {
return [UTType.fileURL].map(\.identifier)
}
func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
switch typeIdentifier {
case UTType.fileURL.identifier:
let urlOfSomeFile = FileManager
.default
.urls(for: .cachesDirectory, in: .userDomainMask)[0]
.appendingPathComponent("fileToDrag.txt")
.absoluteURL
try! String("hello world").write(to: urlOfSomeFile, atomically: true, encoding: .utf8)
let data = urlOfSomeFile.dataRepresentation
completionHandler(data, nil)
return nil
default:
fatalError()
}
}
}
import PlaygroundSupport
PlaygroundPage.current.setLiveView(
Text("Drag me")
.padding()
.onDrag {
NSItemProvider(object: MyDragProvider())
}
)