June 9, 2026
iOS 27 Brings Native Drag-to-Reorder to Any SwiftUI Layout
For a long time, drag-to-reorder in SwiftUI was effectively a List feature. If you needed it in a grid, a custom stack, or any layout that wasn’t a list, you’d end up writing your own gesture handling. It worked, but it was a lot of code for something that should be simple.
iOS 27 adds two new modifiers that bring reordering to any SwiftUI container: .reorderable() and .reorderContainer(for:move:).
Availability
Both modifiers require iOS 27 or later. The SwiftUI API itself needs no extra imports.
Setting Up the Model
Your data type needs to conform to Identifiable, with an id that’s both Hashable and Sendable. UUID covers both:
struct Song: Identifiable {
let id: UUID
var title: String
var artist: String
}
Making Items Reorderable
There are two parts: mark the items, then define the container.
Add .reorderable() to the ForEach inside your layout, and .reorderContainer(for:move:) to the enclosing view:
struct SongRow: View {
let song: Song
var body: some View {
HStack(spacing: 16) {
Image(systemName: "music.note")
.font(.title3)
.foregroundStyle(.tint)
.frame(width: 24)
VStack(alignment: .leading, spacing: 2) {
Text(song.title)
.font(.headline)
Text(song.artist)
.font(.subheadline)
.foregroundStyle(.secondary)
}
Spacer()
}
.padding(.horizontal)
.padding(.vertical, 12)
}
}
ScrollView {
LazyVStack(alignment: .leading, spacing: 0) {
ForEach(songs) { song in
SongRow(song: song)
}
.reorderable()
}
.reorderContainer(for: Song.self) { difference in
difference.apply(to: &songs)
}
}
.reorderable() tells SwiftUI which views can be dragged. .reorderContainer(for:move:) defines the scope where reordering happens and receives a ReorderDifference when the user drops an item. The difference.apply(to:) helper currently comes from Apple’s open source swift-collections package, via OrderedCollections.
The Same API on a Grid
This is where it gets interesting. The exact same two modifiers work on LazyVGrid without any changes:
let columns = [GridItem(.adaptive(minimum: 160), spacing: 12)]
ScrollView {
LazyVGrid(columns: columns, spacing: 12) {
ForEach(songs) { song in
SongCard(song: song)
}
.reorderable()
}
.padding()
.reorderContainer(for: Song.self) { difference in
difference.apply(to: &songs)
}
}
The container modifier goes on the grid, the ForEach modifier stays the same. No special grid handling.
Disabling Reordering Temporarily
reorderContainer has an isEnabled parameter, which is useful while saving or syncing:
.reorderContainer(
for: Song.self,
isEnabled: !isSaving
) { difference in
difference.apply(to: &songs)
}
When isSaving flips to true, the drag interaction disappears. When it flips back, it returns. That’s cleaner than conditionally applying the modifier.
Multiple Collections
If your content is split into sections backed by separate arrays, there’s a reorderable(collectionID:) variant. You label each ForEach with a collection identifier:
ForEach(albumSection.items) { song in
SongCard(song: song)
}
.reorderable(collectionID: albumSection.id)
Then pass the collection ID type to the container modifier:
.reorderContainer(
for: Song.self,
in: AlbumSection.ID.self
) { difference in
difference.apply(to: &songs)
}
When someone moves a song from one album to another, the closure receives a ReorderDifference that includes both the collection identifier and the new position, so you know exactly which array to update.
The useful bit is that .reorderContainer() is explicit about scope. It goes on the view that owns the data, which naturally constrains where drops land. If you have two unrelated lists, each gets its own container and items stay in their own collection. There’s no accidental cross-contamination.
The design also separates reordering from general drag-and-drop. .reorderable() is specifically about moving items within a collection, it doesn’t involve Transferable or item providers. If you also need to drag items out to other apps, you’d add .dragContainer() on top of the same view. The two layers compose cleanly.
The trade-off right now is that the convenient apply(to:) helper comes from swift-collections, not from SwiftUI itself. If you don’t want that dependency, ReorderDifference still gives you the moved item IDs and the destination position (.before(id) or .end), so you can update the array yourself.
Reordering in a LazyVGrid is something I’ve wanted for a while, and the fact that it’s the same two modifiers as a stack makes it easy to adopt.
Further Reading
- Code-along: Build powerful drag and drop in SwiftUI - WWDC26 session that introduced these APIs
- Reordering items in lists, stacks, grids, and custom layouts - Apple’s full article covering single and multi-collection reordering