Drag & Drop in SwiftUI and UIKit
Data Transfer vs Reordering — Using the Right Tool for the Right Job
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 + onDropin SwiftUI is **not designed for reordering lists***
Technical reason:
- SwiftUI fully controls
ListandForEachlayout - 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 →
onMoveData 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:
UICollectionViewDragDelegateUICollectionViewDropDelegateUIDragItem.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