← Back to list

Drag & Drop in SwiftUI and UIKit

Data Transfer vs Reordering — Using the Right Tool for the Right Job

Gaye Uğur · 2026-01-13 22:06 · 2 claps · 3.8 min read
#drag #drop #swift #ios #onmove
Open on Medium ↗
Wiki topics: RAG · RAG & Retrieval 📱 · Mobile Development

Drag & Drop in SwiftUI and UIKit

Data Transfer vs Reordering — Using the Right Tool for the Right Job

With SwiftUI, Apple introduced a much simpler and more declarative way to work with Drag & Drop interactions that have existed in UIKit for years. However, despite the cleaner API, it is crucial to distinguish between Drag & Drop as a data-transfer mechanism and reordering (rearranging UI elements).

In this article, we will clearly explain what Drag & Drop does, what it does not do, and when SwiftUI or UIKit should be preferred.

What Is Drag & Drop?

Drag & Drop is an interaction model that allows users to move content:

  • within the same screen
  • between different parts of the same app
  • between different apps

Examples:

  • Dragging a URL from Safari into Notes
  • Moving an item from one list to another
  • Dragging an image into a canvas or drop zone

📌 Important:

Drag & Drop moves data, not layout.

Drag in SwiftUI (onDrag)

In SwiftUI, a view becomes draggable by using the onDrag modifier.

Text("Drag Me")
    .padding()
    .background(.blue)
    .foregroundColor(.white)
    .onDrag {
        NSItemProvider(object: "Hello SwiftUI" as NSString)
    }

This code:

  • Marks the view as draggable
  • Defines the transferred data using NSItemProvider

What Is NSItemProvider?

NSItemProvider is the core of Drag & Drop. It defines:

  • The type of the dragged data
  • How the system shares that data across apps

It works the same way in SwiftUI and UIKit.

Drop in SwiftUI (onDrop)

For receiving dragged content, SwiftUI provides onDrop.

.onDrop(of: ["public.text"], isTargeted: nil) { providers in
    return true
}

A Working Text Drop Example

struct ContentView: View {
    @State private var droppedText = "Nothing dropped yet"

    var body: some View {
        VStack(spacing: 30) {
            Text("Drag Me")
                .padding()
                .background(.blue)
                .foregroundColor(.white)
                .onDrag {
                    NSItemProvider(object: "SwiftUI Drag & Drop" as NSString)
                }

            Text(droppedText)
                .frame(width: 300, height: 120)
                .background(.gray.opacity(0.3))
                .onDrop(of: ["public.text"], isTargeted: nil) { providers in
                    providers.first?.loadObject(ofClass: String.self) { value, _ in
                        if let value {
                            DispatchQueue.main.async {
                                droppedText = value
                            }
                        }
                    }
                    return true
                }
        }
        .padding()
    }
}

This example:

  • Is stable
  • Works as expected
  • Represents the correct SwiftUI Drag & Drop usage

⚠️ Why SwiftUI Cannot Reorder Items Using Drag & Drop

This point must be very clear.

Short answer:

*onDrag + onDrop in SwiftUI is **not designed for reordering lists***

Technical reason:

  • SwiftUI fully controls List and ForEach layout
  • Drag & Drop only transfers data, not layout intent
  • SwiftUI does not automatically compute:
  • Which item moved where
  • Which index changed

So:

Drag happens → Drop happens ❌ But the list order does not update automatically

This is not a bug — it is a design decision.

The Correct Way to Reorder in SwiftUI: onMove

SwiftUI provides a dedicated API for reordering lists: onMove.

List {
    ForEach(items) { item in
        Text(item.title)
    }
    .onMove(perform: move)
}
.toolbar {
    EditButton()
}
func move(from source: IndexSet, to destination: Int) {
    items.move(fromOffsets: source, toOffset: destination)
}

What onMove Provides

  • System-supported behavior
  • Built-in animations
  • Works on both iPhone and iPad
  • Stable and officially recommended

📌 Apple’s intended separation:

List reordering → onMove Data transfer → Drag & Drop

When Does Drag & Drop Make Sense in SwiftUI?

SwiftUI Drag & Drop is ideal for:

  • Moving items between lists
  • Dragging data across screens
  • Inter-app data transfer
  • Canvas or grid-style UIs

It is not intended for list reordering.

Why UIKit Is Stronger for Reordering

In UIKit, you have full control using:

  • UICollectionViewDragDelegate
  • UICollectionViewDropDelegate
  • UIDragItem.localObject
  • Diffable Data Source snapshots

