09
Oct
2022
SwiftUI Drag & Drop
Reading time: 2 min
This recipe shows how to implement drag & drop functionality in SwiftUI.
The end result looks like this:
This code works starting with SwiftUI 3 (iOS 15, macOS 12).
Drag & drop in SwiftUI works somewhat out of the box, as long as you're acquanited with UTType
s you wish to handle. The recipe goes like this:
- The draggable view should have a
onDrag
modifier to provide aNSItemProvider
instance. This wrapper represents the data you want to provide to the drop location.- You can optionally provide a custom
preview
, which will be shown during the drag.
- You can optionally provide a custom
- The drop location should have a
onDrop
modifier. There, you need to provide:- List of valid
UTType
s for this drop location. Only drags that contain valid data will be accepted in the drop handler. - Either a
DropDelegate
reference or a handler that is called when the drop happens and allows you to make use of the droppedNSItemProvider
s. Additionally, if you use the handler variant, you can specify aBool
binding that indicates if there's an active drop above the location or not. The handler variant is all you need most of the time.
- List of valid
Here's some code that shows all of that in action:
import SwiftUI
import UniformTypeIdentifiers
struct DragDropTest: View {
@State private var textInput = ""
@State private var items = ["Apple", "Orange", "Kiwi", "Pear"]
@State private var dragInProgress = false
var body: some View {
VStack(alignment: .leading) {
TextField("New fruit", text: $textInput)
.onDrag {
NSItemProvider(object: textInput as NSString)
} preview: {
Label("Add new fruit!", systemImage: "applelogo")
}
Text("Fruit")
.padding(.top, 30)
HStack {
ForEach(items, id: \.self) { fruit in
Text(fruit)
}
}
.background(dragInProgress ? Color.orange : nil)
.onDrop(of: [UTType.plainText], isTargeted: $dragInProgress) { providers in
for item in providers {
item.loadObject(ofClass: NSString.self) { item, error in
if let str = item as? String {
items.append(str)
}
}
}
return true
}
}
}
}
For the sake of completeness, here's the same code, but using DropDelegate
:
import SwiftUI
import UniformTypeIdentifiers
struct DragDropTest: View, DropDelegate {
@State private var textInput = ""
@State private var items = ["Apple", "Orange", "Kiwi", "Pear"]
@State private var dragInProgress = false
var body: some View {
VStack(alignment: .leading) {
TextField("New fruit", text: $textInput)
.onDrag {
NSItemProvider(object: textInput as NSString)
} preview: {
Label("Add new fruit!", systemImage: "applelogo")
}
Text("Fruit")
.padding(.top, 30)
HStack {
ForEach(items, id: \.self) { fruit in
Text(fruit)
}
}
.background(dragInProgress ? Color.orange : nil)
.onDrop(of: [UTType.plainText], delegate: self)
Spacer()
}
.padding()
}
func dropEntered(info: DropInfo) {
dragInProgress = info.hasItemsConforming(to: [UTType.plainText])
}
func dropExited(info: DropInfo) {
dragInProgress = false
}
func performDrop(info: DropInfo) -> Bool {
for item in info.itemProviders(for: [UTType.plainText]) {
item.loadObject(ofClass: NSString.self) { item, error in
if let str = item as? String {
items.append(str)
}
}
}
dragInProgress = false
return true
}
}
The drag functionality doesn't work in XCode previews - you need to run the code on either a simulator or a real device.