Introduction
Drag and drop in iOS uses UIDragInteraction and UIDropInteraction (or built-in support in UITableView and UICollectionView) to let users move content within an app or between apps on iPad. The drag source provides items via UIDragInteractionDelegate, and the drop target receives them via UIDropInteractionDelegate. Data is transferred using NSItemProvider, which supports strings, URLs, images, and custom types. On iPhone, drag and drop works only within the same app.
Basic Drag and Drop on a Custom View
1class ViewController: UIViewController {
2
3 let sourceLabel = UILabel()
4 let targetLabel = UILabel()
5
6 override func viewDidLoad() {
7 super.viewDidLoad()
8
9 sourceLabel.text = "Drag me!"
10 sourceLabel.isUserInteractionEnabled = true
11 sourceLabel.frame = CGRect(x: 50, y: 100, width: 200, height: 50)
12
13 targetLabel.text = "Drop here"
14 targetLabel.backgroundColor = .systemGray6
15 targetLabel.frame = CGRect(x: 50, y: 300, width: 200, height: 50)
16
17 view.addSubview(sourceLabel)
18 view.addSubview(targetLabel)
19
20 // Add drag interaction to source
21 let dragInteraction = UIDragInteraction(delegate: self)
22 sourceLabel.addInteraction(dragInteraction)
23
24 // Add drop interaction to target
25 let dropInteraction = UIDropInteraction(delegate: self)
26 targetLabel.addInteraction(dropInteraction)
27 }
28}
Drag Delegate
1extension ViewController: UIDragInteractionDelegate {
2
3 func dragInteraction(_ interaction: UIDragInteraction,
4 itemsForBeginning session: UIDragSession) -> [UIDragItem] {
5 guard let text = sourceLabel.text else { return [] }
6
7 let itemProvider = NSItemProvider(object: text as NSString)
8 let dragItem = UIDragItem(itemProvider: itemProvider)
9 dragItem.localObject = text // For in-app fast access
10 return [dragItem]
11 }
12
13 // Optional: custom preview
14 func dragInteraction(_ interaction: UIDragInteraction,
15 previewForLifting item: UIDragItem,
16 session: UIDragSession) -> UITargetedDragPreview? {
17 guard let view = interaction.view else { return nil }
18 let parameters = UIDragPreviewParameters()
19 parameters.backgroundColor = .clear
20 return UITargetedDragPreview(view: view, parameters: parameters)
21 }
22}
itemsForBeginning provides the data to drag. Wrapping the data in NSItemProvider allows the system to handle serialization. localObject provides fast access when drag and drop happens within the same app.
Drop Delegate
1extension ViewController: UIDropInteractionDelegate {
2
3 // Indicate that the view can accept the drop
4 func dropInteraction(_ interaction: UIDropInteraction,
5 canHandle session: UIDropSession) -> Bool {
6 return session.canLoadObjects(ofClass: NSString.self)
7 }
8
9 // Specify the drop operation (copy, move, etc.)
10 func dropInteraction(_ interaction: UIDropInteraction,
11 sessionDidUpdate session: UIDropSession) -> UIDropProposal {
12 return UIDropProposal(operation: .copy)
13 }
14
15 // Handle the dropped data
16 func dropInteraction(_ interaction: UIDropInteraction,
17 performDrop session: UIDropSession) {
18 session.loadObjects(ofClass: NSString.self) { items in
19 if let text = items.first as? String {
20 DispatchQueue.main.async {
21 self.targetLabel.text = text
22 }
23 }
24 }
25 }
26}
Three delegate methods handle the drop: canHandle checks if the data type is accepted, sessionDidUpdate returns the visual indicator (copy/move/cancel), and performDrop processes the received data.
UITableView Drag and Drop
1class TableViewController: UITableViewController {
2
3 var items = ["Apple", "Banana", "Cherry", "Date", "Elderberry"]
4
5 override func viewDidLoad() {
6 super.viewDidLoad()
7 tableView.dragDelegate = self
8 tableView.dropDelegate = self
9 tableView.dragInteractionEnabled = true // Required on iPhone
10 }
11}
12
13extension TableViewController: UITableViewDragDelegate {
14
15 func tableView(_ tableView: UITableView,
16 itemsForBeginning session: UIDragSession,
17 at indexPath: IndexPath) -> [UIDragItem] {
18 let item = items[indexPath.row]
19 let itemProvider = NSItemProvider(object: item as NSString)
20 let dragItem = UIDragItem(itemProvider: itemProvider)
21 dragItem.localObject = item
22 return [dragItem]
23 }
24}
25
26extension TableViewController: UITableViewDropDelegate {
27
28 func tableView(_ tableView: UITableView,
29 performDropWith coordinator: UITableViewDropCoordinator) {
30 let destinationIndexPath = coordinator.destinationIndexPath ?? IndexPath(row: items.count, section: 0)
31
32 for item in coordinator.items {
33 if let sourceIndexPath = item.sourceIndexPath {
34 // Reorder within the same table
35 tableView.performBatchUpdates {
36 let movedItem = items.remove(at: sourceIndexPath.row)
37 items.insert(movedItem, at: destinationIndexPath.row)
38 tableView.moveRow(at: sourceIndexPath, to: destinationIndexPath)
39 }
40 coordinator.drop(item.dragItem, toRowAt: destinationIndexPath)
41 }
42 }
43 }
44
45 func tableView(_ tableView: UITableView,
46 dropSessionDidUpdate session: UIDropSession,
47 withDestinationIndexPath destinationIndexPath: IndexPath?) -> UITableViewDropProposal {
48 if session.localDragSession != nil {
49 return UITableViewDropProposal(operation: .move, intent: .insertAtDestinationIndexPath)
50 }
51 return UITableViewDropProposal(operation: .copy, intent: .insertAtDestinationIndexPath)
52 }
53}
UITableView and UICollectionView have built-in drag and drop support through specialized delegates. Set dragInteractionEnabled = true on iPhone (it defaults to true on iPad).
Dragging Images
1// Drag delegate for an image view
2extension ViewController: UIDragInteractionDelegate {
3
4 func dragInteraction(_ interaction: UIDragInteraction,
5 itemsForBeginning session: UIDragSession) -> [UIDragItem] {
6 guard let image = imageView.image else { return [] }
7
8 let itemProvider = NSItemProvider(object: image)
9 let dragItem = UIDragItem(itemProvider: itemProvider)
10 dragItem.localObject = image
11 return [dragItem]
12 }
13}
14
15// Drop delegate to receive images
16extension ViewController: UIDropInteractionDelegate {
17
18 func dropInteraction(_ interaction: UIDropInteraction,
19 canHandle session: UIDropSession) -> Bool {
20 return session.canLoadObjects(ofClass: UIImage.self)
21 }
22
23 func dropInteraction(_ interaction: UIDropInteraction,
24 performDrop session: UIDropSession) {
25 session.loadObjects(ofClass: UIImage.self) { items in
26 if let image = items.first as? UIImage {
27 DispatchQueue.main.async {
28 self.targetImageView.image = image
29 }
30 }
31 }
32 }
33}
SwiftUI Drag and Drop
1struct ContentView: View {
2 @State private var items = ["Apple", "Banana", "Cherry"]
3 @State private var droppedText = "Drop here"
4
5 var body: some View {
6 VStack(spacing: 40) {
7 ForEach(items, id: \.self) { item in
8 Text(item)
9 .padding()
10 .background(Color.blue.opacity(0.2))
11 .draggable(item) // iOS 16+
12 }
13
14 Text(droppedText)
15 .padding()
16 .frame(maxWidth: .infinity)
17 .background(Color.gray.opacity(0.2))
18 .dropDestination(for: String.self) { items, location in
19 droppedText = items.first ?? droppedText
20 return true
21 }
22 }
23 }
24}
SwiftUI uses .draggable() and .dropDestination() modifiers (iOS 16+) for a declarative drag and drop API.
Common Pitfalls
Forgetting dragInteractionEnabled on iPhone: On iPhone, UITableView and UICollectionView have drag interaction disabled by default. Set dragInteractionEnabled = true explicitly. iPad enables it by default.
Not handling localObject for in-app drops: NSItemProvider loading is asynchronous and designed for cross-app transfers. For in-app drag and drop, use dragItem.localObject for instant synchronous access to the data.
UI updates off the main thread: loadObjects(ofClass:) and NSItemProvider callbacks execute on a background thread. Always dispatch UI updates to the main queue with DispatchQueue.main.async.
Missing canHandle returning false: If canHandle returns false or is not implemented, the drop target shows a "no entry" icon and the drop never fires. Verify the data types match between drag source and drop target.
Cross-app drag not working on iPhone: iPhone restricts drag and drop to within the same app (no multi-window). Cross-app drag and drop only works on iPad in Split View or Slide Over mode.
Summary
Use UIDragInteraction and UIDropInteraction with delegates for custom views
UITableView and UICollectionView have built-in drag/drop delegate protocols
Data is transferred via NSItemProvider — supports strings, images, URLs, and custom types
Use localObject for fast synchronous access in same-app drag and drop
Set dragInteractionEnabled = true on iPhone (enabled by default on iPad)
SwiftUI uses .draggable() and .dropDestination() modifiers (iOS 16+)