Reputation: 1269
I want to register multiple Transferable
representations for an draggable item so it can be used for internal drag & drop (drag the item from one place within the app to another) and for external drag & drop to the Finder (e.g. user Desktop).
When the item is dragged internally, I basically just need the information which item was dropped at a certain location. But when dragging the item to the Finder, I need to perform a network operation to actually download the item to a file or when dropping to my App I need to upload the file.
I tried multiple things already but none of them worked so far. My expectation was that I could just register multiple transfer representations for my item, and based on the order they would be prioritised. But it turns out this is not the case. Only the first representation is taken into account, and the others are ignored. I also tried to set the .visibility(_)
of the representations, but it has no effect. Also ordering them differently does not help.
This is one example what I have tried:
struct Item: Codable, Transferable {
let url: URL
let isExternal = false
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(exportedContentType: .data) { item in
return try await download(url: item.url)
}
FileRepresentation(importedContentType: .fileURL) { transferredFile in
// Need to upload item later, so marking it as 'isExternal'
return Item(url: transferredFile.file, isExternal: true)
}
CodableRepresentation(contentType: .custom)
.visibility(.ownProcess)
}
}
extension UTType {
static var custom = UTType(exportedAs: "com.exmaple.custom")
}
let items = [...]
var body: some View {
List {
ForEach(items, id: \.self) { item in
ItemView(item: item)
.draggable(item)
}
}
.dropDestination(for: Item.self) { items, _ in
print(items)
}
}
I also tried replacing the Transferable
protocol with NSItemProvider
API by using the .itemProvider
and .onDrop
view modifiers, but it also does not work. When I registered multiple representations on the NSItemProvider
, only the first one is taken into account. Also e.g. when receiving items from an internal drop operation in the .onDrop(of: [.fileURL, .custom], isTargeted: $isTargeted)
callback, the passed NSItemProvider
instances have no registered types for .custom
. It only works when I have only the CodableRepresentation
, or if the CodableRepresentation
comes first. But then dragging to the Finder does not work anymore.
Note that I cannot use a single representation due to the fact that externally I need to download / upload the item in addition. I even tried to get around this limitation by having a global internalDrop
variable which indicates to me if it's an internal or external drag & drop operation. But here I face the problem that due to the download of the file for the external drop, I cannot use the content type .fileURL
, as the OS will otherwise eagerly call the Transferable
callback once my mouse moves to the Desktop, and not once the drag finished (as described in this question). So I can only fallback to supertypes of .fileURL
, which are .data
, .url
and .item
. But .url
has the same behaviour as .fileURL
, an .item
is not accepted by the Finder and .data
returns me not the URL of the dragged file, but its binary content instead (which is not acceptable in case the dragged files are many and of large size):
static var transferRepresentation: some TransferRepresentation {
// When '.fileURL' or '.url' is used as a contentType, the callback will be
// called too early (already when the mouse moves over the Desktop)
// When using ' .item' or any other type, the Finder does not accept the dragged item
DataRepresentation(contentType: .data) { item in
if internalDrop {
return try JSONEncoder().encode(item)
}
return try await download(url: item.url)
} importing: { data in
if internalDrop {
return try JSONDecoder().decode(Item.self, from: data)
}
// When '.data' is used as content type, this does not work as the file
// binary content is returned instead of the file URL
guard
let path = String(data: data, encoding: .utf8),
let url = URL(string: path as String)
else {
throw ItemTransferError.missingURL
}
return Item(url: url, isExternal: true)
}
}
I have the feeling this is a bug in the implementation of NSItemProvider
, or do I miss something here? How can I make it work so that I can drag & drop my item both internally and externally at the same time?
Upvotes: 1
Views: 88