@FetchRequest en SwiftUI — qué es, obtención de Core Data y mecanismo

Autor: IT Sectr Publicado: 2026-06-25 Tiempo de lectura: 8 min

@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 — un property wrapper para la obtención reactiva de Core Data en SwiftUI
  • Actualización automática de la IU al añadir, eliminar o modificar registros en Core Data
  • Predicados y ordenaciones se definen en el inicializador o mediante parámetros del property wrapper
  • managedObjectContext debe estar disponible a través de @Environment en la jerarquía de View
  • Predicados dinámicos permiten cambiar las condiciones de filtro en tiempo de ejecución

¿Qué es @FetchRequest en SwiftUI?

@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().

Ordenación y predicados de FetchRequest

@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.

swift
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 con List y datos dinámicos

@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.

swift
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.

Ejemplos de código de @FetchRequest

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.

swift
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.

Predicados dinámicos en tiempo de ejecución

@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

¿Cómo pasar managedObjectContext a @FetchRequest?

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.

¿Se puede actualizar @FetchRequest sin recargar la View?

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().

¿Cuál es la diferencia entre @FetchRequest y @SectionedFetchRequest?

@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.

¿Cómo usar @FetchRequest con Relationships?

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 admite paginación?

@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

  • @FetchRequest — un property wrapper para la obtención reactiva automática de Core Data en SwiftUI
  • Ordenación definida por un array de NSSortDescriptor, filtrado por NSPredicate
  • Actualización automática de la IU ante cualquier cambio de datos en el contexto de Core Data
  • managedObjectContext se pasa a través de @Environment en la jerarquía de Views
  • Predicados dinámicos se implementan mediante un init personalizado con un parámetro de predicado
  • Operaciones CRUD se realizan a través de viewContext con sincronización automática de la lista
  • Use @FetchRequest para mostrar Core Data, @SectionedFetchRequest para listas agrupadas

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.

Discutir el proyecto

Lea también