iOS Development
Drag and Drop
Mobile App Design
Swift Programming
Apple SDK

Basic Drag and Drop in iOS

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

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

swift
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

swift
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

swift
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

swift
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

swift
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

swift
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+)

Course illustration
Course illustration

All Rights Reserved.