Clayton Andrew Cohn Clayton Andrew Cohn - 1 month ago 13
iOS Question

Get Unique Results from NSFetchRequest

I'm trying to return an

NSFetchRequest
with distinct values. This is what I have:

class Tag: NSManagedObject {
static let entityName = "\(Tag.self)"

static var allTagsRequest: NSFetchRequest<NSFetchRequestResult> = {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: Tag.entityName)
request.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)]
return request
}()

static var uniqueTagsRequest: NSFetchRequest<NSFetchRequestResult> = {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: Tag.entityName)
request.sortDescriptors = [NSSortDescriptor(key: "title", ascending: true)]
request.resultType = .dictionaryResultType
request.returnsDistinctResults = true
return request
}()
}


The
allTagsRequest
returns properly, i.e. it returns all of the tags stored in CoreData - even the duplicates. I want the
uniqueTagsRequest
to return only unique tags, but when I do, the tableView doesn't populate. Here is my tableView code:

class SortableDataSource<SortType: CustomTitleConvertible>: NSObject, UITableViewDataSource where SortType: NSManagedObject {

let kReuseIdentifier = "sortableItemCell"

fileprivate let fetchedResultsController: NSFetchedResultsController<NSFetchRequestResult>

var results: [SortType] {
return fetchedResultsController.fetchedObjects as! [SortType]
}

init(fetchRequest: NSFetchRequest<NSFetchRequestResult>, managedObjectContext moc: NSManagedObjectContext) {
self.fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: moc, sectionNameKeyPath: nil, cacheName: nil)

super.init()
executeFetch()
}

func executeFetch() {
do {
try fetchedResultsController.performFetch()
} catch let error as NSError {
let alertController = UIAlertController(
title: "Error",
message: "\(error.debugDescription)",
preferredStyle: .alert)
let okAction = UIAlertAction(
title: "Ok",
style: .cancel,
handler: nil)
alertController.addAction(okAction)
UIApplication.topViewController()?.present(alertController, animated: true, completion: nil)
}
}

//MARK: - UITableViewDataSource
func numberOfSections(in tableView: UITableView) -> Int { return 2 }

func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch section {
case 0: return 1
case 1: return fetchedResultsController.fetchedObjects?.count ?? 0
default: return 0
}
}

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: kReuseIdentifier)
cell.selectionStyle = .none

switch (indexPath.section, indexPath.row) {
case (0, 0):
cell.textLabel?.text = "All \(SortType.self)s"
cell.accessoryType = .checkmark
case (1, _):
guard let sortItem = fetchedResultsController.fetchedObjects?[indexPath.row] as? SortType else { break}
cell.textLabel?.text = sortItem.title
default: break
}
return cell
}
}


And sort controller:

@objc fileprivate func presentSortController() {
let tagDataSource = SortableDataSource<Tag>(fetchRequest: Tag.uniqueTagsRequest, managedObjectContext: CoreDataController.sharedInstance.managedObjectContext)
let sortItemSelector = SortItemSelector(sortItems: tagDataSource.results)
let sortController = PhotoSortListController(dataSource: tagDataSource, sortItemSelector: sortItemSelector)

sortController.onSortSelection = { checkedItems in
if !checkedItems.isEmpty {
var predicates = [NSPredicate]()
for tag in checkedItems {
let predicate = NSPredicate(format: "%K CONTAINS %@", "tags.title", tag.title)
predicates.append(predicate)
}
let compoundPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
self.dataSource.performFetch(withPredicte: compoundPredicate)
} else {
self.dataSource.performFetch(withPredicte: nil)
}
}

let navController = UINavigationController(rootViewController: sortController)

present(navController, animated: true, completion: nil)
}


Thanks guys!

Answer

If it was me, I would populate a local array with the allTagsRequest and have the NSFetchedResultsController feed that. Then I would have that local array feed the tableview.

That way, when you need the unique list you can just do the little trick where you convert the array into an NSSet and then back to an array (this removes duplicates). When you're ready to get the whole list again, you can just refetch from the original and reload the tableview.

Comments