@FetchRequest у SwiftUI — що це, вибірка Core Data та механізм

Автор: IT Sectr Опубліковано: 2026-06-25 Час читання: 8 хв

@FetchRequest у SwiftUI — property wrapper для автоматичної вибірки даних із Core Data з реактивним оновленням інтерфейсу. Wrapper приймає NSFetchRequest з предикатами фільтрації та сортуваннями, а результат — масив NSManagedObject — автоматично оновлюється при будь-яких змінах у контексті Core Data. За даними Apple Developer Documentation (2025), @FetchRequest вимагає managedObjectContext в оточенні SwiftUI та підтримує динамічні предикати, забезпечуючи реактивний зв’язок між Core Data та користувацьким інтерфейсом.

Головне

  • @FetchRequest — property wrapper для реактивної вибірки Core Data у SwiftUI
  • Автооновлення інтерфейсу при додаванні, видаленні або зміні записів у Core Data
  • Предикати та сортування задаються в ініціалізаторі або через параметри property wrapper
  • managedObjectContext має бути доступним через @Environment в ієрархії View
  • Динамічні предикати дозволяють змінювати умови фільтрації під час виконання

Що таке @FetchRequest у SwiftUI?

@FetchRequest — це property wrapper SwiftUI, який створює та керує FetchRequest із Core Data. При ініціалізації wrapper приймає тип сутності, масив NSSortDescriptor для сортування та опціональний NSPredicate для фільтрації. Результат зберігається як масив об’єктів указаного типу NSManagedObject і автоматично оновлюється при змінах у persistence layer.

Ключова особливість @FetchRequest — реактивність. Коли дані в Core Data змінюються (save, insert, delete, update), SwiftUI автоматично виконує повторну вибірку та перемальовує всі View, що використовують цей fetch request. Розробнику не потрібно вручну викликати refresh, підписуватися на NSManagedObjectContextDidSaveNotification або перезавантажувати дані. SwiftUI керує всім циклом синхронізації.

Для роботи @FetchRequest потрібен NSManagedObjectContext, доступний в оточенні SwiftUI через @Environment(\.managedObjectContext). Контекст зазвичай передається з кореня застосунку, де створюється NSPersistentContainer. Стандартний патерн — використання ViewModel або App struct для налаштування стека Core Data та передачі контексту через модифікатор .environment().

Сортування та предикати FetchRequest

@FetchRequest приймає кілька параметрів для налаштування вибірки. SortDescriptors — масив NSSortDescriptor, що визначає порядок записів. Predicate — NSPredicate для фільтрації, наприклад, вибірка лише завдань зі статусом «виконано». Animation — анімація для оновлення списку при змінах даних.

Предикати Core Data використовують формат SQL-подібних виразів: NSPredicate(format: «status == %@», «completed»). Можна комбінувати кілька умов через AND, OR та NOT. SortDescriptors задають порядок: NSSortDescriptor(keyPath: \Task.dueDate, ascending: true). Для сортування за кількома полями передається масив дескрипторів — перший застосовується як основний, решта як вторинні.

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

У прикладі @FetchRequest вибирає всі незавершені завдання (isCompleted == NO), відсортовані спочатку за датою (зростання), потім за пріоритетом (спадання). FetchedResults<Task> — це колекція, що автоматично оновлюється та поводиться як масив для ForEach і List. Анімація .default забезпечує плавну появу/зникнення записів при змінах.

@FetchRequest із List та динамічними даними

@FetchRequest ідеально поєднується з List та ForEach для відображення динамічних списків Core Data. Результат вибірки (FetchedResults) відповідає протоколу RandomAccessCollection, тому його можна використовувати безпосередньо в List та ForEach. При додаванні, видаленні або зміні записів список автоматично перемальовується з анімацією.

Для видалення записів використовується метод контексту delete. Для додавання — створення нового NSManagedObject та виклик save. Усі зміни виконуються через viewContext, який разом із @FetchRequest забезпечує консистентність даних. Для редагування використовується @ObservedObject або @Bindable для конкретного об’єкта Core Data.

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

Цей приклад демонструє повний CRUD із @FetchRequest. Додавання: створення Task через контекст і save(). Видалення: delete() на контексті та save(). Оновлення: перемикання властивості та save(). Після кожного save() @FetchRequest автоматично перезапитує дані, і List анімовано оновлюється. Модифікатори onDelete та onTapGesture надають стандартні взаємодії iOS.

