NSPredicate for searching a string with double quotes - swift

An attribute element in coredata model is a string(json string format), which is an array of dictionaries like below,
one element has
"[{"tagName":"sad","count":2},{“tagName":"happy","count":1}]"
and other has
"[{"tagName":"sad1","count":2},{“tagName":"happy1","count":1},{“tagName":"nothappy","count":1}]"
Need to search the list with refer to the tagname.
If I use the predicate below,
tagName = "sad"
tagNameFilter += String(format: "vrTags CONTAINS[cd] \"%#\"", tagName)
it's returning both elements. It should return the first element alone
If I use without double quotes
tagName = "sad"
tagNameFilter += String(format: "vrTags CONTAINS[cd] %#", tagName)
it's crashing with reason:
unimplemented SQL generation for predicate : (vrTags CONTAINS[cd] sad) (LHS and RHS both keypaths) with userInfo of (null)
If I use
tagName = "sad"
tagNameFilter += String(format: "vrTags CONTAINS[cd] \"\"%#\"\"", tagName)
it's crashing with reason: Unable to parse the format string
How to solve this filter issue? Any suggestions would be appreciated.

In order to search for a string "sad" including the quotation marks you have to pass that string with the quotation marks as an argument to NSPredicate(format:). This can be done with with string interpolation:
let tagName = "sad"
let predicate = NSPredicate(format: "vrTags CONTAINS[cd] %#", "\"\(tagName)\"")
print(predicate) // vrTags CONTAINS[cd] "\"sad\""
And never use String(format:) and string concatenation to build complex predicates. That is very error-prone because the quoting and escaping rules for predicate strings are different from those to format strings.
If you need to combine multiple conditions with “AND” then do it like
let p1 = NSPredicate(...)
let p2 = NSPredicate(...)
// ...
let predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [p1, p2, ...])

Related

Sort filtered results in Realm by matching

I have a dictionary with ~100k entries and provide a search function where the user can search for a string in english/chinese and all entries that contain this string will be displayed. An entry has the structure:
class DictionaryEntry: Object, Identifiable {
#objc dynamic var id: String = NSUUID().uuidString
#objc dynamic var character: String = ""
#objc dynamic var pinyin: String = ""
#objc dynamic var definition: String = ""
}
Let's say we have the following two entries:
entry1 = "宝山 / Bǎoshān / Bǎoshān district of Shanghai"
entry2 = "上海 / Shànghǎi / Shanghai"
and the search string is "shanghai". I filter the entries by this way:
let realm = try! Realm()
realm.objects(DictionaryEntry.self)
.filter("pinyin CONTAINS[cd] %# OR definition CONTAINS[cd] %# OR character CONTAINS[cd] %#", searchString, searchString, searchString)
//.sorted(by: ???)
Since both entries contain the search string both entries will appear, but they are not sorted by any logic yet. I would like to sort the results by the highest "coverage/matching". So first should "Shanghai" appear, then "Shanghai university", ..., and at the end entries like "Old canal between Suzhou and Shanghai". Is there any built-in realm solution?
Realm's Results have built-in methods for sorting, see Sorting in the docs.
If you want to sort based on a specific property, you can use sorted(byKeyPath:) which takes a single String representing the property name that you want to sort by:
let realm = try! Realm()
realm.objects(DictionaryEntry.self)
.filter("pinyin CONTAINS[cd] %# OR definition CONTAINS[cd] %# OR character CONTAINS[cd] %#", searchString, searchString, searchString)
.sorted(byKeyPath: "pinyin")
Or if you want to sort based on all 3 properties that you are filtering by, you can use sorted(by:), which takes a Sequence of SortDescriptor objects, which can be created based on key paths as well:
let realm = try! Realm()
realm.objects(DictionaryEntry.self)
.filter("pinyin CONTAINS[cd] %# OR definition CONTAINS[cd] %# OR character CONTAINS[cd] %#", searchString, searchString, searchString)
.sorted(by: [SortDescriptor(keyPath: "pinyin"), SortDescriptor(keyPath: "definition"), SortDescriptor(keyPath: "character")])

Searching 2 Attributes at the same time CoreData

