@FetchRequest en SwiftUI — un property wrapper para la obtención automática de datos de Core Data con actualización reactiva de la interfaz. El wrapper acepta NSFetchRequest con predicados de filtro y ordenaciones, y el resultado — un array de NSManagedObject — se actualiza automáticamente ante cualquier cambio en el contexto de Core Data. Según la Apple Developer Documentation (2025), @FetchRequest requiere managedObjectContext en el entorno de SwiftUI y admite predicados dinámicos, proporcionando una conexión reactiva entre Core Data y la interfaz de usuario.
Puntos clave
@FetchRequest es un property wrapper de SwiftUI que crea y gestiona un FetchRequest de Core Data. Al inicializarse, el wrapper acepta un tipo de entidad, un array de NSSortDescriptor para la ordenación y un NSPredicate opcional para el filtrado. El resultado se almacena como un array de objetos del tipo NSManagedObject especificado y se actualiza automáticamente cuando se producen cambios en la capa de persistencia.
La característica clave de @FetchRequest es la reactividad. Cuando los datos en Core Data cambian (save, insert, delete, update), SwiftUI realiza automáticamente una nueva obtención y redibuja todas las Views que usan este fetch request. El desarrollador no necesita llamar manualmente a refresh, suscribirse a NSManagedObjectContextDidSaveNotification ni recargar los datos. SwiftUI gestiona todo el ciclo de sincronización.
Para que @FetchRequest funcione, se requiere un NSManagedObjectContext disponible en el entorno de SwiftUI a través de @Environment(\.managedObjectContext). El contexto se pasa normalmente desde la raíz de la aplicación, donde se crea un NSPersistentContainer. El patrón estándar es usar una ViewModel o struct App para configurar el stack de Core Data y pasar el contexto mediante el modificador .environment().
@FetchRequest acepta varios parámetros para configurar la obtención. SortDescriptors — un array de NSSortDescriptor que determina el orden de los registros. Predicate — un NSPredicate para el filtrado, por ejemplo, obtener solo las tareas con estado “completado”. Animation — animación para actualizar la lista cuando los datos cambian.
Los predicados de Core Data usan un formato de expresiones similares a SQL: NSPredicate(format: “status == %@”, “completed”). Se pueden combinar múltiples condiciones mediante AND, OR y NOT. SortDescriptors definen el orden: NSSortDescriptor(keyPath: \Task.dueDate, ascending: true). Para ordenar por varios campos, se pasa un array de descriptores — el primero se aplica como principal, los restantes como secundarios.
struct TaskListView: View {
@Environment(\.managedObjectContext) var viewContext
@FetchRequest(
sortDescriptors: [
NSSortDescriptor(keyPath: \.Task.dueDate, ascending: true),
NSSortDescriptor(keyPath: \.Task.priority, ascending: false)
],
predicate: NSPredicate(format: "isCompleted == NO"),
animation: .default
)
var tasks: FetchedResults<Task>
var body: some View {
List(tasks) { task in
Text(task.title ?? "")
}
}
}
En el ejemplo, @FetchRequest obtiene todas las tareas incompletas (isCompleted == NO), ordenadas primero por fecha (ascendente), luego por prioridad (descendente). FetchedResults<Task> es una colección que se actualiza automáticamente y se comporta como un array para ForEach y List. La animación .default garantiza una aparición/desaparición suave de los registros ante cambios.
@FetchRequest se combina perfectamente con List y ForEach para mostrar listas dinámicas de Core Data. El resultado de la obtención (FetchedResults) se ajusta al protocolo RandomAccessCollection, por lo que se puede usar directamente en List y ForEach. Al añadir, eliminar o modificar registros, la lista se redibuja automáticamente con animación.
Para eliminar registros se usa el método del contexto delete. Para añadir registros, se crea un nuevo NSManagedObject y se llama a save. Todos los cambios se realizan a través de viewContext, que junto con @FetchRequest garantiza la consistencia de los datos. Para la edición se usa @ObservedObject o @Bindable para un objeto específico de Core Data.
struct TaskListWithActions: View {
@Environment(\.managedObjectContext) var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \.Task.createdAt,
ascending: false)]
)
var tasks: FetchedResults<Task>
var body: some View {
List {
ForEach(tasks) { task in
HStack {
Text(task.title ?? "")
Spacer()
Image(systemName: task.isCompleted
? "checkmark.circle.fill"
: "circle")
}
.onTapGesture { toggleTask(task) }
}
.onDelete(perform: deleteTasks)
}
.toolbar {
Button(systemImage: "plus") { addTask() }
}
}
private func addTask() {
let newTask = Task(context: viewContext)
newTask.title = "New task \(tasks.count + 1)"
newTask.createdAt = Date()
try? viewContext.save()
}
private func toggleTask(_ task: Task) {
task.isCompleted.toggle()
try? viewContext.save()
}
private func deleteTasks(at offsets: IndexSet) {
for index in offsets {
viewContext.delete(tasks[index])
}
try? viewContext.save()
}
}
Este ejemplo demuestra un CRUD completo con @FetchRequest. Añadir: crear una Task a través del contexto y save(). Eliminar: delete() en el contexto y save(). Actualizar: alternar una propiedad y save(). Después de cada save(), @FetchRequest vuelve a obtener los datos automáticamente y la List se actualiza con animación. Los modificadores onDelete y onTapGesture proporcionan interacciones estándar de iOS.
Veamos un ejemplo completo de una aplicación de diario con @FetchRequest para la obtención de registros. El formulario de búsqueda usa un predicado dinámico: al introducir texto, FetchRequest actualiza automáticamente los resultados, filtrando los registros por título y contenido. La ordenación es por fecha de creación, de más reciente a más antiguo.
struct JournalEntryView: View {
@Environment(\.managedObjectContext) var viewContext
@State private var searchText = ""
private var searchPredicate: NSPredicate? {
guard !searchText.isEmpty else { return nil }
return NSPredicate(format: "title CONTAINS[c] %@", searchText)
}
@FetchRequest var entries: FetchedResults<JournalEntry>
init(searchText: String) {
let sort = [NSSortDescriptor(keyPath: \.JournalEntry.date,
ascending: false)]
let pred: NSPredicate? = searchText.isEmpty
? nil
: NSPredicate(format: "title CONTAINS[c] %@ OR content CONTAINS[c] %@",
searchText, searchText)
self._entries = FetchRequest(
sortDescriptors: sort,
predicate: pred,
animation: .default
)
}
var body: some View {
List(entries) { entry in
VStack(alignment: .leading) {
Text(entry.title ?? "").font(.headline)
Text(entry.content ?? "").font(.subheadline)
.lineLimit(2)
}
}
.searchable(text: $searchText)
}
}
Diario de notas con búsqueda: al introducir texto en el campo searchable se crea un NSPredicate con CONTAINS[c] (búsqueda sin distinción de mayúsculas/minúsculas) en los campos title y content. Init acepta searchText y crea un FetchRequest con el predicado correspondiente. El modificador searchable actualiza automáticamente searchText, y @FetchRequest reacciona al cambio del predicado y vuelve a obtener los datos.
Para escenarios más complejos con múltiples filtros, combine NSCompoundPredicate con lógica AND/OR. @FetchRequest admite predicados de cualquier complejidad, incluyendo subconsultas anidadas (SUBQUERY) para filtrar por relationship. Sin embargo, tenga en cuenta: los predicados complejos afectan al rendimiento — para conjuntos de datos grandes (más de 10 000 registros), use la indexación de Core Data y limite la obtención mediante fetchLimit.
@FetchRequest admite predicados dinámicos a través del parámetro predicate, que se puede cambiar en tiempo de ejecución. Para ello, se crea un @FetchRequest con un predicado variable y se pasa mediante Binding. Cuando el usuario cambia el filtro — por ejemplo, selecciona una categoría de tareas — el predicado se actualiza y la obtención se reinicia automáticamente.
Los predicados dinámicos se implementan mediante una propiedad @State o @Published, que se pasa como Binding al inicializador de @FetchRequest. SwiftUI rastrea los cambios en este Binding y recrea el NSFetchRequest con el nuevo predicado, actualizando automáticamente la lista.
Preguntas frecuentes
Añada @Environment(\.managedObjectContext) var viewContext en la View raíz y páselo mediante el modificador .environment(\.managedObjectContext, context). Todas las Views hijas que usen @FetchRequest recibirán automáticamente el contexto del entorno.
Sí, modifique el contexto de Core Data: añada, elimine o actualice un objeto y llame a save(). @FetchRequest realizará automáticamente una nueva obtención y actualizará la IU. Para una actualización forzada sin save(), use viewContext.refreshAllObjects().
@FetchRequest devuelve un array plano de objetos. @SectionedFetchRequest agrupa los resultados por un key path especificado en secciones, similar a UITableView con sectionNameKeyPath. Use @SectionedFetchRequest para listas agrupadas con encabezados de sección.
Para filtrar por relationship, use la notación de punto: NSPredicate(format: “category.name == %@”, “Work”). @FetchRequest realiza automáticamente un JOIN en SQLite. Para acceder a una relationship a través de un objeto seleccionado, use las propiedades habituales de NSManagedObject.
@FetchRequest no admite paginación directamente, ya que está diseñado para mostrar todos los registros con actualización reactiva. Para paginación, use NSFetchRequest con fetchLimit/fetchOffset y cargue los datos manualmente al hacer scroll en la lista.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.
Lea también