I am attempting to filter the items in my FetchedResultsController to filter out a certain set of items. These are engines which have a certain letter designation in them followed by numbers. The possible rage of letters in the designation are from "A" to "O". I would like to be able to filter out engines that have the designations from "D" to "O" based on another condition.
I set a constant "highPowerEngines" that is a set containing those letters. Since the designation only contains one letter, I want to exclude any designation that contains any of the letters in highPowerEngines. So far, this is my code, the predicate I am working on is contained after the FIXME:
func configureFetchedResultsController() {
let context = databaseController.getContext()
let enginesFetchRequest = NSFetchRequest<Engine>(entityName: CoreData.engine)
let highPowerEngines: Set = ["D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"]
var predicate = NSPredicate()
if currentStage == 1 && stages! == 1 {
predicate = NSPredicate(format: "engineType == %#", EngineType.singleStage.rawValue)
} else if currentStage < stages! {
predicate = NSPredicate(format: "engineType == %#", EngineType.boosterStage.rawValue)
} else {
predicate = NSPredicate(format: "engineType == %#", EngineType.upperStage.rawValue)
}
var predicateArray:[NSPredicate] = [
predicate
]
// FIXME: Sort out highPowerEngines
if !dPlusEngineIAP {
if currentStage == 1 && stages! == 1 {
predicate = NSPredicate(format: "NOT engineDesignation CONTAINS %#", highPowerEngines)
predicateArray.append(predicate)
}
}
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicateArray)
enginesFetchRequest.predicate = compoundPredicate
let primarySortDescriptor = NSSortDescriptor(key: CoreData.isMadeByName, ascending: true)
let secondarySortDescriptor = NSSortDescriptor(key: CoreData.engineDesignation, ascending: true)
enginesFetchRequest.sortDescriptors = [primarySortDescriptor, secondarySortDescriptor]
self.fetchedResultsController = NSFetchedResultsController<Engine>(
fetchRequest: enginesFetchRequest,
managedObjectContext: context,
sectionNameKeyPath: CoreData.isMadeByName,
cacheName: nil)
self.fetchedResultsController.delegate = self
}
Use IN instead of CONTAINS
NOT (engineDesignation IN %#)
update
Since you want to exclude all but tree letters why not turn the predicate around to include rather than exclude, something like
engineDesignation BEGINSWITH ‘A’ OR
engineDesignation BEGINSWITH ‘B’ OR
engineDesignation BEGINSWITH ‘C’
Thanks to the post here: CoreData Predicate get every sentence that contains any word in array
, I was finally able to solve my problem. My current working code is here:
func configureFetchedResultsController() {
let context = databaseController.getContext()
let enginesFetchRequest = NSFetchRequest<Engine>(entityName: CoreData.engine)
let lowPowerEngines = ["A", "B", "C"]
var predicate = NSPredicate()
if currentStage == 1 && stages! == 1 {
predicate = NSPredicate(format: "engineType == %#", EngineType.singleStage.rawValue)
} else if currentStage < stages! {
predicate = NSPredicate(format: "engineType == %#", EngineType.boosterStage.rawValue)
} else {
predicate = NSPredicate(format: "engineType == %#", EngineType.upperStage.rawValue)
}
var predicateArray:[NSPredicate] = [
predicate
]
if !dPlusEngineIAP {
let predicates = lowPowerEngines.map {
NSPredicate(format: "engineDesignation CONTAINS %#", $0)
}
let predicate = NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
predicateArray.append(predicate)
}
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: predicateArray)
enginesFetchRequest.predicate = compoundPredicate
let primarySortDescriptor = NSSortDescriptor(key: CoreData.isMadeByName, ascending: true)
let secondarySortDescriptor = NSSortDescriptor(key: CoreData.engineDesignation, ascending: true)
enginesFetchRequest.sortDescriptors = [primarySortDescriptor, secondarySortDescriptor]
self.fetchedResultsController = NSFetchedResultsController<Engine>(
fetchRequest: enginesFetchRequest,
managedObjectContext: context,
sectionNameKeyPath: CoreData.isMadeByName,
cacheName: nil)
self.fetchedResultsController.delegate = self
}
The solution to dealing with each item in the array was to make a compound predicate using the map function. Then adding the compound predicate to the fetchedResultsController. That allows me to compare the engine to see if it is in lowPowerEngines. Map makes a separate predicate for each item in lowPowerEngines. I could even change it programmatically on the fly. I really hope this helps someone as there is no central place to come up with these sort of tricks. When I collect enough of them, I will do a master post.
Related
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
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.
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]()
}
}
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)
}
}
}
Which predicate i need to use where objects in returned from Core Data array:
First objects must match completely;
Other object must just contained the specific word;
For example:
I have entity Man(firstName:String, lastName: String).
Let's say, i have this objects in Core Data:
1) Man(firstName: "John", secondName: "Alexandrov"), 2) Man(firstName: "Alex", secondName: "Kombarov"), 3) Man(firstName: "Felps", secondName: "Alexan").
And in returned arr i want to see [Man(firstName: "Alex", secondName: "Kombarov"), Man(firstName: "Felps", secondName: "Alexan"), Man(firstName: "John", secondName: "Alexandrov")]
How can i achieved this?
You could use a NSCompoundPredicate.
First, you'd create a predicate for the firstName. This one would be strict, so you'd search for matches using ==:
let firstNamePredicate = NSPredicate(format: "%K == %#", argumentArray: [#keyPath(Man.firstName), "alex"])
Then, you'd create a predicate for the lastName. This one is less strict, so you'd use CONTAINS:
let lastNamePredicate = NSPredicate(format: "%K CONTAINS[c] %#", argumentArray: [#keyPath(Man.lastName), "alex"])
Then you'd create an NSCompoundPredicate using the orPredicateWithSubpredicates signature.
let compoundPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [firstNamePredicate, lastNamePredicate])
From there, you could create a NSFetchRequest and assign compoundPredicate as the predicate for the fetchRequest.
If you want to sort the results, you can add one or more NSSortDescriptors to your NSFetchRequest:
let sortByLastName = NSSortDescriptor(key: #keyPath(Man.lastName), ascending: true)
let sortByFirstName = NSSortDescriptor(key: #keyPath(Man.firstName), ascending: true)
request.sortDescriptors = [sortByLastName, sortByFirstName]
Then, you'd do the fetch:
let request: NSFetchRequest = Man.fetchRequest()
request.predicate = compoundPredicate
var results: [Man] = []
do {
results = try context.fetch(request)
} catch {
print("Something went horribly wrong!")
}
Here's a link to a useful post on NSPredicate
Adding to #Adrian answer, I had to make couple changes for it to work.
let FIRSTNAME = "Alex"
let LASTNAME = "Smith"
let firstNamePredicate = NSPredicate(format: "firstName == %#", FIRSTNAME)
let lastNamePredicate = NSPredicate(format: "firstName == %#", LASTNAME)
let compoundPredicate = NSCompoundPredicate(orPredicateWithSubpredicates: [firstNamePredicate, lastNamePredicate])
request.predicate = compoundPredicate
do {
results = try context.fetch(request)
} catch {
print("Something went horribly wrong!")
}
I am new to Core Data in Swift and need help using NSPredicate. I currently have a table view and a search bar in my app. I also have an entity called Item with an attribute of allergen (string). I want to filter this table view so that cells only display if searchBar.text is equal to Item.allergen.
func attemptFetch() {
let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest()
let dateSort = NSSortDescriptor(key: "created", ascending: false)
let titleSort = NSSortDescriptor(key: "title", ascending: true)
if segment.selectedSegmentIndex == 0 {
fetchRequest.sortDescriptors = [dateSort]
} else if segment.selectedSegmentIndex == 1 {
fetchRequest.sortDescriptors = [titleSort]
} else {
if searchBar.text != nil, searchBar.text != ""{
print("Search bar text exists")
print(searchBar.text!)
fetchRequest.sortDescriptors = [titleSort]
//Search; Only display cell if searchBar.text = Item.allergen
} else {
print("Search bar text does not exist!")
fetchRequest.sortDescriptors = [titleSort]
}
}
let controller = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
controller.delegate = self
self.controller = controller
do {
try controller.performFetch()
} catch {
let error = error as NSError
print("\(error)")
}
}
I attempted to use NSPredicate to do this, but it resulted in a key path not found in entity error. I would include this code, but I'm sure it was entirely wrong.
Any advice?
Update:
Here's a picture of the Item entity's attributes in the Core Data Model.This is the code in the ItemEntity.swift file, I think this was autogenerated? Hopefully this is what you needed.
Update 2:
Thanks for the help! I found a solution. This is the code that worked for me:
let userSearch = searchBar.text!
commitPredicate = NSPredicate(format: "allergen == %#", userSearch)
fetchRequest.predicate = commitPredicate
Add the following predicate to filter only those which exactly match your search text:
fetchRequest.predicate = NSPredicate(format:"allergen == %#", searchBar.text)
Alternatively, you might want to match if the allergen string contains the search text:
fetchRequest.predicate = NSPredicate(format:"allergen CONTAINS %#", searchBar.text)
To make the comparison case and diacritic insensitive, add [cd] (so ... ==[cd] ... or ... CONTAINS[cd] ...).
EntityA: Gymnast
firstName
lastName
one to many MeetResults
EntityB: MeetResults
meetDate
barScore
beamScore
floorScore
vaultScore
I am loading all gymnasts who are set as active into the picker. As I create an NSSET result.meetresults = meetScore.copy() as? NSSet which works fine I am trying to figure out how to remove a gymnast who already has a NSSET for this date. Any assistance would be appreciated.
func getGymnasts() {
let fetchRequest = NSFetchRequest(entityName: "Gymnast")
let sortDescriptor1 = NSSortDescriptor(key: "fullName", ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor1]
let filter1 = NSPredicate(format: "isActive == %#", "Yes")
let filter2 = NSPredicate(format: "ANY meetresults.meetDate != %#", "\(meetDateText.text!)")
let predicate = NSCompoundPredicate(type: NSCompoundPredicateType.AndPredicateType, subpredicates: [filter1, filter2])
fetchRequest.predicate = predicate
//fetchRequest.predicate = filter1
do {
self.gymnastArray = try AD.managedObjectContext.executeFetchRequest(fetchRequest) as! [Gymnast]
self.gymnastPicker.reloadAllComponents()
} catch {
fatalError("Fetch Failed")
}
}
To-many relationships are awkward sometimes. You can use the subquery syntax to query the count of an internal fetch to find Meets matching your criteria, then check there aren't any:
let filter2 = NSPredicate(format: "SUBQUERY(meetresults.meetDate, $m, $m.meetDate == %#).#count == 0", meetDateText.text!)
I am trying to use NSPredicate in Swift to query Core Data but it throws an EXC_BAD_ACCESS(Code=1, address=0x1) error when trying to run it, what am I doing wrong?
Here is the file where the error happens
class LevelsScreenModel : UIViewController {
func getWord(level: Int, section: Int) -> String
{
let fetchRequest = NSFetchRequest(entityName: "Words")
//This is the line where the error happens
fetchRequest.predicate = NSPredicate(format: "level = %#", level)
fetchRequest.predicate = NSPredicate(format: "section = %#", section)
let word = AppDelegate().managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as [Words]
if(word.count > 1)
{
for words in word
{
println(words.word)
return words.word
}
}
return "ERROR"
}
}
The %# placeholder in predicate format strings is for Objective-C
objects, so you have to wrap the integer into an NSNumber:
fetchRequest.predicate = NSPredicate(format: "level = %#", NSNumber(integer: level))
or use ld instead to format a (long) integer:
fetchRequest.predicate = NSPredicate(format: "level = %ld", level)
Note also that
fetchRequest.predicate = NSPredicate(format: ...)
fetchRequest.predicate = NSPredicate(format: ...)
does not create a compound predicate, the seconds assignment simply
overwrites the first. You can use an NSCompoundPredicate:
let p1 = NSPredicate(format: "level = %ld", level)!
let p2 = NSPredicate(format: "section = %ld", section)!
fetchRequest.predicate = NSCompoundPredicate.andPredicateWithSubpredicates([p1, p2])
or simply combine the predicates with "AND":
fetchRequest.predicate = NSPredicate(format: "level = %ld AND section = %ld", level, section)
Instead of fussing around with format conversions and AND subpredicates, you could use the PredicatePal framework:
fetchRequest.predicate = *(Key("level") == level && Key("section") == section)
Note that you'll need to use == instead of = for equality comparison.