Приклади коду @FetchRequest

Розглянемо повноцінний приклад застосунку-журналу з @FetchRequest для вибірки записів. Форма пошуку використовує динамічний предикат: при введенні тексту FetchRequest автоматично оновлює результати, фільтруючи записи за заголовком та вмістом. Сортування — за датою створення, від нових до старих.

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

Журнал записів із пошуком: при введенні тексту в searchable-поле створюється NSPredicate із CONTAINS[c] (регістронезалежний пошук) за полями title та content. Init приймає searchText та створює FetchRequest із відповідним предикатом. Searchable-модифікатор автоматично оновлює searchText, а @FetchRequest реагує на зміну предиката та перезапитує дані.

Для складніших сценаріїв із множинними фільтрами комбінуйте NSCompoundPredicate з логікою AND/OR. @FetchRequest підтримує предикати будь-якої складності, включаючи вкладені підзапити (SUBQUERY) для фільтрації за relationship. Однак пам’ятайте: складні предикати впливають на продуктивність — для великих наборів даних (понад 10 000 записів) використовуйте індексацію Core Data та обмежуйте вибірку через fetchLimit.

Динамічні предикати під час виконання

@FetchRequest підтримує динамічні предикати через параметр predicate, який можна змінювати під час виконання. Для цього потрібно створити @FetchRequest зі змінною-предикатом і передати її через Binding. Коли користувач змінює фільтр — наприклад, вибирає категорію завдань — предикат оновлюється, і вибірка автоматично перезапускається.

Динамічні предикати реалізуються через @State або @Published властивість, яка передається як Binding в ініціалізатор @FetchRequest. SwiftUI відстежує зміну цього Binding і перестворює NSFetchRequest із новим предикатом, автоматично оновлюючи список.

Часті запитання

Як передати managedObjectContext у @FetchRequest?

Додайте @Environment(\.managedObjectContext) var viewContext у кореневий View та передайте його через модифікатор .environment(\.managedObjectContext, context). Усі дочірні View, що використовують @FetchRequest, автоматично отримають контекст з оточення.

Чи можна оновити @FetchRequest без перезавантаження View?

Так, змініть контекст Core Data: додайте, видаліть або оновіть об’єкт і викличте save(). @FetchRequest автоматично виконає повторну вибірку та оновить інтерфейс. Для примусового оновлення без save() використовуйте viewContext.refreshAllObjects().

У чому різниця між @FetchRequest та @SectionedFetchRequest?

@FetchRequest повертає плаский масив об’єктів. @SectionedFetchRequest групує результати за вказаним key path у секції, аналогічно UITableView із sectionNameKeyPath. Використовуйте @SectionedFetchRequest для grouped-списків із заголовками секцій.

Як використовувати @FetchRequest з Relationship?

Для фільтрації за relationship використовуйте крапкову нотацію: NSPredicate(format: «category.name == %@», «Work»). @FetchRequest автоматично виконує JOIN у SQLite. Для доступу до relationship через вибраний об’єкт використовуйте звичайні властивості NSManagedObject.

Чи підтримує @FetchRequest пагінацію?

@FetchRequest не підтримує пагінацію безпосередньо, оскільки призначений для відображення всіх записів із реактивним оновленням. Для пагінації використовуйте NSFetchRequest із fetchLimit/fetchOffset та підвантажуйте дані вручну під час прокручування списку.

Підсумки

  • @FetchRequest — property wrapper для автоматичної реактивної вибірки Core Data у SwiftUI
  • Сортування задається масивом NSSortDescriptor, фільтрація — NSPredicate
  • Автооновлення інтерфейсу при будь-яких змінах даних у контексті Core Data
  • managedObjectContext передається через @Environment в ієрархії View
  • Динамічні предикати реалізуються через кастомний init із параметром-предикатом
  • CRUD операції виконуються через viewContext з автоматичною синхронізацією списку
  • Використовуйте @FetchRequest для відображення Core Data, @SectionedFetchRequest — для grouped-списків

Ми розробимо мобільний застосунок під ключ

IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.

Обговорити проект

Читайте також