I'm using the new Transferable protocol with the draggable/dropDestination modifiers to let users drop content onto a ZStack. The issue I'm having is that I want to support multiple Transferable types being dropped into a single container. For example, I want users to be able to drop a String, a URL, or a Data (i.e., image data) onto a single ZStack. The problem is that the "for" parameter on the dropDestination view modifier does not accept multiple Types, like the onDrop modifier does.
I tried adding a second dropDestination modifier with a different payload, but when I drop an item corresponding to the second drop destination payload, I see an icon on the dragged image that indicates dropping is not allowed. However, if I drop a String payload, I get the + icon as I would expect, and the drop is successful.
struct ContentView: View {
@State private var stringPayload: String = ""
@State private var urlPayload: URL?
var body: some View {
VStack {
ZStack {
Color.yellow
Text(stringPayload)
if let urlPayload {
Image(uiImage: UIImage(data: (try? Data(contentsOf: urlPayload))!)!)
}
}
.dropDestination(for: String.self) { items, location in
stringPayload = items.first!
return true
}
.dropDestination(for: URL.self) { items, location in
return true
}
Text("Hello world!")
.draggable("Hello world!")
}
}
}
Thanks to @user1046037's suggestion to look at ProxyRepresentation, I was able to put together some code that allows me to accept multiple drop types within a single dropDestination receiver.
First, I created a separate enum to represent the different types of data that could be dropped:
import CoreTransferable
enum DropItem: Codable, Transferable {
case none
case text(String)
case url(URL)
static var transferRepresentation: some TransferRepresentation {
ProxyRepresentation { DropItem.text($0) }
ProxyRepresentation { DropItem.url($0) }
}
var text: String? {
switch self {
case .text(let str): return str
default: return nil
}
}
var url: URL? {
switch self {
case.url(let url): return url
default: return nil
}
}
}
Then in the View, just tell the dropDestination to accept items of type DropItem.self, like this:
struct ContentView: View {
@State private var payload: DropItem = .none
@State private var urlPayload: URL?
var body: some View {
VStack {
ZStack {
Color.yellow
if let text = payload.text {
Text(text)
} else if let url = payload.url {
Text(url.absoluteString)
}
}
.dropDestination(for: DropItem.self) { items, location in
payload = items.first!
return true
}
Text("Hello world!")
.draggable("Hello world!")
}
}
}
What's really nice about this approach is that you can rely on strongly typed DropItems to determine what to do based on the case in the enum that is received.