This allows:

  • True drag-to-reorder
  • IndexPath-based control
  • Restricting movement of certain cells
  • Safe and efficient snapshot updates

That’s why:

If drag-based reordering is required, UIKit is the better choice

UIKit — Diffable CollectionView + Drag & Drop Example

Model

struct Icon: Hashable {
    let id = UUID()
    var name: String = ""
    var price: Double = 0.0
    var isFeatured: Bool = false

    init(name: String, price: Double, isFeatured: Bool) {
        self.name = name
        self.price = price
        self.isFeatured = isFeatured
    }
}

Section

enum Section {
    case all
}

ViewController

class IconCollectionViewController: UICollectionViewController {

    private lazy var dataSource = configureDataSource()

    private var iconSet: [Icon] = [ Icon(name: "candle", price: 3.99, isFeatured: false),
                                    Icon(name: "cat", price: 2.99, isFeatured: true),
                                    Icon(name: "dribbble", price: 1.99, isFeatured: false),
                                    Icon(name: "ghost", price: 4.99, isFeatured: false),
                                    Icon(name: "hat", price: 2.99, isFeatured: false),
                                    Icon(name: "owl", price: 5.99, isFeatured: true)]


    override func viewDidLoad() {
        super.viewDidLoad()

        // Configure the layout and item size
        if let layout = collectionViewLayout as? UICollectionViewFlowLayout {
            layout.itemSize = CGSize(width: 100, height: 150)
            layout.estimatedItemSize = .zero
            layout.minimumInteritemSpacing = 10
        }

        collectionView.dataSource = dataSource
        updateSnapshot()

        collectionView.dragInteractionEnabled = true
        collectionView.dragDelegate = self
        collectionView.dropDelegate = self

    }

}

Diffable DataSource

    func configureDataSource() -> UICollectionViewDiffableDataSource<Section, Icon> {

        let dataSource = UICollectionViewDiffableDataSource<Section, Icon>(collectionView: collectionView) { (collectionView, indexPath, icon) -> UICollectionViewCell? in

            let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! IconCollectionViewCell
            cell.iconImageView.image = UIImage(named: icon.name)
            cell.iconPriceLabel.text = "$\(icon.price)"

            return cell
        }

        return dataSource
    }

    func updateSnapshot(animatingChange: Bool = false) {

        var snapshot = NSDiffableDataSourceSnapshot<Section, Icon>()
        snapshot.appendSections([.all])
        snapshot.appendItems(iconSet, toSection: .all)

        dataSource.apply(snapshot, animatingDifferences: false)
    }

Drag Delegate

extension IconCollectionViewController: UICollectionViewDragDelegate {

    func collectionView(
        _ collectionView: UICollectionView,
        itemsForBeginning session: UIDragSession,
        at indexPath: IndexPath
    ) -> [UIDragItem] {

        let icon = iconSet[indexPath.item]
        let provider = NSItemProvider(object: icon.id.uuidString as NSString)
        let item = UIDragItem(itemProvider: provider)
        item.localObject = icon
        return [item]
    }
}

Drop Delegate (Reorder)

extension IconCollectionViewController: UICollectionViewDropDelegate {

    func collectionView(
        _ collectionView: UICollectionView,
        performDropWith coordinator: UICollectionViewDropCoordinator
    ) {
        guard
            let source = coordinator.items.first?.sourceIndexPath,
            let destination = coordinator.destinationIndexPath
        else { return }

        let moved = iconSet.remove(at: source.item)
        iconSet.insert(moved, at: destination.item)

        updateSnapshot()
    }

    func collectionView(
        _ collectionView: UICollectionView,
        dropSessionDidUpdate session: UIDropSession,
        withDestinationIndexPath destinationIndexPath: IndexPath?
    ) -> UICollectionViewDropProposal {

        if collectionView.hasActiveDrag {
            return UICollectionViewDropProposal(
                operation: .move,
                intent: .insertAtDestinationIndexPath
            )
        } else {
            return UICollectionViewDropProposal(operation: .forbidden)
        }
    }

}

Summary

SwiftUI Drag & Drop:

  • Solves data transfer, not layout control

UIKit:

  • Combines Drag & Drop with full layout management

메타데이터
post_id
68514101a8ff
slug
drag-drop-in-swiftui-and-uikit-68514101a8ff
url
https://medium.com/@gayeugur/drag-drop-in-swiftui-and-uikit-68514101a8ff
canonical_url
https://medium.com/@gayeugur/drag-drop-in-swiftui-and-uikit-68514101a8ff
author_url
https://medium.com/@gayeugur
status
ok
fetched_at
2026-06-15 20:49:13