DragAndDrop
Simple drag and drop implementation with custom objects in iOS 14.
Demo
Making your custom type draggable
- Start with a
Codable
object that you want to drag and drop.
struct Bird: Codable {
let name: String
}
- Add your custom object info to your Project
Project > Target > Info > Exported Type Identifiers
-
Add
Providable.swift
to your project. -
Add an extension that conforms your object to
Providable
-
Add a new final class
Wrapper
that inherits fromNSObject
and conforms toProvidableWrapper
extension Bird: Providable {
final class Wrapper: NSObject, ProvidableWrapper {
/// Add properties here
}
}
- Add typealias, item and required init to
Wrapper
typealias Item = Bird
let item: Item
required init(_ item: Item) {
self.item = item
super.init()
}
- Add a type name and a custom
UTType
to match your exported type identifier.
static var name = "bird"
static var uti = UTType("com.yourname.yourappname.bird") ?? .data
- Add arrays of writable and readable types as
UTType
static var writableTypes: [UTType] {
[uti]
}
static var readableTypes: [UTType] {
[uti, UTType.plainText]
}
- Add arrays of identifier strings computed from the
UTType
arrays.
static var writableTypeIdentifiersForItemProvider: [String] {
writableTypes.map(\.identifier)
}
static var readableTypeIdentifiersForItemProvider: [String] {
readableTypes.map(\.identifier)
}
- Add functions to transform the type to and from
NSItemProvider
func loadData(withTypeIdentifier typeIdentifier: String, forItemProviderCompletionHandler completionHandler: @escaping @Sendable (Data?, Error?) -> Void) -> Progress? {
do {
switch typeIdentifier {
case Self.uti.identifier:
let data = try JSONEncoder().encode(item)
completionHandler(data, nil)
default:
throw DecodingError.valueNotFound(Bird.self, .init(codingPath: [], debugDescription: "No Birds"))
}
} catch {
completionHandler(nil, error)
}
return Progress(totalUnitCount: 100)
}
static func object(withItemProviderData data: Data, typeIdentifier: String) throws -> Self {
switch typeIdentifier {
case Self.uti.identifier:
let bird = try JSONDecoder().decode(Bird.self, from: data)
return .init(bird)
case UTType.plainText.identifier:
let string = String(decoding: data, as: UTF8.self)
let bird = Bird(name: string)
return .init(bird)
default:
throw DecodingError.valueNotFound(Bird.self, .init(codingPath: [], debugDescription: "No Birds"))
}
}
Adding drag and drop to your SwiftUI views
Once your type conforms to Providable
, adding SwiftUI drag and drop modifiers is easy!
onDrag
.onDrag { bird.provider }
onDrop
.onDrop(of: Bird.Wrapper.readableTypes, isTargeted: nil) { providers, location in
providers.reversed().loadItems(Bird.self) { bird, error in
if let bird {
birds.append(bird)
}
}
return true
}
onInsert(of:)
.onInsert(of: Bird.Wrapper.readableTypes) { index, providers in
providers.reversed().loadItems(Bird.self) { bird, error in
if let bird {
birds.insert(bird, at: index)
}
}
}