SwiftUI and @FetchRequest - modify predicate or sort dynamically?

Has anyone had any luck using the @FetchRequest modifier to set up a set of @FetchedResults using SwiftUI and Core Data and then dynamically modifying those results during runtime by changing the predicate and/or sort descriptors? Is there any way to do this using an elegent approach like the @FetchRequest combine pipeline? Thanks!

Replies

Perhaps not an elegant solution, but you can set the fetch request in the init of your View.

Code Block swift
private var filteredFetchRequest: FetchRequest<Item>
private var filteredItems: FetchedResults<Item> {
filteredFetchRequest.wrappedValue
}
init(filter: String) {
filteredFetchRequest = FetchRequest<Item>(entity: Item.entity(), sortDescriptors: [], predicate: NSPredicate(format: "title BEGINSWITH %@", filter))
}


Maybe there's a better way to do it, but have a look: https://github.com/AlmightyBeaver/Dynamic-Predicate-CoreData-SwiftUI
Another way:

Code Block
@FetchRequest var filteredItems: FetchedResults<Item>
init(filter: String) {
_filteredItems = FetchRequest(sortDescriptors: [], predicate: NSPredicate(format: "title BEGINSWITH %@", filter))
}


Or you could pass a fetch request in from superview and it could even be @State there. Turn on SQL debugging and check how often you are hitting the database. I wish Apple would help us out here.
Post not yet marked as solved Up vote reply of malc Down vote reply of malc

Extending what has already been suggested, you can modify the predicate of the wrappedValue to dynamically trigger an update to a SwiftUI list. In the example below, toggling the isFiltered @State causes an update of the List and it reloads the FetchedResults with the filtered or unfiltered predicate.

@FetchRequest private var items: FetchedResults<Item>
@State var isFiltered = false
let filteredPredicate: NSPredicate
let unfilteredPredicate: NSPredicate

init(element: Element) {
     self.element = element
     filteredPredicate = NSPredicate(format: "element == %@ && score > 0.85", element)
     unfilteredPredicate = NSPredicate(format: "element == %@", element)

     self._items = FetchRequest<Item>(entity: Item.entity(),
                                      sortDescriptors: [NSSortDescriptor(keyPath: \Item.name, ascending: true),
                                      predicate: unfilteredPredicate,
                                      animation: .default)
}

var listItems: FetchedResults<Moment> {
     get {
          _moments.wrappedValue.nsPredicate = isFiltered ? filteredPredicate : unfilteredPredicate
          return moments
     }
}

var body: some View {
    List {
       ForEach(Array(listItems.enumerated()), id: \.element) { index, item in
          Text(item.name)
          .toolbar {
               ToolbarItem {
                   Button {
                       withAnimation {
                           isFiltered.toggle()
                       }
                   } label: {
                       Label("Filter Items", systemImage: isFiltered ? "star.circle.fill" : "star.circle")
                   }
               }
           }
        }
    }
}

After two years I have the solution you are looking for, but you probably don't need it anymore, but for others who may also be wondering the same thing here is an example of how to sort a FetchedResults type with a search bar

You'll ned to include this at the top of your struct. and you have to change "Item" to the name of the entity in your core data file.

@FetchRequest(sortDescriptors: []) var items: FetchedResults<Item>
@State private var searchingFor = ""

And link this to the end of your list or whatever contains your list. You'll have to change title to whatever the name of your attribute is in your core data file

.searchable(text: $searchingFor, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search Titles")
.onChange(of: searchingFor){ value in
if (searchingFor != ""){
    items.nsPredicate=NSPredicate(format: "title CONTAINS[c] %@", searchingFor)
} else {
    items.nsPredicate=nil
}

Let me know if you have any questions!

@WeagleWeagle I have a scenario in which the initialiser to the view passes a value and depending on the value I need to determine the predicate and sort descriptors.

Currently I have an if else statement and then I am setting self._items as suggested by @aharriscrowne

For that scenario, is it ok to set self._items in the initialiser?

Add a Comment

Here's how to dynamically set the predicates for your fetch request. First, the properties I have in my SwiftUI view:

@FetchRequest(
    sortDescriptors: [NSSortDescriptor(keyPath: \Pokemon.id, ascending: true)],
    animation: .default
) private var pokedex: FetchedResults&lt;Pokemon>

@State var searchText = ""
@State var filterByFavorites = false

var compoundPredicate: NSPredicate {
    var predicates: [NSPredicate] = []
    
    &#x2F;&#x2F; Add search predicate
    if !searchText.isEmpty {
        predicates.append(NSPredicate(format: "name contains[c] %@", searchText))
    }
    
    &#x2F;&#x2F; Add favorite filter predicate
    if filterByFavorites {
        predicates.append(NSPredicate(format: "favorite == %d", true))
    }
    
    &#x2F;&#x2F; Combine predicates
    return NSCompoundPredicate(andPredicateWithSubpredicates: predicates)
}

Notice I don't set any predicate in the fetch request at this point. Also notice, the part that makes this all work is the compoundPredicate computed property. I start with an empty predicates array, then I check my searchText and append the appropriate predicate if the condition is true. Then I check my filterByFavorites property and do the same.

Alright, now the code inside the body var that makes it work. I won't post my whole view since most of it is irrelevant, but here is where I add my List view which shows my pokedex fetched results:

List(pokedex.filter { compoundPredicate.evaluate(with: $0) }) { pokemon in

I filter my pokedex fetched results with the compoundPredicate and evaluate every single pokemon in the list to make sure only those that match the criteria are shown.

I also have a .searchable(text: $searchText, prompt: "Find a Pokemon") modifier on the List view to manage the searching part. And then I have a button the user can tap to toggle filterByFavorites between true and false.

  • Using .filter seems terribly inefficient. Can you try comparing the load speed with a large number of files? Something tells me that changing the fetchRequest predicate (rather than evaluating the results) should be much faster.

Add a Comment