@FetchRequest in SwiftUI — a property wrapper for automatically fetching data from Core Data with reactive UI updates. The wrapper accepts NSFetchRequest with filter predicates and sort descriptors, and the result — an array of NSManagedObject — automatically updates on any changes in the Core Data context. According to Apple Developer Documentation (2025), @FetchRequest requires managedObjectContext in the SwiftUI environment and supports dynamic predicates, providing a reactive connection between Core Data and the user interface.
Key Takeaways
@FetchRequest is a SwiftUI property wrapper that creates and manages a FetchRequest from Core Data. On initialization, the wrapper accepts an entity type, an array of NSSortDescriptor for sorting, and an optional NSPredicate for filtering. The result is stored as an array of specified NSManagedObject type objects and automatically updates when changes occur in the persistence layer.
The key feature of @FetchRequest is reactivity. When data in Core Data changes (save, insert, delete, update), SwiftUI automatically performs a re-fetch and redraws all Views using this fetch request. The developer does not need to manually call refresh, subscribe to NSManagedObjectContextDidSaveNotification, or reload data. SwiftUI manages the entire synchronization cycle.
For @FetchRequest to work, it requires an NSManagedObjectContext available in the SwiftUI environment through @Environment(\.managedObjectContext). The context is typically passed from the application root, where an NSPersistentContainer is created. The standard pattern is using a ViewModel or App struct to configure the Core Data stack and pass the context through the .environment() modifier.
@FetchRequest accepts several parameters for configuring the fetch. SortDescriptors — an array of NSSortDescriptor that determines the order of records. Predicate — an NSPredicate for filtering, for example, fetching only tasks with the status “completed”. Animation — animation for updating the list when data changes.
Core Data predicates use a format of SQL-like expressions: NSPredicate(format: “status == %@”, “completed”). You can combine multiple conditions using AND, OR, and NOT. SortDescriptors define the order: NSSortDescriptor(keyPath: \Task.dueDate, ascending: true). For sorting by multiple fields, an array of descriptors is passed — the first one is applied as primary, the rest as secondary.
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 ?? "")
}
}
}
In the example, @FetchRequest fetches all incomplete tasks (isCompleted == NO), sorted first by date (ascending), then by priority (descending). FetchedResults<Task> is an automatically updating collection that behaves like an array for ForEach and List. The .default animation ensures smooth appearance/disappearance of records on changes.
@FetchRequest pairs perfectly with List and ForEach for displaying dynamic Core Data lists. The fetch result (FetchedResults) conforms to the RandomAccessCollection protocol, so it can be used directly in List and ForEach. When records are added, deleted, or modified, the list automatically redraws with animation.
To delete records, the context method delete is used. To add records, a new NSManagedObject is created and save is called. All changes are performed through viewContext, which together with @FetchRequest ensures data consistency. For editing, @ObservedObject or @Bindable is used for a specific Core Data object.
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()
}
}
This example demonstrates full CRUD with @FetchRequest. Adding: creating a Task through context and save(). Deleting: delete() on context and save(). Updating: toggling a property and save(). After each save(), @FetchRequest automatically re-fetches the data, and the List updates with animation. The onDelete and onTapGesture modifiers provide standard iOS interactions.
Let’s look at a full-featured journal application example with @FetchRequest for fetching records. The search form uses a dynamic predicate: as text is entered, FetchRequest automatically updates the results, filtering records by title and content. Sorting is by creation date, from newest to oldest.
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)
}
}
A searchable journal: as text is entered in the searchable field, an NSPredicate is created with CONTAINS[c] (case-insensitive search) on the title and content fields. Init accepts searchText and creates a FetchRequest with the corresponding predicate. The searchable modifier automatically updates searchText, and @FetchRequest reacts to the predicate change and re-fetches the data.
For more complex scenarios with multiple filters, combine NSCompoundPredicate with AND/OR logic. @FetchRequest supports predicates of any complexity, including nested subqueries (SUBQUERY) for filtering by relationship. However, keep in mind: complex predicates affect performance — for large datasets (over 10,000 records), use Core Data indexing and limit the fetch through fetchLimit.
@FetchRequest supports dynamic predicates through the predicate parameter, which can be changed at runtime. To do this, you create an @FetchRequest with a variable predicate and pass it via Binding. When the user changes the filter — for example, selects a task category — the predicate updates, and the fetch automatically restarts.
Dynamic predicates are implemented through an @State or @Published property, which is passed as a Binding to the @FetchRequest initializer. SwiftUI tracks changes to this Binding and recreates the NSFetchRequest with the new predicate, automatically updating the list.
Frequently Asked Questions
Add @Environment(\.managedObjectContext) var viewContext to the root View and pass it through the .environment(\.managedObjectContext, context) modifier. All child Views using @FetchRequest will automatically receive the context from the environment.
Yes, modify the Core Data context: add, delete, or update an object and call save(). @FetchRequest will automatically perform a re-fetch and update the UI. For a forced update without save(), use viewContext.refreshAllObjects().
@FetchRequest returns a flat array of objects. @SectionedFetchRequest groups results by a specified key path into sections, similar to UITableView with sectionNameKeyPath. Use @SectionedFetchRequest for grouped lists with section headers.
For filtering by relationship, use dot notation: NSPredicate(format: “category.name == %@”, “Work”). @FetchRequest automatically performs a JOIN in SQLite. To access a relationship through a selected object, use regular NSManagedObject properties.
@FetchRequest does not support pagination directly, as it is designed for displaying all records with reactive updates. For pagination, use NSFetchRequest with fetchLimit/fetchOffset and manually load data as the list scrolls.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also