@FetchRequest în SwiftUI — property wrapper pentru selectarea automată a datelor din Core Data cu actualizarea reactivă a interfeței. Wrapper primește NSFetchRequest cu predicate de filtrare și sortări, iar rezultatul — un array NSManagedObject — se actualizează automat la orice modificare în contextul Core Data. Conform Apple Developer Documentation (2025), @FetchRequest necesită managedObjectContext în mediul SwiftUI și suportă predicate dinamice, asigurând o legătură reactivă între Core Data și interfața utilizatorului.
Principalele puncte
@FetchRequest — este un property wrapper SwiftUI care creează și gestionează un FetchRequest din Core Data. La inițializare, wrapper primește tipul entității, un array NSSortDescriptor pentru sortare și un NSPredicate opțional pentru filtrare. Rezultatul este stocat ca un array de obiecte de tipul NSManagedObject specificat și se actualizează automat la modificări în stratul de persistență.
Caracteristica cheie a @FetchRequest este reactivitatea. Când datele din Core Data se modifică (save, insert, delete, update), SwiftUI execută automat o reselectare și redesenează toate View-urile care folosesc acest fetch request. Dezvoltatorul nu trebuie să apeleze manual refresh, să se aboneze la NSManagedObjectContextDidSaveNotification sau să reîncarce datele. SwiftUI gestionează întregul ciclu de sincronizare.
Pentru funcționare, @FetchRequest necesită NSManagedObjectContext disponibil în mediul SwiftUI prin @Environment(\.managedObjectContext). Contextul este de obicei transmis din rădăcina aplicației, unde este creat NSPersistentContainer. Modelul standard — utilizarea ViewModel sau a structurii App pentru configurarea stivei Core Data și transmiterea contextului prin modificatorul .environment().
@FetchRequest primește mai mulți parametri pentru configurarea selectării. SortDescriptors — un array NSSortDescriptor care determină ordinea înregistrărilor. Predicate — NSPredicate pentru filtrare, de exemplu, selectarea doar a sarcinilor cu statusul „finalizat". Animation — animație pentru actualizarea listei la modificări ale datelor.
Predicatele Core Data folosesc un format de expresii asemănător SQL: NSPredicate(format: „status == %@", „completed"). Se pot combina mai multe condiții prin AND, OR și NOT. SortDescriptors stabilesc ordinea: NSSortDescriptor(keyPath: \Task.dueDate, ascending: true). Pentru sortarea după mai multe câmpuri se transmite un array de descriptori — primul se aplică ca principal, următoarele ca secundare.
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 ?? "")
}
}
}
În exemplu, @FetchRequest selectează toate sarcinile nefinalizate (isCompleted == NO), sortate mai întâi după dată (crescător), apoi după prioritate (descrescător). FetchedResults<Task> — este o colecție actualizată automat care se comportă ca un array pentru ForEach și List. Animația .default asigură apariția/dispariția lină a înregistrărilor la modificări.
@FetchRequest se combină ideal cu List și ForEach pentru afișarea listelor dinamice Core Data. Rezultatul selectării (FetchedResults) corespunde protocolului RandomAccessCollection, deci poate fi folosit direct în List și ForEach. La adăugarea, ștergerea sau modificarea înregistrărilor, lista se redesenează automat cu animație.
Pentru ștergerea înregistrărilor se folosește metoda delete a contextului. Pentru adăugare — crearea unui nou NSManagedObject și apelarea save. Toate modificările se execută prin viewContext, care împreună cu @FetchRequest asigură consistența datelor. Pentru editare se folosește @ObservedObject sau @Bindable pentru un obiect Core Data specific.
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: "adaugă") { 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()
}
}
Acest exemplu demonstrează CRUD complet cu @FetchRequest. Adăugare: crearea Task prin context și save(). Ștergere: delete() pe context și save(). Actualizare: toggle proprietății și save(). După fiecare save(), @FetchRequest reinteroghează automat datele, iar List se actualizează cu animație. Modificatorii onDelete și onTapGesture asigură interacțiunile standard iOS.
@FetchRequest suportă predicate dinamice prin parametrul predicate care poate fi modificat în timpul execuției. Pentru aceasta trebuie creat @FetchRequest cu o variabilă-predicat și transmisă prin Binding. Când utilizatorul schimbă filtrul — de exemplu, selectează o categorie de sarcini — predicatul se actualizează, iar selectarea se repornește automat.
Predicatele dinamice se implementează prin proprietatea @State sau @Published, care este transmisă ca Binding la inițializatorul @FetchRequest. SwiftUI urmărește modificarea acestui Binding și recrează NSFetchRequest cu noul predicat, actualizând automat lista.
Să analizăm un exemplu complet de aplicație jurnal cu @FetchRequest pentru selectarea înregistrărilor. Formularul de căutare folosește un predicat dinamic: la introducerea textului, FetchRequest actualizează automat rezultatele, filtrând înregistrările după titlu și conținut. Sortare — după data creării, de la cele mai noi la cele mai vechi.
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)
}
}
Jurnal de înregistrări cu căutare: la introducerea textului în câmpul searchable, se creează NSPredicate cu CONTAINS[c] (căutare fără sensibilitate la majuscule) în câmpurile title și content. Init primește searchText și creează FetchRequest cu predicatul corespunzător. Modificatorul Searchable actualizează automat searchText, iar @FetchRequest reacționează la modificarea predicatului și reinteroghează datele.
Pentru scenarii mai complexe cu filtre multiple, combinați NSCompoundPredicate cu logica AND/OR. @FetchRequest suportă predicate de orice complexitate, inclusiv subinterogări imbricate (SUBQUERY) pentru filtrarea după relații. Totuși, rețineți: predicatele complexe afectează performanța — pentru seturi mari de date (peste 10 000 de înregistrări) utilizați indexarea Core Data și limitați selectarea prin fetchLimit.
Întrebări frecvente
Adăugați @Environment(\.managedObjectContext) var viewContext în View-ul rădăcină și transmiteți-l prin modificatorul .environment(\.managedObjectContext, context). Toate View-urile copil care folosesc @FetchRequest vor primi automat contextul din mediu.
Da, modificați contextul Core Data: adăugați, ștergeți sau actualizați obiectul și apelați save(). @FetchRequest va executa automat o reselectare și va actualiza UI. Pentru actualizare forțată fără save() utilizați viewContext.refreshAllObjects().
@FetchRequest returnează un array plat de obiecte. @SectionedFetchRequest grupează rezultatele după key path-ul specificat în secțiuni, similar cu UITableView cu sectionNameKeyPath. Utilizați @SectionedFetchRequest pentru liste grupate cu titluri de secțiuni.
Pentru filtrarea după relații utilizați notația cu punct: NSPredicate(format: „category.name == %@", „Work"). @FetchRequest execută automat JOIN în SQLite. Pentru accesul la relație prin obiectul selectat, utilizați proprietățile obișnuite NSManagedObject.
@FetchRequest nu suportă paginarea direct, deoarece este destinat afișării tuturor înregistrărilor cu actualizare reactivă. Pentru paginare utilizați NSFetchRequest cu fetchLimit/fetchOffset și încărcați datele manual la derularea listei.
Rezumat
Vom dezvolta o aplicație mobilă la cheie
IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.
Citiți și