I'm trying to use a predicate to search 2 attributes at the same time. I initially tried a compound predicate but it would only return results if both predicates matched the string.
Basically I'm looking for something similar to this:
let predicate = NSPredicate(format: "title CONTAINS[cd] %#" || "plainTextBody CONTAINS[cd] %#", searchString, searchString)
So it seems I was close with my original post but it's important to keep the search terms in quotation marks and not separate them like I did in my original question. Simply using the following works perfectly:
let predicate = NSPredicate(format: "title CONTAINS[cd] %# || plainTextBody CONTAINS[cd] %#", searchString, searchString)

CoreData predicate using relationship

I have two entities - Quotes and Customers. One customer can have many quotes. The relationships are quotes and customers.
I want to get a quote object based on the customer name and email address, sorted by date but I'm stuck trying to format the predicate...
func getMostRecentQuote(name: String, email: String) -> Quotes? {
var predicateList = [NSPredicate]()
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: "Quotes")
let predicate1 = NSPredicate(format: "name CONTAINS[c] %#", name)
let predicate2 = NSPredicate(format: "email CONTAINS[c] %#", email)
let orCompoundPredicate = NSCompoundPredicate(type: .or, subpredicates: [predicate1, predicate2])
predicateList.append(orCompoundPredicate)
fetchRequest.predicate = NSCompoundPredicate(type: .and, subpredicates: predicateList)
fetchRequest.fetchLimit = 1
Probably you have a to-one relationship from Quotes to Customers, if not, establish one and name the property customer
Then use this single predicate
let predicate = NSPredicate(format: "customer.name CONTAINS[c] %# OR customer.email CONTAINS[c] %#", name, email)
If you want to filter the full string caseinsensitive CONTAINS is actually the wrong operator, better use LIKE
let predicate = NSPredicate(format: "customer.name LIKE[c] %# OR customer.email LIKE[c] %#", name, email)
 
Note: Please name entities in singular form, semantically your method is going to return one Quote, not one Quotes

NSPredicate in swift for empty string

Swift 4.2 iOS 11.x
Trying to read the records in a iCloud database that have a lineOwner field set, but struggling to create an NSPredicate that works!
This looks ok, but doesn't parse.
let predicate = NSPredicate(format: remoteAttributes.lineOwner + " != %#",0)
I get an invalid predicate error message [and a nasty crash into the bargain]. Spend a couple of hours on this and losing the will to live.
If I print out the database I see this.
You can simply compare to the empty String, "". The %# placeholder represents a String, so the crash happens because you supply an Int to the NSPredicate instead of a String.
You should also use the %K placeholder for variable names instead of appending strings.
let predicate = NSPredicate(format: " %K != %#", remoteAttributes.lineOwner, "")
If you also want to filter out nil values, you can use a compound predicate:
let predicate = NSPredicate(format: " %K != %# AND %K != nil", remoteAttributes.lineOwner, "")

CoreData Predicate get every sentence that contains any word in array

In Swift 4 I have a CoreData "Sentence" model that has a String attribute "englishsentence". I also have an array of "words" and would like to fetch all sentences for which the "englishsentence" attribute contains one or more of the words in the array.
var words = ["today", "yesterday", "tomorrow"]
This array is just an example. It is supposed to change at runtime and can have any length.
and in the fetch request I am trying to do something like this:
let fetchRequest =
NSFetchRequest<NSManagedObject>(entityName: "Sentence")
let predicate = NSPredicate(format: "ANY englishsentence CONTAINS ANY word IN %#", words)
fetchRequest.predicate = predicate
I am able to create a predicate for all sentences that contain one particular word. However, I cannot get it to work with an array of words unless of course I iterate through the words-array and make a new fetch request for every single word. But this seems awfully inefficient.
One option would be to create a “compound predicate:”
let words = ["today", "yesterday", "tomorrow"]
let predicates = words.map {
NSPredicate(format: "englishsentence CONTAINS %#", $0)
}
let predicate = NSCompoundPredicate(orPredicateWithSubpredicates: predicates)
Another option is to match against a regular expression:
let regex = ".*(" + words.map {
NSRegularExpression.escapedPattern(for: $0)
}.joined(separator: "|") + ").*"
let predicate = NSPredicate(format: "englishsentence MATCHES %#", regex)
To match only “whole words” you can add the word boundary pattern \b:
let regex = ".*\\b(" + words.map {
NSRegularExpression.escapedPattern(for: $0)
}.joined(separator: "|") + ")\\b.*"