I have a number of records in my cloudKit public database and I would like to return those with text matching a given 'searchTerm' text string.
If I use this predicate I get all of the records (up to the maximum limit)
let myPredicate = NSPredicate(value: true) // returns records
When I try to just return records containing a partial search term I get nothing
let myPredicate = NSPredicate(format: "allTokens TOKENMATCHES[cdl] %#", searchTerm) // - empty
let myPredicate = NSPredicate(format: "self contains %#", "H")// empty
let myPredicate = NSPredicate(format: "allTokens TOKENMATCHES[cdl] %#", searchTerm) // empty
let myPredicate = NSPredicate(format: "self contains 'H'")// empty
But when I search for a complete word the search works
let myPredicate = NSPredicate(format: "self contains 'Honda'") // returns matching records
Is there a way to do a partial search eg wild cards or similar?
This is a repeat of a qaestion I asked 5 years ago(!)
Related
My goal is to get User records from CloudKit I specify in an array of CKRecord.IDs, but only if they were updated more recently than my last updated date which I track locally.
I'm doing the following CloudKit query:
var predicate:NSPredicate
if let lastChecked = defaults.object(forKey: "lastUserFetch") as? Date{
//Subsequent fetches
predicate = NSPredicate(format: "modificationDate > %# AND recordID IN %#", argumentArray: [lastChecked, userRecordIDs])
}else{
//First fetch
predicate = NSPredicate(format: "recordID IN %#", argumentArray: [userRecordIDs])
}
let query = CKQuery(recordType: "User", predicate: predicate)
let operation = CKQueryOperation(query: query)
...
I initially set lastUserFetch to nil when my app launches, and the "First fetch" part of the query succeeds. I get all the user records back.
But once I set my lastUserFetch date after the query is done:
defaults.set(Date(), forKey: "lastUserFetch")
I get 0 records returned when the modificationDate > %# portion is included. I, of course, modify records in the CloudKit Dashboard, and I can see that their modificationDate has updated and is newer than my lastUserFetch date, but they still don't get returned in my search results.
How do I combine a modificationDate comparison with a recordID IN query?
Use an NSCompoundPredicate:
var predicate:NSPredicate
//Every fetch
predicate = NSPredicate(format: "recordID IN %#", argumentArray: [userRecordIDs])
if let lastChecked = defaults.object(forKey: "lastUserFetch") as? Date{
//Subsequent fetches
predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicate, NSPredicate(format: "modificationDate > %#", argumentArray: [lastChecked]))
}
let query = CKQuery(recordType: "User", predicate: predicate)
let operation = CKQueryOperation(query: query)
...
Make sure you have queryable index created on modificationDate field on cloudKit
One Gymnast to Many Meets. Can someone provide some info on how I can set meet for each gymnast. It prints each gymnast in array but the meet only gets assigned to last gymnast in array.
newMeet.meetName = "Winter Classic"
newMeet.meetDate = "Sat October 26, 2016"
newMeet.meetDateSort = "2016-09-26"
newMeet.meetTime = "02:00 PM"
newMeet.meetLocation = "Phoenix, AZ"
newMeet.meetStatus = "Active"
let request = NSFetchRequest(entityName: "Gymnast")
request.predicate = NSPredicate(format: "isActive = %#", "Yes")
do {
let results = try AD.managedObjectContext.executeFetchRequest(request) as! [Gymnast]
for result in results {
let newMeets = result.meets?.mutableCopy() as! NSMutableSet
newMeets.addObject(newMeet)
result.meets = newMeets.copy() as? NSSet
print("\(result.fullName!)")
}
} catch {
fatalError("Failed to save data")
}
AD.saveContext()
self.navigationController?.popViewControllerAnimated(true)
You describe the relationship as one Gymnast to many Meets. If it's a one-to-many relationship, that implies that each Meet can only be associated with a single Gymnast, although each Gymnast can be associated with more than one Meet.
If your description of the relationship is correct, then what you're seeing is exactly what would be expected. Since each Meet can only have one Gymnast, each pass through the loop reassigns that relationship from the one set in the previous pass.
I'm guessing that the relationship should be many-to-many, not one-to-many, since each Meet can presumably involve more than one Gymnast.
According to Apples Class Reference CKQuery, the operator CONTAINS is one of the supported operators. However, that doesn't seem to work. I have a RecordType called myRecord, and a record with field name name type String. I try to fetch the record with two different predicates, one with "==" operator, and one with CONTAINS operator.
func getRecords() {
let name = "John"
let Predicate1 = NSPredicate(format: "name == %#",name)
let Predicate2 = NSPredicate(format: "name CONTAINS %#",name)
let sort = NSSortDescriptor(key: "Date", ascending: false)
let query = CKQuery(recordType: "myRecord", predicate: Predicate1)
// let query = CKQuery(recordType: "myRecord", predicate: Predicate2)
query.sortDescriptors = [sort]
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["name", "Date"]
operation.recordFetchedBlock = { (record) in
print(record["name"])
operation.queryCompletionBlock = { [unowned self] (cursor, error) in
dispatch_async(dispatch_get_main_queue()) {
if error == nil {
print ("sucess")
} else {
print("couldn't fetch record error:\(error?.localizedDescription)")
}
}
}
CKContainer.defaultContainer().publicCloudDatabase.addOperation(operation)
}
Using Predicate1, output is:
Optional(John)
sucess
Using Predicate2, output is:
couldn't fetch record error:Optional("Field \'name\' has a value type of STRING and cannot be queried using filter type LIST_CONTAINS")
Also using [c] to ignore casings gives a server issue.
How do I use the operator CONTAINS correctly?
EDIT:
I have now looked closer at the documentation, and seen that CONTAINS can only be used with SELF. Meaning that all String fields will be used for searching. Isn't there a better way?
It's an exception mentioned as below:
With one exception, the CONTAINS operator can be used only to test
list membership. The exception is when you use it to perform full-text
searches in conjunction with the self key path. The self key path
causes the server to look in searchable string-based fields for the
specified token string. For example, a predicate string of #"self
contains 'blue'" searches for the word “blue” in all fields marked for
inclusion in full-text searches. You cannot use the self key path to
search in fields whose type is not a string.
So, you can use 'self' instead of '%K' in order to search sub-text of string field.
For the full document written by Apple
Hello Need help with predicate. The problem is that I want to have one function which will fetch from database depends on the multiple values it receives, but the values don't always exist, does it mean I have to create several different predicates?
Code below
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"brand_id LIKE %# AND brand_id LIKE %# AND brand_id LIKE %# AND brand_id LIKE %# AND brand_id LIKE %# AND item_category LIKE %# AND item_make LIKE %# AND item_gender LIKE %# || item_price>%# || item_price<%#",
brand_one, brand_two, brand_three, brand_four, brand_five, categoryString, makeString, genderString,[NSNumber numberWithInt:price_bottom],[NSNumber numberWithInt:price_top]];
brand_one, brand_two and etc sometimes exist and sometimes don't.
And how should it for item_gender for example. Let's if there is no gender specified than to have both of them.
Sorry if my description of the problem confusing.
Base on Gobras comments the following code was produces
NSArray *brands = [NSArray arrayWithObjects:brand_one, brand_two, brand_three, brand_four, brand_five, nil];
NSPredicate *predicate_brands = [NSPredicate predicateWithFormat:#"brand_id like %#" argumentArray:brands];
Logical explanation of what what search function should
fetchrequest
predicate on fetch request
predicate based on 5 brand ids, item id, item category, item make, item gender,
if any of above is empty it should fetch all related entries for example if item category is empty it should fetch all "Jewellery, Watches and etc" but limit it with the rest of the predicate expressions.
Should I create for example compound predicates for brands and than create another one for item category with values "item_category like Jewellery" and "item_category like Watches" and after that how do I exactly bring them together?
Assemble an appropriate collection of individual predicates into NSCompoundPredicate
as in
NSMutableArray *parr = [NSMutableArray array];
if([brand_one length]) {
[parr addObject:[NSPredicate predicateWithFormat:#"brand_id LIKE %#",myBrandId]];
}
if([brand_two length]) {
// etc
}
NSPredicate *compoundpred = [NSCompoundPredicate andPredicateWithSubpredicates:parr];
You can stack up your predicates with orPredicateWithSubpredicates: and andPredicateWithSubpredicates: and NSCompoundPredicate being a NSPredicate itself can be compounded.
See
https://developer.apple.com/documentation/foundation/nscompoundpredicate
For those who are interested in Swift!
let arrPred = [NSPredicate(format: "yourAttribute LIKE %#", toCompareID), NSPredicate(format: "yourAttribute2 LIKE %#", toCompareID2)] //Add as many predicates you want in short make your query
let compoundpred:NSPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: arrPred)
var arrFiltered = yourArray.filteredArrayUsingPredicate(compoundpred)
It's pretty simple to build the predicate dynamically: combine your predicate format string with desired number of conditions and build corresponding NSMutableArray with the arguments. [NSPredicate predicateWithFormat:argumentArray:] will do the rest.
I've seen that every predicate that works in a query with a relationship contains at the start the words ANY or ALL (ie: ANY tags.name LIKE[c] "car"), the fact is, if I remove it (ie: tags.name LIKE[c] "car"), the result is wrong or I get a message like this one : Can't do regex matching on object.
Since i'm using an NSPredicateEditor their is no ANY or ALL that starts my query, so it always fail.
The Predicates returned is always like the second exemple (no ANY or ALL).
Do I have to subclass the NSPredicateRowTemplateEditor, in order to add myself the ANY or ALL in my predicate, or is their another way?
Same thing with the dates... my dates are saved in this format: YYYY-MM-DD HH:mm:ss, but the NSPredicateEditor use DD/MM/YYYY, so each time I try a date comparaison, it does not work. Do I also have to subclass the RowEditor, in order to change the date format?
Thank you.
Here you go:
class RowTemplateRelationshipAny: NSPredicateEditorRowTemplate {
override func predicate(withSubpredicates subpredicates: [NSPredicate]?) -> NSPredicate{
let predicate: NSComparisonPredicate = super.predicate(withSubpredicates: subpredicates) as! NSComparisonPredicate
let newPredicate = NSComparisonPredicate(leftExpression: predicate.leftExpression, rightExpression: predicate.rightExpression, modifier: .any, type: predicate.predicateOperatorType, options: predicate.options)
return newPredicate
}
}
class RowTemplateRelationshipAll: NSPredicateEditorRowTemplate {
override func predicate(withSubpredicates subpredicates: [NSPredicate]?) -> NSPredicate{
let predicate: NSComparisonPredicate = super.predicate(withSubpredicates: subpredicates) as! NSComparisonPredicate
let newPredicate = NSComparisonPredicate(leftExpression: predicate.leftExpression, rightExpression: predicate.rightExpression, modifier: .all, type: predicate.predicateOperatorType, options: predicate.options)
return newPredicate
}
}
Just change your row template class in IB to RowTemplateRelationshipAny or RowTemplateRelationshipAll.