@FetchRequest w SwiftUI — co to jest, pobieranie Core Data i mechanizm

Autor: IT Sectr Opublikowano: 2026-06-25 Czas czytania: 8 min

@FetchRequest w SwiftUI — property wrapper do automatycznego pobierania danych z Core Data z reaktywnym aktualizowaniem interfejsu. Wrapper przyjmuje NSFetchRequest z predykatami filtrowania i sortowaniem, a wynik — tablica NSManagedObject — jest automatycznie aktualizowany przy każdej zmianie w kontekście Core Data. Według Apple Developer Documentation (2025), @FetchRequest wymaga managedObjectContext w środowisku SwiftUI i obsługuje dynamiczne predykaty, zapewniając reaktywne połączenie między Core Data a interfejsem użytkownika.

Najważniejsze

  • @FetchRequest — property wrapper do reaktywnego pobierania Core Data w SwiftUI
  • Automatyczna aktualizacja UI przy dodawaniu, usuwaniu lub zmianie rekordów w Core Data
  • Predykaty i sortowanie ustawiane w inicjalizatorze lub przez parametry property wrapper
  • managedObjectContext musi być dostępny przez @Environment w hierarchii View
  • Dynamiczne predykaty pozwalają zmieniać warunki filtrowania w czasie wykonania

Co to jest @FetchRequest w SwiftUI?

@FetchRequest — to property wrapper SwiftUI, który tworzy i zarządza FetchRequest z Core Data. Podczas inicjalizacji wrapper przyjmuje typ encji, tablicę NSSortDescriptor do sortowania i opcjonalny NSPredicate do filtrowania. Wynik jest przechowywany jako tablica obiektów określonego typu NSManagedObject i automatycznie aktualizowany przy zmianach w warstwie trwałości.

Kluczową cechą @FetchRequest jest reaktywność. Gdy dane w Core Data zmieniają się (save, insert, delete, update), SwiftUI automatycznie wykonuje ponowne pobieranie i przerysowuje wszystkie View używające tego fetch request. Deweloper nie musi ręcznie wywoływać refresh, subskrybować NSManagedObjectContextDidSaveNotification ani przeładowywać danych. SwiftUI zarządza całym cyklem synchronizacji.

Do działania @FetchRequest wymaga NSManagedObjectContext dostępnego w środowisku SwiftUI przez @Environment(\.managedObjectContext). Kontekst jest zwykle przekazywany z korzenia aplikacji, gdzie tworzony jest NSPersistentContainer. Standardowy wzorzec — użycie ViewModel lub App struct do skonfigurowania stosu Core Data i przekazania kontekstu przez modyfikator .environment().

Sortowanie i predykaty FetchRequest

@FetchRequest przyjmuje kilka parametrów do konfiguracji pobierania. SortDescriptors — tablica NSSortDescriptor określająca kolejność rekordów. Predicate — NSPredicate do filtrowania, na przykład pobieranie tylko zadań ze statusem „ukończone”. Animation — animacja do aktualizacji listy przy zmianach danych.

Predykaty Core Data używają formatu wyrażeń podobnych do SQL: NSPredicate(format: „status == %@”, „completed”). Można łączyć wiele warunków przez AND, OR i NOT. SortDescriptors określają kolejność: NSSortDescriptor(keyPath: \Task.dueDate, ascending: true). Do sortowania po wielu polach przekazywana jest tablica deskryptorów — pierwszy stosowany jest jako główny, pozostałe jako drugorzędne.

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

W przykładzie @FetchRequest wybiera wszystkie niezakończone zadania (isCompleted == NO), posortowane najpierw według daty (rosnąco), następnie według priorytetu (malejąco). FetchedResults<Task> — to automatycznie aktualizowana kolekcja, która zachowuje się jak tablica dla ForEach i List. Animacja .default zapewnia płynne pojawianie/znikanie rekordów przy zmianach.

@FetchRequest z List i dynamicznymi danymi

@FetchRequest idealnie łączy się z List i ForEach do wyświetlania dynamicznych list Core Data. Wynik pobierania (FetchedResults) jest zgodny z protokołem RandomAccessCollection, więc można go używać bezpośrednio w List i ForEach. Przy dodawaniu, usuwaniu lub zmianie rekordów lista jest automatycznie przerysowywana z animacją.

Do usuwania rekordów używana jest metoda kontekstu delete. Do dodawania — utworzenie nowego NSManagedObject i wywołanie save. Wszystkie zmiany są wykonywane przez viewContext, który wraz z @FetchRequest zapewnia spójność danych. Do edycji używany jest @ObservedObject lub @Bindable dla konkretnego obiektu 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: "dodaj") { 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()
    }
}

