@FetchRequest in SwiftUI — un property wrapper per il recupero automatico di dati da Core Data con aggiornamento reattivo dell'interfaccia. Il wrapper accetta NSFetchRequest con predicati di filtro e ordinamenti, e il risultato — un array di NSManagedObject — viene automaticamente aggiornato a qualsiasi modifica nel contesto di Core Data. Secondo la Apple Developer Documentation (2025), @FetchRequest richiede managedObjectContext nell'ambiente SwiftUI e supporta predicati dinamici, fornendo una connessione reattiva tra Core Data e l'interfaccia utente.
Punti chiave
@FetchRequest è un property wrapper di SwiftUI che crea e gestisce un FetchRequest da Core Data. All'inizializzazione, il wrapper accetta un tipo di entità, un array di NSSortDescriptor per l'ordinamento e un NSPredicate opzionale per il filtraggio. Il risultato viene memorizzato come un array di oggetti del tipo NSManagedObject specificato e si aggiorna automaticamente quando si verificano modifiche nel livello di persistenza.
La caratteristica principale di @FetchRequest è la reattività. Quando i dati in Core Data cambiano (save, insert, delete, update), SwiftUI esegue automaticamente un nuovo recupero e ridisegna tutte le View che utilizzano questa fetch request. Lo sviluppatore non deve chiamare manualmente refresh, iscriversi a NSManagedObjectContextDidSaveNotification o ricaricare i dati. SwiftUI gestisce l'intero ciclo di sincronizzazione.
Affinché @FetchRequest funzioni, è necessario un NSManagedObjectContext disponibile nell'ambiente SwiftUI tramite @Environment(\.managedObjectContext). Il contesto viene solitamente passato dalla radice dell'applicazione, dove viene creato un NSPersistentContainer. Il pattern standard è utilizzare una ViewModel o una struct App per configurare lo stack di Core Data e passare il contesto tramite il modificatore .environment().
@FetchRequest accetta diversi parametri per configurare il recupero. SortDescriptors — un array di NSSortDescriptor che determina l'ordine dei record. Predicate — un NSPredicate per il filtraggio, ad esempio, recuperare solo le attività con stato “completato”. Animation — animazione per aggiornare la lista quando i dati cambiano.
I predicati di Core Data utilizzano un formato di espressioni simili a SQL: NSPredicate(format: “status == %@”, “completed”). Si possono combinare più condizioni tramite AND, OR e NOT. SortDescriptors definiscono l'ordine: NSSortDescriptor(keyPath: \Task.dueDate, ascending: true). Per ordinare per più campi, si passa un array di descrittori — il primo viene applicato come principale, gli altri come secondari.
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 ?? "")
}
}
}
Nell'esempio, @FetchRequest recupera tutte le attività incomplete (isCompleted == NO), ordinate prima per data (crescente), poi per priorità (decrescente). FetchedResults<Task> è una raccolta ad aggiornamento automatico che si comporta come un array per ForEach e List. L'animazione .default garantisce un'apparizione/scomparsa fluida dei record in caso di modifiche.
@FetchRequest si combina perfettamente con List e ForEach per visualizzare liste dinamiche di Core Data. Il risultato del recupero (FetchedResults) è conforme al protocollo RandomAccessCollection, quindi può essere utilizzato direttamente in List e ForEach. Quando si aggiungono, eliminano o modificano record, la lista viene automaticamente ridisegnata con animazione.
Per eliminare record, si utilizza il metodo del contesto delete. Per aggiungere record, si crea un nuovo NSManagedObject e si chiama save. Tutte le modifiche vengono eseguite tramite viewContext, che insieme a @FetchRequest garantisce la consistenza dei dati. Per la modifica, si utilizza @ObservedObject o @Bindable per un oggetto Core Data specifico.
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()
}
}
Questo esempio dimostra un CRUD completo con @FetchRequest. Aggiunta: creare un Task tramite il contesto e save(). Eliminazione: delete() sul contesto e save(). Aggiornamento: alternare una proprietà e save(). Dopo ogni save(), @FetchRequest recupera automaticamente i dati di nuovo, e la List viene aggiornata con animazione. I modificatori onDelete e onTapGesture forniscono interazioni iOS standard.
Consideriamo un esempio completo di applicazione di diario con @FetchRequest per il recupero di record. Il modulo di ricerca utilizza un predicato dinamico: durante l'inserimento del testo, FetchRequest aggiorna automaticamente i risultati, filtrando i record per titolo e contenuto. L'ordinamento è per data di creazione, dal più recente al più vecchio.
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 con ricerca: durante l'inserimento del testo nel campo searchable, viene creato un NSPredicate con CONTAINS[c] (ricerca senza distinzione tra maiuscole e minuscole) sui campi title e content. Init accetta searchText e crea un FetchRequest con il predicato corrispondente. Il modificatore searchable aggiorna automaticamente searchText, e @FetchRequest reagisce al cambio di predicato e recupera nuovamente i dati.
Per scenari più complessi con più filtri, combinare NSCompoundPredicate con logica AND/OR. @FetchRequest supporta predicati di qualsiasi complessità, incluse sottoquery annidate (SUBQUERY) per il filtraggio per relationship. Tuttavia, ricordare: i predicati complessi influenzano le prestazioni — per set di dati grandi (oltre 10.000 record), utilizzare l'indicizzazione di Core Data e limitare il recupero tramite fetchLimit.
@FetchRequest supporta predicati dinamici tramite il parametro predicate, che può essere modificato in fase di esecuzione. Per farlo, si crea un @FetchRequest con un predicato variabile e lo si passa tramite Binding. Quando l'utente cambia il filtro — ad esempio, seleziona una categoria di attività — il predicato viene aggiornato e il recupero si riavvia automaticamente.
I predicati dinamici vengono implementati tramite una proprietà @State o @Published, che viene passata come Binding all'inizializzatore di @FetchRequest. SwiftUI tiene traccia delle modifiche a questo Binding e ricrea il NSFetchRequest con il nuovo predicato, aggiornando automaticamente la lista.
Domande frequenti
Aggiungere @Environment(\.managedObjectContext) var viewContext nella View radice e passarlo tramite il modificatore .environment(\.managedObjectContext, context). Tutte le View figlie che utilizzano @FetchRequest riceveranno automaticamente il contesto dall'ambiente.
Sì, modificare il contesto di Core Data: aggiungere, eliminare o aggiornare un oggetto e chiamare save(). @FetchRequest eseguirà automaticamente un nuovo recupero e aggiornerà l'interfaccia. Per un aggiornamento forzato senza save(), utilizzare viewContext.refreshAllObjects().
@FetchRequest restituisce un array piatto di oggetti. @SectionedFetchRequest raggruppa i risultati per un key path specificato in sezioni, simile a UITableView con sectionNameKeyPath. Utilizzare @SectionedFetchRequest per liste raggruppate con intestazioni di sezione.
Per filtrare per relationship, utilizzare la notazione punto: NSPredicate(format: “category.name == %@”, “Work”). @FetchRequest esegue automaticamente un JOIN in SQLite. Per accedere a una relationship tramite un oggetto selezionato, utilizzare le normali proprietà di NSManagedObject.
@FetchRequest non supporta la paginazione direttamente, poiché è progettato per visualizzare tutti i record con aggiornamento reattivo. Per la paginazione, utilizzare NSFetchRequest con fetchLimit/fetchOffset e caricare i dati manualmente durante lo scorrimento della lista.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche