wandres.dev
SWIFTDATA · persistencia moderna

@Query: leer y filtrar datos

Trae tus datos a la interfaz con @Query, que se actualiza sola cuando cambian. Filtra con #Predicate, ordena, y construye consultas dinámicas.

⏱ 13 min

Guardar datos es la mitad; mostrarlos es la otra. @Query trae tus modelos a la vista y, lo mejor, se mantiene sincronizado: insertas o borras en cualquier parte de la app, y las listas que usan @Query se actualizan solas.

🎯 Al terminar esta lección sabrás
  • Leer datos con @Query.
  • Ordenar los resultados.
  • Filtrar con #Predicate.
  • Consultas dinámicas según la interfaz.

@Query: datos reactivos

Declara una propiedad @Query en tu vista y SwiftData la rellena con los objetos del almacén, manteniéndola al día:

struct ListaTareas: View {
    @Query private var tareas: [Tarea]

    var body: some View {
        List(tareas) { tarea in
            Text(tarea.titulo)
        }
    }
}
La reactividad de punta a punta

Fíjate en el círculo completo: una vista inserta una Tarea con el context, y esta lista —que ni sabe de la otra vista— se actualiza automáticamente. @Query observa el almacén; cuando cambia, SwiftUI redibuja. Es la misma reactividad de @State y @Observable, ahora extendida a la base de datos. No escribes código de “recargar la lista”: el dato es la fuente de la verdad, y la interfaz lo sigue. Persistencia y UI, un solo flujo.

Ordenar

Pasa un sort con un key path:

@Query(sort: \Tarea.creada, order: .reverse) private var tareas: [Tarea]

// varios criterios
@Query(sort: [SortDescriptor(\Tarea.completada),
              SortDescriptor(\Tarea.titulo)]) private var todas: [Tarea]

Filtrar con #Predicate

#Predicate describe una condición de forma segura y traducible a la base de datos:

@Query(filter: #Predicate<Tarea> { tarea in
    !tarea.completada
}) private var pendientes: [Tarea]

Combina condiciones con && y ||, compara textos con .contains, etc. El compilador valida el predicado — nada de strings mágicos.

Consultas dinámicas

Si el filtro depende de algo que cambia (una búsqueda del usuario), no puedes usar @Query estático. Configúralo en el init:

struct Buscador: View {
    @Query private var resultados: [Tarea]

    init(texto: String) {
        _resultados = Query(filter: #Predicate<Tarea> { tarea in
            texto.isEmpty || tarea.titulo.contains(texto)
        }, sort: \.titulo)
    }

    var body: some View {
        List(resultados) { Text($0.titulo) }
    }
}

La vista padre le pasa el texto (por ejemplo, desde un @State con un TextField), y al cambiar, recrea la vista con la consulta nueva.

💡
Borrar desde la lista

Combina @Query con .onDelete para el clásico “deslizar para borrar”:

List {
    ForEach(tareas) { Text($0.titulo) }
        .onDelete { indices in
            for i in indices { context.delete(tareas[i]) }
        }
}
⚔️ Muestra y filtra tus datos
  1. Muestra tus Nota (o Tarea) con @Query en una List.
  2. Ordénalas por fecha de creación descendente.
  3. Añade un @Query filtrado con #Predicate (solo las no completadas).
  4. Añade “deslizar para borrar” con .onDelete y context.delete.
  5. Inserta desde otra vista y confirma que la lista se actualiza sola.