Reputation: 639
I am developing an application that retrieves data from CoreData. I retrieve a list of items from the database and display these on screen.
The user has an option to filter these items for up to 5 categories in 5 separate drop downs. What is the best way of doing this dynamically? What I mean by that, is if the user selects one filter option, only the items that match that filter will be shown, as well as the other filter options then only showing filter options that exist for the already filtered items.
I hope that makes sense!
This is the code I currently have for retrieving the items:
func showDropDown(filterButton: UIButton) -> Void {
selectedButton = filterButton
let popController = UIStoryboard(name: STORYBOARD_NAME,
bundle: nil).instantiateViewController(withIdentifier: STORYBOARD_ID) as! FilterDropDownViewController
popController.modalPresentationStyle = .popover
popController.delegate = self
popController.popoverPresentationController?.permittedArrowDirections = .up
popController.popoverPresentationController?.delegate = self
popController.popoverPresentationController?.sourceView = filterButton
popController.popoverPresentationController?.sourceRect = filterButton.bounds
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item")
var predicate = NSPredicate(format: "code matches[c] '\(code!)'")
fetchRequest.returnsObjectsAsFaults = false
fetchRequest.predicate = predicate
let entity = NSEntityDescription.entity(forEntityName: "Item",
in: context)
fetchRequest.resultType = .dictionaryResultType
let entityProperties = entity?.propertiesByName
let filterToFetch = "filter\(filterButton.tag)"
let propertiesToFetch: [Any] = [entityProperties![filterToFetch]!]
fetchRequest.propertiesToFetch = propertiesToFetch
fetchRequest.returnsDistinctResults = true
var result = [[String : String]]()
do {
result = try context.fetch(fetchRequest) as! [[String : String]]
} catch {
print("Unable to fetch managed objects for Item).")
}
var filterArray = [Filter]()
for dict in result {
if let search = dict[filterToFetch] {
predicate = NSPredicate(format: "code matches[c] '\(search)'")
let filterCode = DatabaseHelper.fetchRecordsForEntity(entity: "Filter",
managedObjectContext: context,
predicate: predicate) as! [Filter]
filterArray.append(filterCode.first!)
}
}
popController.filterArray = filterArray
present(popController, animated: true, completion: nil)
}
Upvotes: 1
Views: 399
Reputation: 8563
You can do it with these simple steps:
NSFetchResultsController
with the appropriate predicate based on the current filter settings (use a NSCompoundPredicate
), and fetch with it. Set the view controller as the delegate and reload the data of the collectionView when data changes (it seems you don't expect data to be changing while the user is viewing it, so you can just keep it simple). Don't forget to reload the collectionView when updating the NSFetchResultsController.fetchedObjects
in the NSFetchResultsController and see what filterable properties it has. Add those properties to a set (one for each filter category). Then look at the set to determine what filters to display and update the UI.NSFetchResultsController
to nil, before creating a new one and the create a new one as described in step one.In the code you shared you are needlessly doing an complicated fetch to figure out which filters are relevant. I don't know if your code is correct or not, but I do know that it is complicated. And it is faster to just look at the properties in the managedObject that you already have access to in the fetchResultsController. Those items are already fetched and in memory - so it doesn't need to hit the database again. And filtering those item in code is easier than figuring out how to write a complex predicate.
Upvotes: 1