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.
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
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(!)
I tried to create a expression like this
let performedDate = Expression<String>("strftime('%Y-%m-%d', performedDate)")
and then compare with another string using filter method.
But the generated SQL is
"strftime('%Y-%m-%d', performedDate)" = '2017-08-17'.
There is a way to remove quotation marks from "strftime('%Y-%m-%d', performedDate)" or I have to use raw SQL string?
I solved my problem by creating a extension on Expression type like this
extension Expression {
public func strftime(format: String) -> Expression {
return Expression("strftime('\(format)', \(template))", bindings)
}
}
and then use
let date = Expression<String>("startDate")
let dateFormat = date.strftime(format: "%Y-%m-%d")
You can use dateFormat as projection or in filter method as well.
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