mtet88
mtet88

Reputation: 524

Have some priority sorting for compound NSPredicate

I have some data like the following:

NAME | LAST_NAME | PLACE

Roger - Martins - Miami
Mary - Rogers - Paris
Jack - Smith - Maryland
Alfred - Cooper - Germany

... and many more

And I have a predicate like this:

let searchPredicateName = NSPredicate(format: Database.Key.Name + " CONTAINS[cd] %@", _searchString)
let searchPredicateLastName = NSPredicate(format: Database.Key.LastName + " CONTAINS[cd] %@", _searchString)
let searchPredicatePlace = NSPredicate(format: Database.Key.Place + " CONTAINS[cd] %@", _searchString)

let finalPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [searchPredicateTitle, searchPredicateLastName, searchPredicatePlace])

When I search for example "Mar" then the results I would get will be:

Roger - Martins - Miami
Mary - Rogers - Paris
Jack - Smith - Maryland

The results are OK, but I would like to have Mary Rogers first. One option would be to fetch separately but then I wouldn't have one NSFetchedResultsController as a datasource and that would impact the performance

Upvotes: 3

Views: 352

Answers (3)

SeaSpell
SeaSpell

Reputation: 758

After reviewing comments from hush-entangle above I can clearly see my answer is completely wrong. I was a little too excited to get started on this website.. You are actually searching for a string position, likely using a UISearchController yeah? In that case you want to sort your results ie:

func sortForString(_ string: String) -> [Person] {
     
   let string = string.lowerCased()

   // searchResultsArray or if you want all just sort the fetched objects
    (fetchedResultsController.fetchedObjects?.sorted(by: { lhs, rhs in
    personInfoString(lhs).range(of: string ).lowerBound < personInfoString(rhs).range(of: string ).lowerBound
})) ?? [Person]()
  }

func personInfoString(_ person: Person) -> NSString {
    (person.first ?? "").appending(person.last ?? "").appending(person.place ?? "").lowercased() as NSString
}

let sorted = sortForString("Mar")

Then display your sorted strings. Notice if the search string is not contained the original order remains.

cleaned up might be

extension Person {
func sortableString() -> NSString {
    (self.first ?? " ").appending(self.last ?? " ").appending(self.place ?? " ").lowercased() as NSString
     }
}


 extension NSFetchedResultsController where ResultType == Person {

    func sortedObjects(for searchString: String) -> [Person] {
         (self.fetchedObjects?.sorted(by: { lhs, rhs in
        lhs.sortableString().range(of: searchString ).lowerBound < rhs.sortableString().range(of: searchString ).lowerBound
         })) ?? [Person]()
    }
}

Upvotes: 1

dronpopdev
dronpopdev

Reputation: 817

I assume that you have some entity UserMO with fields name, lastName and place. Also, I assume that you are using the UISearchController.

To sort by name, use the NSSortDescriptor. We omit the predicate, since we initially want to display all the elements. Based on this, we will write a function for creating a controller:

func createFetchedResultsController() -> NSFetchedResultsController<UserMO> {
   let request: NSFetchRequest<UserMO> = UserMO.fetchRequest()
   request.sortDescriptors = [
      NSSortDescriptor(keyPath: \UserMO.name, ascending: true)
   ]
   return NSFetchedResultsController<UserMO>(
      fetchRequest: request, 
      managedObjectContext: context,
      sectionNameKeyPath: nil,
      cacheName: nil)
}

At the moment when the user edits the search string, the delegate method updateSearchResults will be called. There, depending on what the user entered, we edit the predicate. You don't need to change the sorting rules.

extension ViewController: UISearchResultsUpdating {

   func updateSearchResults(for searchController: UISearchController) {
      let text = searchController.searchBar.text ?? ""
      let predicate: NSPredicate?
      if text.isEmpty {
         predicate = nil // We want to display all items
      } else {
         predicate = NSCompoundPredicate(orPredicateWithSubpredicates: [
            NSPredicate(format: Database.Key.Name + " CONTAINS[cd] %@", text),
            NSPredicate(format: Database.Key.LastName + " CONTAINS[cd] %@", text),
            NSPredicate(format: Database.Key.Place + " CONTAINS[cd] %@", text),
         ])
      }
      fetchedResultsController.fetchRequest.predicate = predicate
      
      do {
         try fetchedResultsController.performFetch()
      } catch {
         print(error)
      }
   }

}

Upvotes: 0

SeaSpell
SeaSpell

Reputation: 758

I think what you're actually looking for is NSSortDescriptor. When you use NSFetchedResultsController your NSFetchRequest has to have a sort descriptor -- that's when you do the sorting.

NSPredicate is for comparing. ie: What you want to find.

fetchRequest.sortDescriptors = [NSSortDescriptor(key: "section", ascending: true),// sort by section key first
                                 NSSortDescriptor(key:  "Name", ascending: true), //continue sorting by first name
                                 NSSortDescriptor(key:  "LastName", ascending: true), //continue sorting by last name.
                                 NSSortDescriptor(key: "Place", ascending: true)] //finally by place.

Upvotes: 1

Related Questions