Skip to content

Instantly share code, notes, and snippets.

@jmcd
Created February 14, 2020 18:21
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jmcd/ffd9be37fc48331b1d20a484d81bc0ee to your computer and use it in GitHub Desktop.
Save jmcd/ffd9be37fc48331b1d20a484d81bc0ee to your computer and use it in GitHub Desktop.
Checking out drag n' drop in Swift UI
import SwiftUI
struct AnimalView: View {
var model: Animal
var body: some View {
VStack {
Image(systemName: model.systemImageName)
.resizable()
.scaledToFit()
.frame(width: 50, height: 50)
Text(model.name)
}
.onDrag {
NSItemProvider(object: self.model)
}
}
}
struct TrashView: View {
@EnvironmentObject var zoo: Zoo
var body: some View {
Image(systemName: "trash")
.resizable()
.scaledToFit()
.frame(width: 100, height: 100)
.onDrop(of: [Animal.typeIdentifier], isTargeted: nil) { ips in
guard let ip = ips.first(where: { ip in ip.hasItemConformingToTypeIdentifier(Animal.typeIdentifier) }) else { return false }
ip.loadObject(ofClass: Animal.self) { reading, _ in
guard let animal = reading as? Animal else { return }
DispatchQueue.main.async {
self.zoo.amimals.removeAll(where: { a in a.name == animal.name })
}
}
return true
}
}
}
struct ContentView: View {
@ObservedObject var zoo = Zoo()
var body: some View {
HStack {
Spacer()
VStack {
ForEach(zoo.amimals, id: \.name) { animal in
AnimalView(model: animal)
}
}
Spacer()
TrashView().environmentObject(zoo)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
// MARK: - Model
class Zoo: ObservableObject {
@Published var amimals = [
Animal(name: "Hare", systemImageName: "hare.fill"),
Animal(name: "Ant", systemImageName: "ant.fill"),
Animal(name: "Tortoise", systemImageName: "tortoise.fill"),
]
}
final class Animal: NSObject {
public static let typeIdentifier = "dnd.animal"
let name: String
let systemImageName: String
internal init(name: String, systemImageName: String) {
self.name = name
self.systemImageName = systemImageName
}
}
extension Animal: NSItemProviderReading {
static var readableTypeIdentifiersForItemProvider: [String] = [typeIdentifier]
static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> Animal {
let components = String(data: data, encoding: .utf8)!.split(separator: ",").map(String.init)
return Animal(name: components[0], systemImageName: components[1])
}
}
extension Animal: NSItemProviderWriting {
static var writableTypeIdentifiersForItemProvider: [String] = [typeIdentifier]
func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping (Data?, Error?) -> Void) -> Progress? {
completionHandler("\(name),\(systemImageName)".data(using: .utf8), nil) // very terrible encoding and decoding 🙃
let p = Progress(totalUnitCount: 1)
p.completedUnitCount = 1
return p
}
}
@juandahurt
Copy link

I was looking for an implementation like this! Thanks for sharing!

@hj-michal-jendrzejewski
Copy link

Thanks man, you've helped me a lot 👍

@andy-thomas
Copy link

I copied and pasted the code as-is and ran it. However, it does not work. The guard at line 32 fails because the ips array is empty. Can you please advise?
guard let ip = ips.first(where:... <-- ips is empty

@juandahurt
Copy link

I copied and pasted the code as-is and ran it. However, it does not work. The guard at line 32 fails because the ips array is empty. Can you please advise?
guard let ip = ips.first(where:... <-- ips is empty

What if you try to do an if let ip = ips.first...

@andy-thomas
Copy link

Hi juandahurt, Thanks for the reply.
As it turns out, I came back to the project after a couple of days, and now the "ips" parameter variable is populated correctly and the code works. I did not change any code, so I cannot explain why it was not working earlier. All good now. :-)

@marktrobinson
Copy link

To any future people reading this, this example doesn't work building for macOS Big Sur, Xcode 12.5. You have to add "dnd.animal" as a UTType in your info.plist, you can see how to do that here.

Alternatively, you can just remove the "dnd.animal" and just swap it with a different UTType, I used "UTType.data" and everything worked ok

static let typeIdentifier = UTType.data
static var readableTypeIdentifiersForItemProvider: [String] = [typeIdentifier.identifier]
static var writableTypeIdentifiersForItemProvider: [String] = [typeIdentifier.identifier]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment