@FetchRequest в SwiftUI — property wrapper для автоматической выборки данных из Core Data с реактивным обновлением интерфейса. Wrapper принимает NSFetchRequest с предикатами фильтрации и сортировками, а результат — массив NSManagedObject — автоматически обновляется при любых изменениях в контексте Core Data. По данным Apple Developer Documentation (2025), @FetchRequest требует managedObjectContext в окружении SwiftUI и поддерживает динамические предикаты, обеспечивая реактивную связь между Core Data и пользовательским интерфейсом.
Главное
@FetchRequest — это property wrapper SwiftUI, создающий и управляющий FetchRequest из Core Data. При инициализации wrapper принимает entity type, массив 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 stack и передачи контекста через .environment() модификатор.
@FetchRequest принимает несколько параметров для настройки выборки. SortDescriptors — массив NSSortDescriptor, определяющий порядок записей. Predicate — NSPredicate для фильтрации, например, выборка только задач со статусом «выполнено». Animation — анимация для обновления списка при изменениях данных.
Предикаты Core Data используют формат SQL-подобных выражений: NSPredicate(format: "status == %@", "completed"). Можно комбинировать несколько условий через AND, OR и NOT. SortDescriptors задают порядок: NSSortDescriptor(keyPath: \Task.dueDate, ascending: true). Для сортировки по нескольким полям передаётся массив дескрипторов — первый применяется как основной, остальные как вторичные.
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 и ForEach для отображения динамических списков Core Data. Результат выборки (FetchedResults) соответствует протоколу RandomAccessCollection, поэтому его можно использовать напрямую в List и ForEach. При добавлении, удалении или изменении записей список автоматически перерисовывается с анимацией.
Для удаления записей используется метод контекста delete. Для добавления — создание нового NSManagedObject и вызов save. Все изменения выполняются через viewContext, который вместе с @FetchRequest обеспечивает консистентность данных. Для редактирования используется @ObservedObject или @Bindable для конкретного объекта Core Data.
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(). Обновление: toggle свойства и save(). После каждого save() @FetchRequest автоматически перезапрашивает данные, и List анимированно обновляется. Модификаторы onDelete и onTapGesture предоставляют стандартные iOS-взаимодействия.
Рассмотрим полноценный пример приложения-журнала с @FetchRequest для выборки записей. Форма поиска использует динамический предикат: при вводе текста FetchRequest автоматически обновляет результаты, фильтруя записи по заголовку и содержимому. Сортировка — по дате создания, от новых к старым.
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 с новым предикатом, автоматически обновляя список.
Часто задаваемые вопросы
Добавьте @Environment(\.managedObjectContext) var viewContext в корневой View и передайте его через модификатор .environment(\.managedObjectContext, context). Все дочерние View, использующие @FetchRequest, автоматически получат контекст из окружения.
Да, измените контекст Core Data: добавьте, удалите или обновите объект и вызовите save(). @FetchRequest автоматически выполнит повторную выборку и обновит UI. Для принудительного обновления без save() используйте viewContext.refreshAllObjects().
@FetchRequest возвращает плоский массив объектов. @SectionedFetchRequest группирует результаты по указанному key path в секции, аналогично UITableView с sectionNameKeyPath. Используйте @SectionedFetchRequest для grouped-списков с заголовками секций.
Для фильтрации по relationship используйте точечную нотацию: NSPredicate(format: "category.name == %@", "Work"). @FetchRequest автоматически выполняет JOIN в SQLite. Для доступа к relationship через выбранный объект используйте обычные свойства NSManagedObject.
@FetchRequest не поддерживает пагинацию напрямую, так как предназначен для отображения всех записей с реактивным обновлением. Для пагинации используйте NSFetchRequest с fetchLimit/fetchOffset и подгружайте данные вручную при прокрутке списка.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также