I've a list displaying object from CoreData using @FetchRequest, I want to provide the user with a bar button that when clicked will filter the displayed list. How can I change the @FetchRequest predicate and rerun it dynamically to rebuild the list with the filtered items?
struct EmployeeListView : View {
@FetchRequest(
entity: Department.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Department.name, ascending: false)],
)
var depts: FetchedResults<Department>
@Environment(\.managedObjectContext) var moc
var body: some View {
NavigationView {
List {
ForEach(depts, id: \.self) { dept in
Section(header: Text(dept.name)) {
ForEach(dept.employees, id: \.self) { emp in
Text(emp.name)
}
}
}
}
.navigationBarTitle("Employees")
}
}
}
I know how to provide a filter, what I don't know how is changing the property wrapper predicate and rerunning the fetch request.
You can change your results based on a binding in your fetch predicate, but with Bool vars, I've found it is difficult to do. The reason is, the predicate to test a Bool in CoreData is something like NSPredicate(format: "myAttrib == YES")
whereas your Bool binding variable will be true or false, not YES or NO... So if you NSPredicate(format: "%K ==%@", #keypath(Entity.seeMe), seeMe.wrappedValue)
, this will always be false. Maybe I'm wrong, but this is what I've experienced.
You can filter your fetch based on String data easier.. But it works a little differently than my example below because your need to run your fetch in the init() of the View like this:
@Binding var searchTerm:String
var fetch: FetchRequest<Entity>
var rows: FetchedResults<Entity>{fetch.wrappedValue}
init(searchTerm:Binding<String>) {
self._searchTerm = searchTerm
self.fetch = FetchRequest(entity: Entity.entity(), sortDescriptors: [], predicate: NSPredicate(format: "%K == %@", #keyPath(Entity.attribute),searchTerm.wrappedValue))
}
To accomplish the task you've described, clicking on a bar button item thereby toggling a Bool, the below example is what I would recommend:
This example will accomplish your goal without changing the fetch predicate. It uses logic to decide whether or not to display a row of data based on the entry in the data model and the value of your @State variable.
import SwiftUI
import CoreData
import Combine
struct ContentView: View {
@Environment(\.managedObjectContext) var viewContext
@State var seeMe = false
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Entity.attribute, ascending: true)],
animation: .default)
var rows: FetchedResults<Entity>
var body: some View {
NavigationView {
VStack {
ForEach(self.rows, id: \.self) { row in
Group() {
if (self.validate(seeMe: row.seeMe)) {
Text(row.attribute!)
}
}
}
.navigationBarItems(leading:
Button(action: {
self.seeMe.toggle()
}) {
Text("SeeMe")
}
)
Button(action: {
Entity.create(in: self.viewContext, attribute: "See Me item", seeMe: true)
}) {
Text("add seeMe item")
}
Button(action: {
Entity.create(in: self.viewContext, attribute: "Dont See Me item", seeMe: false)
}) {
Text("add NON seeMe item")
}
}
}
}
func validate(seeMe: Bool) -> Bool {
if (self.seeMe && seeMe) {
return true
} else if (!self.seeMe && !seeMe ){
return true
} else {
return false
}
}
}
extension Entity {
static func create(in managedObjectContext: NSManagedObjectContext,
attribute: String,
seeMe: Bool
){
let newEvent = self.init(context: managedObjectContext)
newEvent.attribute = attribute
newEvent.seeMe = seeMe
}
static func save(in managedObjectContext: NSManagedObjectContext) {
do {
try managedObjectContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
To use this example, create a core data model with an entity named "Entity" and two attributes, one named 'attribute' as a String and the other named 'seeMe' as a Bool. Then run it, press the buttons to create the two types of data and then click the bar button item at the top to select which to display.
I'ts not the prettiest of examples, but it should demonstrate the functionality of what you are trying to accomplish.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With