Fetching Data With Core Data

Fetching data is a fundamental part of working with Core Data in SwiftUI. SwiftUI’s @FetchRequest makes it incredibly easy

article

Fetching data is a fundamental part of working with Core Data in SwiftUI. SwiftUI’s @FetchRequest makes it incredibly easy to fetch and display data, automatically updating the UI when changes occur in the underlying data store. In this article, we’ll cover how to perform basic and advanced fetch operations, how to filter and sort data, and how to optimize fetch requests to ensure your app remains responsive even with large datasets.


Basic Fetching with @FetchRequest

The most common way to fetch data in SwiftUI with Core Data is by using the @FetchRequest property wrapper. This wrapper automatically fetches data from the persistent store and keeps the SwiftUI view in sync with the Core Data store. Here’s an example of a basic fetch request to display a list of tasks:



struct TaskListView: View {
    @FetchRequest(
        entity: Task.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \Task.name, ascending: true)]
    ) private var tasks: FetchedResults

    var body: some View {
        List {
            ForEach(tasks) { task in
                Text(task.name ?? "Unknown Task")
            }
        }
    }
}

In this example, the @FetchRequest fetches all Task entities from the Core Data store and sorts them by their name attribute in ascending order. The FetchedResults is automatically updated when there are changes to the data, ensuring that the UI remains in sync with the data in Core Data.


Filtering Data with NSPredicate

Sometimes, you’ll want to fetch only a subset of your data, based on certain criteria. In Core Data, you can achieve this by using an NSPredicate to filter your results. Here’s how to use a predicate to fetch only tasks that are marked as completed:



struct CompletedTaskListView: View {
    @FetchRequest(
        entity: Task.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \Task.name, ascending: true)],
        predicate: NSPredicate(format: "isCompleted == YES")
    ) private var tasks: FetchedResults

    var body: some View {
        List {
            ForEach(tasks) { task in
                Text(task.name ?? "Unknown Task")
            }
        }
    }
}

In this example, the NSPredicate is used to filter tasks based on their isCompleted attribute. The predicate filters the fetch request to return only the tasks where isCompleted is set to YES. Predicates are highly flexible and allow for more complex queries, such as filtering by multiple conditions or performing comparisons on date or numeric fields.


Sorting Data

Sorting data in Core Data is done using NSSortDescriptor. You can specify multiple sort descriptors to sort the data by more than one field. Here’s an example where tasks are sorted by their priority first, and then by name in ascending order:



struct SortedTaskListView: View {
    @FetchRequest(
        entity: Task.entity(),
        sortDescriptors: [
            NSSortDescriptor(keyPath: \Task.priority, ascending: false),
            NSSortDescriptor(keyPath: \Task.name, ascending: true)
        ]
    ) private var tasks: FetchedResults

    var body: some View {
        List {
            ForEach(tasks) { task in
                Text("\(task.name ?? "Unknown Task") - Priority: \(task.priority)")
            }
        }
    }
}

Here, we first sort tasks by their priority in descending order (high priority tasks first), and then by name in ascending order. SwiftUI will update the list view whenever the sorting criteria or data changes.


Dynamic Fetching Based on User Input

In some cases, you may need to perform dynamic fetch requests based on user input. For example, allowing users to search tasks by name. While @FetchRequest doesn’t natively support dynamic predicates, we can work around this limitation by using a NSFetchedResultsController or by manually triggering fetch requests in response to user input. Here’s an example using a search bar with a dynamically updated fetch request:



struct SearchableTaskListView: View {
    @Environment(\.managedObjectContext) private var viewContext
    @State private var searchText = ""
    @State private var tasks: [Task] = []

    var body: some View {
        VStack {
            TextField("Search tasks", text: $searchText, onCommit: fetchTasks)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .padding()

            List(tasks, id: \.self) { task in
                Text(task.name ?? "Unknown Task")
            }
        }
        .onAppear(perform: fetchTasks)
    }

    private func fetchTasks() {
        let request: NSFetchRequest = Task.fetchRequest()
        request.sortDescriptors = [NSSortDescriptor(keyPath: \Task.name, ascending: true)]
        
        if !searchText.isEmpty {
            request.predicate = NSPredicate(format: "name CONTAINS[cd] %@", searchText)
        }

        do {
            tasks = try viewContext.fetch(request)
        } catch {
            print("Failed to fetch tasks: \(error.localizedDescription)")
        }
    }
}

In this example, the user can enter text into a search bar, and the app will dynamically filter tasks based on the search query. The fetch request is performed manually when the search text changes or when the view first appears. The tasks list is updated with the results of the fetch request.


Fetching Large Datasets Efficiently

When working with large datasets in Core Data, you should take care to ensure that your app remains responsive. Core Data allows you to limit the number of records fetched at once, which can prevent performance bottlenecks in your SwiftUI views. Here’s an example where we limit the fetch request to return only the first 20 tasks:



struct LimitedTaskListView: View {
    @FetchRequest(
        entity: Task.entity(),
        sortDescriptors: [NSSortDescriptor(keyPath: \Task.name, ascending: true)],
        fetchLimit: 20
    ) private var tasks: FetchedResults

    var body: some View {
        List {
            ForEach(tasks) { task in
                Text(task.name ?? "Unknown Task")
            }
        }
    }
}

In this example, the fetchLimit parameter is used to limit the number of tasks fetched to 20. This helps improve performance by only loading a small subset of tasks at a time, which is especially useful when dealing with large datasets.


Asynchronous Fetching in Core Data

SwiftUI performs all UI updates on the main thread, but for long-running fetch operations on large datasets, it’s best to perform the fetch asynchronously to avoid blocking the UI. Core Data supports asynchronous fetching, which can help keep your app responsive. Here's how to perform an asynchronous fetch in SwiftUI:



func fetchTasksAsync() {
    let fetchRequest: NSFetchRequest = Task.fetchRequest()
    fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \Task.name, ascending: true)]

    let asyncFetchRequest = NSAsynchronousFetchRequest(fetchRequest: fetchRequest) { result in
        guard let tasks = result.finalResult else { return }
        DispatchQueue.main.async {
            self.tasks = tasks
        }
    }

    do {
        try viewContext.execute(asyncFetchRequest)
    } catch {
        print("Failed to fetch tasks: \(error.localizedDescription)")
    }
}

In this example, we use NSAsynchronousFetchRequest to fetch tasks in the background, allowing the UI to remain responsive. Once the fetch is complete, the results are returned to the main thread to update the UI.


Conclusion

Fetching data in Core Data with SwiftUI is incredibly powerful and flexible. With the @FetchRequest property wrapper, you can easily retrieve and display data while keeping the UI in sync with the underlying data store. More advanced techniques, such as filtering with predicates, dynamic fetching, and asynchronous fetch requests, help you build responsive and scalable apps. In the next article, we’ll dive deeper into managing relationships between entities in Core Data and how to display related data in SwiftUI views.


In this article, we explored various ways to fetch data in Core Data using SwiftUI. We covered basic fetching with @FetchRequest, filtering with NSPredicate, sorting data, and handling dynamic and asynchronous fetches. These techniques ensure that your app can efficiently manage and display data from Core Data in a SwiftUI environment. In the next article, we’ll focus on handling relationships between entities in Core Data and displaying related data in SwiftUI.

instructor

Exodai INSTRUCTOR!

Johan t'Sas

Owner and Swift developer!