Si eres un iOS Developer moderno, seguramente ya has dejado atrás los días de lidiar con UITableViewDataSource y métodos delegados interminables para lograr tareas simples. La evolución de la programación Swift nos ha traído herramientas declarativas que hacen que el desarrollo sea mucho más rápido y menos propenso a errores.
Una de las interacciones más comunes en cualquier aplicación es la capacidad de modificar colecciones de datos. En este artículo, vamos a explorar a fondo cómo implementar listas editables en SwiftUI, permitiendo a los usuarios eliminar y reordenar elementos con gestos nativos. Lo mejor de todo es que, gracias a SwiftUI, aprenderemos a aplicar estos conceptos de manera multiplataforma, usando Xcode y Swift para que tu código funcione a la perfección en iOS, macOS y watchOS.
1. El Poder de la Interacción: ¿Qué hace a una List Editable?
En SwiftUI, una List es mucho más que un simple contenedor de vistas. Está diseñada para integrarse estrechamente con el estado de tu aplicación. Para crear listas editables en SwiftUI, dependemos de modificadores específicos que se aplican a los elementos iterados (normalmente dentro de un ForEach) y no a la lista en sí.
Los dos pilares fundamentales de la edición de listas son:
- Eliminación (Delete): Permitir al usuario deslizar una fila para borrarla o tocar un botón de eliminación en el modo de edición.
- Reordenamiento (Move): Permitir al usuario arrastrar filas para cambiar su orden lógico dentro de la colección.
Para que SwiftUI pueda gestionar estas animaciones y cambios de estado correctamente, nuestros datos deben estar respaldados por un modelo robusto.
2. Configurando el Entorno en Xcode
Antes de escribir la lógica, preparemos nuestro lienzo.
- Abre Xcode y selecciona “Create a new Xcode project”.
- Ve a la pestaña Multiplatform y selecciona la plantilla App. Esto nos garantiza que nuestro código base será compatible con todo el ecosistema.
- Nombra tu proyecto, por ejemplo,
EditableListMastery. - Asegúrate de que la interfaz sea SwiftUI y el lenguaje sea Swift.
Al usar el enfoque multiplataforma, Xcode configura los targets necesarios para que podamos probar la misma vista de lista en un simulador de iPhone, en nuestro Mac y en el Apple Watch.
3. Construyendo el Modelo de Datos
Como cualquier buen iOS Developer sabe, la interfaz de usuario es solo un reflejo de los datos subyacentes. Vamos a crear una aplicación sencilla de gestión de tareas (To-Do List).
Crea un nuevo archivo de Swift llamado TaskItem.swift:
import Foundation
// Nuestro modelo debe conformar Identifiable para funcionar bien en List y ForEach
struct TaskItem: Identifiable, Equatable {
let id = UUID()
var title: String
var isCompleted: Bool = false
}
El protocolo Identifiable es crucial aquí. Cuando implementamos listas editables en SwiftUI, el framework necesita saber exactamente qué elemento se está moviendo o eliminando. El id único se encarga de esto, evitando comportamientos erráticos o cierres inesperados (crashes) que solían ocurrir en UIKit cuando la fuente de datos perdía sincronización con la vista.
4. Implementando la Eliminación de Filas (Swipe to Delete)
Vamos a crear nuestra vista principal y añadir la funcionalidad de borrado. Para esto, utilizamos el modificador .onDelete(perform:).
Abre tu ContentView.swift e implementa lo siguiente:
import SwiftUI
struct ContentView: View {
// Usamos @State para que SwiftUI reaccione a los cambios en el array
@State private var tasks: [TaskItem] = [
TaskItem(title: "Estudiar programación Swift"),
TaskItem(title: "Dominar Xcode 15"),
TaskItem(title: "Implementar listas editables en SwiftUI"),
TaskItem(title: "Subir app a la App Store")
]
var body: some View {
NavigationStack {
List {
// Es vital usar ForEach dentro de la List para habilitar la edición
ForEach(tasks) { task in
Text(task.title)
.font(.body)
}
// El modificador onDelete se aplica al ForEach, no a la List
.onDelete(perform: deleteTasks)
}
.navigationTitle("Mis Tareas")
}
}
// Función para manejar la eliminación
func deleteTasks(at offsets: IndexSet) {
tasks.remove(atOffsets: offsets)
}
}
Entendiendo IndexSet
En la programación Swift, cuando usas .onDelete, SwiftUI no te pasa el objeto en sí, sino un IndexSet. Un IndexSet es una colección que representa los índices de los elementos que el usuario quiere eliminar (puede ser más de uno si estás en modo de edición múltiple). La librería estándar de Swift proporciona el método remove(atOffsets:) en los Arrays, lo que hace que esta operación sea trivial y de una sola línea.
Si pruebas esto en el simulador de iOS, verás que ya puedes deslizar de derecha a izquierda sobre cualquier fila para revelar el botón rojo de eliminar. ¡Magia pura con unas pocas líneas de código!
5. Implementando el Reordenamiento de Filas (Drag to Move)
Ahora, demos al usuario la capacidad de priorizar sus tareas moviéndolas de lugar. Para esto, añadiremos el modificador .onMove(perform:).
Modifica tu ContentView.swift para añadir esta funcionalidad:
import SwiftUI
struct ContentView: View {
@State private var tasks: [TaskItem] = [
TaskItem(title: "Estudiar programación Swift"),
TaskItem(title: "Dominar Xcode"),
TaskItem(title: "Implementar listas editables en SwiftUI"),
TaskItem(title: "Subir app a la App Store")
]
var body: some View {
NavigationStack {
List {
ForEach(tasks) { task in
Text(task.title)
}
.onDelete(perform: deleteTasks)
.onMove(perform: moveTasks) // Añadimos el modificador de movimiento
}
.navigationTitle("Mis Tareas")
.toolbar {
// Añadimos el EditButton nativo en la barra de navegación
EditButton()
}
}
}
func deleteTasks(at offsets: IndexSet) {
tasks.remove(atOffsets: offsets)
}
// Función para manejar el reordenamiento
func moveTasks(from source: IndexSet, to destination: Int) {
tasks.move(fromOffsets: source, toOffset: destination)
}
}
El papel del EditButton y moveTasks
A diferencia de .onDelete, que se puede activar mediante un gesto de deslizamiento (swipe), el reordenamiento visual tradicional con los iconos de “hamburguesa” a la derecha de la fila requiere que la lista esté en Modo de Edición.
Para habilitar este modo fácilmente en iOS, SwiftUI nos regala la vista EditButton(). Al colocarlo en el .toolbar, SwiftUI automáticamente alterna el estado de edición del entorno (Environment editMode).
La función moveTasks(from:to:) recibe el conjunto de índices originales (source) y el índice de destino (destination). Al igual que con el borrado, las colecciones en Swift tienen un método nativo move(fromOffsets:toOffset:) que maneja el intercambio en tu array subyacente de manera segura.
6. Adaptando las Listas Editables para macOS y watchOS
Como verdaderos profesionales, no nos detenemos en el iPhone. El ecosistema es amplio, y el comportamiento de las listas editables en SwiftUI varía ligeramente dependiendo del sistema operativo.
watchOS
En el Apple Watch, el espacio es vital. El modificador .onDelete funciona de manera nativa y fantástica: el usuario simplemente desliza la fila para eliminar.
Sin embargo, el EditButton y el reordenamiento complejo no son patrones de interacción comunes o fáciles de usar en una pantalla tan pequeña. Generalmente, para watchOS, nos limitamos al swipe to delete para mantener una interfaz limpia y utilizable.
Puedes usar directivas de compilación para ocultar el botón de edición en el reloj:
.toolbar {
#if os(iOS)
EditButton()
#endif
}
macOS
En el Mac, la interacción cambia hacia el ratón y el teclado. En macOS, no existe un EditButton nativo que active un modo de edición visual con iconos de “hamburguesa”.
En su lugar, los usuarios de Mac esperan poder arrastrar y soltar (Drag and Drop) elementos directamente sin entrar en un “modo de edición”, o usar el teclado (tecla de retroceso) para eliminar filas seleccionadas.
Para soportar la eliminación con la tecla Backspace en macOS, necesitamos gestionar la selección de la lista:
struct ContentView: View {
@State private var tasks: [TaskItem] = [ /* ... */ ]
@State private var selection: Set<UUID> = [] // Estado para la selección múltiple
var body: some View {
NavigationStack {
// Pasamos el binding de selección a la List
List(selection: $selection) {
ForEach(tasks) { task in
Text(task.title)
.tag(task.id) // Esencial para que la selección funcione
}
.onDelete(perform: deleteTasks)
.onMove(perform: moveTasks)
}
.navigationTitle("Mis Tareas")
.toolbar {
#if os(iOS)
EditButton()
#elseif os(macOS)
// Botón manual para eliminar seleccionados en macOS
Button(role: .destructive, action: deleteSelected) {
Label("Eliminar", systemImage: "trash")
}
.disabled(selection.isEmpty)
#endif
}
}
}
// ... funciones deleteTasks y moveTasks ...
// Función específica para macOS
func deleteSelected() {
tasks.removeAll { selection.contains($0.id) }
selection.removeAll()
}
}
En macOS, gracias a .onMove, el usuario simplemente puede hacer clic, mantener presionado y arrastrar una fila para reordenarla de forma nativa.
7. Mejores Prácticas y Rendimiento
Al trabajar con programación Swift avanzada y listas mutables, ten en cuenta lo siguiente:
- Identificadores Estables: Asegúrate de que tu
UUIDno se regenere en cada renderizado de la vista. Si usas unidcalculado dinámicamente en lugar de una propiedad constante (let id = UUID()), la animación de borrado y movimiento será errática. - Animaciones Personalizadas: Si modificas el array fuera de los modificadores
.onDeleteo.onMove(por ejemplo, un botón para “Borrar Todo”), asegúrate de envolver el cambio de estado en un bloquewithAnimation { }para que SwiftUI transicione la interfaz suavemente. - Gestión de Estado Centralizada: Para aplicaciones más complejas, no mantengas el array directamente en la Vista con
@State. Mueve tu lógica a un ViewModel usando@Observable(oObservableObjecten versiones anteriores) para separar la lógica de negocio de la interfaz.
Conclusión
Saber implementar listas editables en SwiftUI es un requisito indispensable para cualquier iOS Developer. Hemos visto cómo, utilizando Xcode y Swift, podemos reducir la complejidad arquitectónica de UIKit a un par de modificadores declarativos: .onDelete y .onMove.
Además, al adoptar un enfoque multiplataforma, hemos adaptado las interacciones para que se sientan nativas tanto en un entorno táctil como iOS y watchOS, como en un entorno de escritorio con macOS.
Si tienes cualquier duda sobre este artículo, contacta conmigo y estaré encantado de ayudarte . Puedes contactar conmigo en mi perfil de X o en mi perfil de Instagram.









