@FetchRequest in SwiftUI — what it is, Core Data fetching and mechanism

Author: IT Sectr Published: 2026-06-25 Reading time: 8 min

@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 — a property wrapper for reactive Core Data fetching in SwiftUI
  • Auto-update UI when records are added, deleted or modified in Core Data
  • Predicates and sort descriptors are set in the initializer or via property wrapper parameters
  • managedObjectContext must be available through @Environment in the View hierarchy
  • Dynamic predicates allow changing filter conditions at runtime

What is @FetchRequest in SwiftUI?

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

Sorting and Predicates of FetchRequest

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

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 ?? "")
        }
    }
}

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 with List and Dynamic Data

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

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

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.

@FetchRequest Code Examples

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.

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)
    }
}

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.

Dynamic Predicates at Runtime

@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

How to pass managedObjectContext to @FetchRequest?

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.

Can @FetchRequest be updated without reloading the View?

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

What is the difference between @FetchRequest and @SectionedFetchRequest?

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

How to use @FetchRequest with Relationships?

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.

Does @FetchRequest support pagination?

@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

  • @FetchRequest — a property wrapper for automatic reactive Core Data fetching in SwiftUI
  • Sorting is set by an array of NSSortDescriptor, filtering by NSPredicate
  • Auto-update UI on any data changes in the Core Data context
  • managedObjectContext is passed through @Environment in the View hierarchy
  • Dynamic predicates are implemented through a custom init with a predicate parameter
  • CRUD operations are performed through viewContext with automatic list synchronization
  • Use @FetchRequest for displaying Core Data, @SectionedFetchRequest for grouped lists

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.

Discuss the project

Read also