Ten przykład demonstruje pełny CRUD z @FetchRequest. Dodawanie: utworzenie Task przez kontekst i save(). Usuwanie: delete() na kontekście i save(). Aktualizacja: toggle właściwości i save(). Po każdym save() @FetchRequest automatycznie ponownie pobiera dane, a List aktualizuje się z animacją. Modyfikatory onDelete i onTapGesture zapewniają standardowe interakcje iOS.

Przykłady kodu @FetchRequest

Rozważmy pełny przykład aplikacji dziennika z @FetchRequest do pobierania wpisów. Formularz wyszukiwania używa dynamicznego predykatu: podczas wprowadzania tekstu FetchRequest automatycznie aktualizuje wyniki, filtrując wpisy według tytułu i zawartości. Sortowanie — według daty utworzenia, od najnowszych do najstarszych.

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

Dziennik wpisów z wyszukiwarką: podczas wpisywania tekstu w polu searchable tworzony jest NSPredicate z CONTAINS[c] (wyszukiwanie bez uwzględniania wielkości liter) po polach title i content. Init przyjmuje searchText i tworzy FetchRequest z odpowiednim predykatem. Modyfikator Searchable automatycznie aktualizuje searchText, a @FetchRequest reaguje na zmianę predykatu i ponownie pobiera dane.

W przypadku bardziej złożonych scenariuszy z wieloma filtrami łączcie NSCompoundPredicate z logiką AND/OR. @FetchRequest obsługuje predykaty o dowolnej złożoności, w tym zagnieżdżone podzapytania (SUBQUERY) do filtrowania po relacjach. Należy jednak pamiętać: złożone predykaty wpływają na wydajność — dla dużych zbiorów danych (ponad 10 000 rekordów) używajcie indeksowania Core Data i ograniczajcie pobieranie przez fetchLimit.

Dynamiczne predykaty w czasie wykonania

@FetchRequest obsługuje dynamiczne predykaty przez parametr predicate, który można zmieniać w czasie wykonania. W tym celu należy utworzyć @FetchRequest ze zmienną-predykatem i przekazać ją przez Binding. Gdy użytkownik zmienia filtr — na przykład wybiera kategorię zadań — predykat jest aktualizowany, a pobieranie automatycznie uruchamiane ponownie.

Dynamiczne predykaty są implementowane przez @State lub @Published właściwość, która jest przekazywana jako Binding do inicjalizatora @FetchRequest. SwiftUI śledzi zmianę tego Binding i odtwarza NSFetchRequest z nowym predykatem, automatycznie aktualizując listę.

Często zadawane pytania

Jak przekazać managedObjectContext do @FetchRequest?

Dodajcie @Environment(\.managedObjectContext) var viewContext w korzeniowym View i przekarzcie go przez modyfikator .environment(\.managedObjectContext, context). Wszystkie podrzędne View używające @FetchRequest automatycznie otrzymają kontekst ze środowiska.

Czy można zaktualizować @FetchRequest bez przeładowywania View?

Tak, zmieńcie kontekst Core Data: dodajcie, usuńcie lub zaktualizujcie obiekt i wywołajcie save(). @FetchRequest automatycznie wykona ponowne pobieranie i zaktualizuje UI. Do wymuszenia aktualizacji bez save() użyjcie viewContext.refreshAllObjects().

Jaka jest różnica między @FetchRequest a @SectionedFetchRequest?

@FetchRequest zwraca płaską tablicę obiektów. @SectionedFetchRequest grupuje wyniki według określonego key path w sekcje, podobnie jak UITableView z sectionNameKeyPath. Używajcie @SectionedFetchRequest do grouped-list z nagłówkami sekcji.

Jak używać @FetchRequest z Relationship?

Do filtrowania po relacjach używajcie notacji kropkowej: NSPredicate(format: „category.name == %@”, „Work”). @FetchRequest automatycznie wykonuje JOIN w SQLite. Do dostępu do relacji przez wybrany obiekt używajcie zwykłych właściwości NSManagedObject.

Czy @FetchRequest obsługuje paginację?

@FetchRequest nie obsługuje paginacji bezpośrednio, ponieważ jest przeznaczony do wyświetlania wszystkich rekordów z reaktywną aktualizacją. Do paginacji używajcie NSFetchRequest z fetchLimit/fetchOffset i ładujcie dane ręcznie podczas przewijania listy.

Podsumowanie

  • @FetchRequest — property wrapper do automatycznego reaktywnego pobierania Core Data w SwiftUI
  • Sortowanie ustawiane tablicą NSSortDescriptor, filtrowanie — NSPredicate
  • Automatyczna aktualizacja UI przy każdej zmianie danych w kontekście Core Data
  • managedObjectContext przekazywany przez @Environment w hierarchii View
  • Dynamiczne predykaty implementowane przez custom init z parametrem-predykatem
  • Operacje CRUD wykonywane przez viewContext z automatyczną synchronizacją listy
  • Używajcie @FetchRequest do wyświetlania Core Data, @SectionedFetchRequest — do grouped-list

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również