Reorder Any SwiftUI View Starting in Xcode 27

  • Jun 13

Reorder Any SwiftUI View Starting in Xcode 27

At WWDC 2026, Apple continued expanding SwiftUI’s capabilities across iOS 27, iPadOS 27, macOS 27, watchOS 27, tvOS 27, and visionOS 27. 

Another exciting additions for developers building custom interfaces is support for drag-and-drop reordering outside of List.

Starting in Xcode 27, SwiftUI introduces a much simpler and more powerful solution with the new reorderable() modifier and reorderContainer(for:action:).

These APIs make it possible to add drag-to-reorder behavior to virtually any SwiftUI container while allowing you to maintain complete control over how your data source is updated.

To demo this new functionality, we will use a collection of DevTechie courses displayed inside a LazyVGrid.



We will use same data structure as last article

struct DTCourses: Identifiable {
    var name: String
    var lessons: Int
    
    var id: String {
        return name + lessons.description
    }
}

extension DTCourses {
    static var sample = [
        DTCourses(name: "Accelerated iOS Development with Claude Code", lessons: 19),
        DTCourses(name: "Agentic iOS App Development with Claude", lessons: 14),
        DTCourses(name: "Ace Your iOS Developer Interview: From Core Concepts to Interview-Ready Expertise", lessons: 11),
        DTCourses(name: "Master In App Purchase in SwiftUI & iOS 26 with SwiftData", lessons: 17),
    ]
}

Each course is represented by a custom card view

struct CourseCardView: View {
    let course: DTCourses
    
    var body: some View {
        HStack(spacing: 16) {
            
            VStack(alignment: .leading, spacing: 6) {
                Text(course.name)
                    .font(.headline)
                    .foregroundStyle(.primary)
                    .lineLimit(2)
                
                HStack(spacing: 4) {
                    Image(systemName: "play.circle.fill")
                        .font(.caption)
                        .foregroundStyle(.secondary)
                    Text("\(course.lessons) lessons")
                        .font(.subheadline)
                        .foregroundStyle(.secondary)
                }
            }
        }
        .frame(maxWidth: .infinity, alignment: .leading)
        .padding()
        .background(.orange.opacity(0.3), in: .rect(cornerRadius: 10))
    }
}

Our grid is built using a ScrollView and a three-column LazyVGrid.

struct ContentView: View {
    
    @State private var courses: [DTCourses] = DTCourses.sample
    
    var body: some View {
        NavigationStack {
            ScrollView {
                LazyVGrid(columns: Array(repeating: GridItem(), count: 3)) {
                    ForEach(courses) { course in
                        CourseCardView(course: course)
                            .swipeActions {
                                Button(role: .destructive) {
                                    courses.removeAll { $0.id == course.id }
                                } label: {
                                    Label("Delete", systemImage: "trash")
                                }
                            }
                    }

At this point the grid is static. Users can view the courses, but they cannot rearrange them. Enabling drag-and-drop reordering is surprisingly simple just apply the reorderable() modifier to the views inside your ForEach.

struct ContentView: View {
    
    @State private var courses: [DTCourses] = DTCourses.sample
    
    var body: some View {
        NavigationStack {
            ScrollView {
                LazyVGrid(columns: Array(repeating: GridItem(), count: 3)) {
                    ForEach(courses) { course in
                        CourseCardView(course: course)
                            .swipeActions {
                                Button(role: .destructive) {
                                    courses.removeAll { $0.id == course.id }
                                } label: {
                                    Label("Delete", systemImage: "trash")
                                }
                            }
                    }
                    .reorderable()
                }

This tells SwiftUI that the view participates in reordering operations. However, SwiftUI still needs to know how to update the underlying collection when items move that’s where reorderContainer(for:action:) comes in.

Just like swipe actions require a coordinating container, reordering is managed by a parent container using the reorderContainer modifier.

.reorderContainer(for: DTCourses.self) { difference in }

The closure receives a difference parameter that describes what moved and where it was dropped. This gives you complete control over updating your data source.

The difference object contains two important pieces of information:

sources — The IDs of the items being moved.

destination — The drop location.

In our example, we first identify all moved courses.

var moved: [DTCourses] = []
for id in difference.sources {
    if let index = courses.firstIndex(where: { $0.id == id }) {
        moved.append(courses[index])
    }
}

Next, we remove the moved items from their original positions.

courses.removeAll { difference.sources.contains($0.id) }

Finally, we insert them into their new location.

switch difference.destination.position {
case .before(let targetID):
    if let targetIndex = courses.firstIndex(where: { $0.id == targetID }) {
        courses.insert(contentsOf: moved, at: targetIndex)
    }
case .end:
    courses.append(contentsOf: moved)
}

This approach gives you complete flexibility over how reordering behaves while keeping the implementation straightforward.



Complete view

struct ContentView: View {
    
    @State private var courses: [DTCourses] = DTCourses.sample
    
    var body: some View {
        NavigationStack {
            ScrollView {
                LazyVGrid(columns: Array(repeating: GridItem(), count: 3)) {
                    ForEach(courses) { course in
                        CourseCardView(course: course)
                            .swipeActions {
                                Button(role: .destructive) {
                                    courses.removeAll { $0.id == course.id }
                                } label: {
                                    Label("Delete", systemImage: "trash")
                                }
                            }
                    }
                    .reorderable()
                }
                .reorderContainer(for: DTCourses.self) { difference in
                    var moved: [DTCourses] = []
                    for id in difference.sources {
                        if let index = courses.firstIndex(where: { $0.id == id }) {
                            moved.append(courses[index])
                        }
                    }
                   
                    courses.removeAll { difference.sources.contains($0.id) }
                    
                    switch difference.destination.position {
                    case .before(let targetID):
                        if let targetIndex = courses.firstIndex(where: { $0.id == targetID }) {
                            courses.insert(contentsOf: moved, at: targetIndex)
                        }
                    case .end:
                        courses.append(contentsOf: moved)
                    }
                }
                .padding()
            }
            .swipeActionsContainer()
            .navigationTitle("DevTechie Courses")
            .navigationSubtitle("visit DevTechie.com")
        }
    }
}

In our example, each course card also supports swipe actions.

.swipeActions {
    Button(role: .destructive) {
        courses.removeAll { $0.id == course.id }
    } label: {
        Label("Delete", systemImage: "trash")
    }
}

Because our grid lives inside a ScrollView, we also apply the new swipeActionsContainer() modifier introduced in Xcode 27.

.swipeActionsContainer()

This ensures swipe interactions remain coordinated while still allowing users to drag and reorder items naturally.

The result is a highly interactive interface that would have required significantly more code in previous SwiftUI releases.

The new reorderable() and reorderContainer(for:action:) APIs make drag-and-drop reordering a first-class SwiftUI feature across all Apple platforms.

With just a few modifiers, you can add intuitive reordering support to grids, stacks, and custom layouts while maintaining full control over your underlying data model.

Thank you for reading. If you found this article helpful and would like to support our work, visit DevTechie.com for in-depth SwiftUI, iOS, and Apple development courses designed to help you build real-world applications and stay current with the latest Apple technologies.

Happy coding, and I’ll see you in the next article.