Posts

Reordenación nativa de contenedores en SwiftUI con iOS 27

Arturo Rivas Arias

Hasta ahora, si queríamos permitir que los usuarios reorganizasen elementos en una interfaz SwiftUI, la solución más habitual era recurrir a List y su modificador onMove. El problema era que muchas interfaces modernas ya no utilizan las listas tradicionales: dashboards, cuadrículas, paneles personalizables, carruseles horizontales o layouts completamente personalizados quedaban fuera de esta experiencia.

Con iOS 27, SwiftUI introduce una nueva API que generaliza este comportamiento mediante los modificadores reorderable() y reorderContainer(...), permitiendo implementar una reordenación nativa en prácticamente cualquier contenedor de vistas. Esta novedad supone un cambio conceptual importante: la reordenación deja de ser una característica exclusiva de List para convertirse en una capacidad que cualquier contenedor puede adoptar.

Cómo funciona la nueva API

La nueva solución se divide en dos partes claramente diferenciadas. Por un lado, debemos indicar cuál es el contenido dinámico puede participar en una operación de reordenación:

ForEach(items) { item in
    ItemView(item: item)
}
.reorderable()

Por otro lado, el contenedor padre debe declarar que admite operaciones de reordenación y definir cómo se actualizan los datos cuando se ejecuta la misma:

.reorderContainer(for: Item.self) { difference in
    items.apply(difference: difference)
}

Esta separación encaja perfectamente con la filosofía declarativa de SwiftUI.

Un ejemplo práctico

Imaginemos que tenemos un lista de proyecto, pero que estamos usando un LazyVStack dentro de un ScrollView. Al no ser una lista, no tenemos disponible el modificador onMove. Pero a partir de iOS 27, podemos utilizar la nueva API.

import SwiftUI

struct Project: Identifiable, Sendable {
    let id = UUID()
    var name: String
}

struct ProjectsView: View {
    @State private var projects = [
        Project(name: "Backend"),
        Project(name: "iOS"),
        Project(name: "WatchOS"),
        Project(name: "VisionOS")
    ]

    var body: some View {
        ScrollView {
            LazyVStack(spacing: 12) {
                ForEach(projects) { project in
                    Text(project.name)
                }
                .reorderable()
            }
            .reorderContainer(for: Project.self) { difference in
                projects.apply(difference: difference)
            }
        }
    }
}

Podría parece un cambio menor, pero con esta actualización podremos crear layouts más dinámicos sin estar atados a las limitaciones de List ni a realizar una implementación manual para la reordenación de elementos.