SwiftData es el framework de Apple que te permite gestionar la persistencia de datos en iOS sin escribir SQL directamente. Si vienes de Core Data o SQLite, vas a notar que con SwiftData escribes mucho menos código y obtienes modelos que se convierten automáticamente en entidades dentro de tu aplicación.
En la práctica, vas a construir una app de to do que guarda tareas localmente, las ordena, permite marcarlas como completadas y eliminarlas con un gesto de swipe. Es ideal para desarrolladores iOS que quieren persistencia rápida sin la complejidad de un motor SQL.
Cómo defines un modelo con SwiftData
El primer paso es crear el objeto que representará cada tarea en tu base de datos local. Aquí entra en juego la anotación @Model, que convierte una clase normal en una entidad reconocida por SwiftData [01:30].
El flujo es directo: importas SwiftData, marcas la clase con @Model y declaras tus propiedades. En el ejemplo, una tarea necesita dos campos: un title de tipo String y un done de tipo Bool para saber si está completada.
¿Qué hace la anotación @Model en SwiftData? Convierte una clase de Swift en una entidad persistente. SwiftData la reconoce automáticamente y gestiona su almacenamiento sin que escribas SQL.
No olvides el inicializador init(title:done:). Es obligatorio para que SwiftData pueda crear instancias del modelo correctamente.
Cómo accedes al contexto y guardas datos
El contexto es la pieza clave: es la conexión directa con tu almacenamiento local. Lo obtienes con la anotación @Environment(\.modelContext) dentro de cualquier vista [03:45].
Una vez tienes el contexto, el patrón para insertar datos es siempre el mismo:
- Creas el objeto:
let newItem = Item(title: title, done: done).
- Lo insertas en el contexto:
context.insert(newItem).
- Guardas con
try context.save() dentro de un bloque do catch.
Aquí hay un detalle importante que se suele pasar por alto. Insertar no es lo mismo que guardar. El insert solo coloca el objeto temporalmente en el contexto; los cambios se persisten realmente cuando llamas a save(). Por eso envuelves la operación en un do catch, así capturas cualquier error de escritura.
Para cerrar la vista de agregar después de guardar, usa @Environment(\.dismiss) y llama dismiss() al final del flujo.
Cómo consultas y ordenas los items almacenados
Para mostrar la lista de tareas guardadas, SwiftData ofrece la anotación @Query, que ejecuta una consulta automática contra tu base de datos local [07:20].
La magia está en que puedes ordenar los resultados directamente en la declaración:
swift
@Query(sort: \Item.title) private var items: [Item]
Eso te devuelve un arreglo de Item ordenado alfabéticamente por título de forma ascendente. Si agregas una tarea que empieza con A, se irá al principio; si empieza con Z, al final. No necesitas refrescar la vista manualmente porque @Query reacciona a los cambios del contexto.
¿Qué diferencia hay entre @Query y @Environment en SwiftData? @Environment(\.modelContext) te da acceso al contexto para insertar, eliminar o guardar. @Query te entrega los datos ya consultados y ordenados, listos para mostrar.
Cómo conectas la vista de agregar con un sheet
Para abrir la pantalla de nueva tarea usas una variable de estado booleana, por ejemplo @State private var showingAddSheet = false. Cuando el usuario toca el botón de agregar, llamas a showingAddSheet.toggle() y eso cambia el valor entre true y false.
La vista flotante se conecta con el modificador .sheet(isPresented: $showingAddSheet) que envuelve a AddView(). Así obtienes la pantalla modal típica de iOS para capturar nuevos datos.
Dentro de esa vista de agregar, el botón "Añadir" se puede deshabilitar si el campo está vacío usando .disabled(title.isEmpty). Es un detalle de UX que evita guardar tareas sin nombre.
Cómo marcas tareas como completadas con Binding
Cada item de la lista se renderiza como un Toggle. Lo interesante aparece cuando necesitas que el toggle lea y escriba en la propiedad done del modelo en tiempo real [11:40].
La solución es crear un Binding personalizado con get y set:
- En el
get devuelves item.done.
- En el
set asignas item.done = newValue y llamas try context.save().
Este patrón sincroniza el estado visual del toggle con la base de datos. Cada cambio se persiste de inmediato.
Para mejorar la apariencia, aplica un .toggleStyle personalizado que muestre un checkmark cuando la tarea esté completada, y dale altura con .frame(height: 60) para que cada fila respire.
Cómo eliminas elementos con swipe actions
El gesto de deslizar para eliminar es estándar en iOS. SwiftUI lo expone con el modificador .swipeActions(edge: .trailing, allowsFullSwipe: true) [14:10].
Dentro colocas un Button(role: .destructive) que ejecuta dos acciones:
context.delete(item) para quitar el objeto del contexto.
try context.save() dentro de un do catch para confirmar el cambio en la base de datos.
El label del botón puede llevar el texto "borrar" más una imagen del sistema como trash. Igual que con la inserción, eliminar sin guardar deja el cambio solo en memoria.
Por qué necesitas modelContainer en el archivo principal
Si corres la app sin más, vas a recibir un error de model context al intentar guardar. La razón es que SwiftData necesita saber qué modelos debe gestionar a nivel de aplicación [17:30].
La solución vive en tu archivo principal, donde declaras @main. Al WindowGroup le agregas el modificador .modelContainer(for: Item.self). Eso registra el modelo en todo el árbol de vistas y habilita la persistencia real.
¿Qué es modelContainer en SwiftData? Es el contenedor que registra tus modelos a nivel de app. Sin él, el contexto no sabe qué entidades persistir y la operación de guardado falla.
Si tienes varios modelos, puedes pasarlos juntos o agruparlos en una clase contenedora para mantener limpio el archivo principal.
¿Has probado migrar un proyecto de Core Data a SwiftData? Cuéntanos en los comentarios cómo te fue con la transición.