Field '___recordID' is not marked queryable - ios

I try to download all record with a specific recordType that recordID is not matches with a certain one.
But I get an error. Any idea? I thought I can mark as queryable only manually added fields.
<CKError 0x15df2a10: "Invalid Arguments" (12/2015); server message = "Field '___recordID' is not marked queryable"; uuid = ECDD0C97-550F-4DD5-942D-AF4DF8917EEB; container ID = "iCloud.com.kukodajanos.Movie-Buffs">
let query = CKQuery(recordType: "UserSetting", predicate: NSPredicate(format: "recordID != %#", CKRecordID(recordName: loggedInUserRecordName!)))

I found. possible to add metaindexes as well.

You only need to manually set the Query index on ID if you choose NOT to use a predicate with your query. If you use a predicate on any self defined field name such as:
e.g.
let predicate = NSPredicate(format: "UserRecordID = %#", selectedUser!.userRecordID!.recordName)
let query = CKQuery(recordType: recordType, predicate: predicate)
you will not experience this issue.

Related

CoreData fetch request NSPredicate not working

I've added a new bool property to an entity in my CoreData model and migrated the existing data to the new model. I now want to fetch only the data with a bool value of false. I tried everything suggested here on StackOverflow but I could not find an answer to my problem.
I tried following predicates:
let predicate = NSPredicate(format: "%K == %i", #keyPath(Tag.isWrite), false)
fetchRequest.predicate = predicate
let predicate = NSPredicate(format: "isWrite == 0")
fetchRequest.predicate = predicate
let predicate = NSPredicate(format: "isWrite == %#", NSNumber(value: false))
fetchRequest.predicate = predicate
The default value for the migrated model is false for isWrite. When I don't apply the predicate and set a breakpoint before displaying my data I can clearly see that isWrite is false in the debugger. When I apply the predicate I don't get any data.
Am I missing something out?
There appears to be a problem in CoreData with attribute names beginning with “is...”. Try changing the name for your attribute.

Can't Query Single CloudKit Record by recordName UUID

I have a CloudKit app where records are stored locally in CoreData. When creating the CoreData record, a UUID is generated and populated to the system recordName field. The app creates and saves records in CoreData then uploads the records to CloudKit. This works fine.
However, I am now coding to modify a record and am not able to fetch a record by the recordName UUID. I have not been successful setting a predicate to search for only that record. I can retrieve all records with TRUEPREDICATE and I can also retrieve a single record from a field I created in the CloudKit record type. I created a field called myRecordName where I store the same UUID as the CloudKit recordName. A query using the myRecordName works fine.
I have shown three methods below for the predicates. If p1 and p2 are not both commented out the app crashes on running. I assume I am missing something really simple here. Any guidance would be appreciated. Xcode 10.2.1, iOS 12.2, testing on a real device.
If either p1 or p2 or both are not commented out, the console shows:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[CKReference rangeOfString:]:
unrecognized selector sent to instance 0x280878520'
func queryDatabase() {
let recordZone = CKRecordZone(zoneName: "PrimaryZone")
let recName = "0E763775-5AD3-4A50-BFB8-AC310180E8A2"
let recID = CKRecord.ID(recordName: recName, zoneID: recordZone.zoneID)
let searchRecord = CKRecord(recordType: "Patient", recordID: recID)
let p1 = NSPredicate(format: "%K == %#", CKRecord.Reference(recordID: recID, action: CKRecord_Reference_Action.none))
let p2 = NSPredicate(format: "%K == %#", CKRecord.Reference(record: searchRecord, action: CKRecord_Reference_Action.none))
//let predicate = NSPredicate(format: "TRUEPREDICATE")
let p3 = NSPredicate(format: "myRecordName = %#", recName)
let query = CKQuery(recordType: "Patient", predicate: p3)
let sortDescriptor = NSSortDescriptor(key: "lastNameText", ascending: true)
query.sortDescriptors = [sortDescriptor]
privateDatabase.perform(query, inZoneWith: recordZone.zoneID) { (results, error) in
if error != nil {
print("error querying database\(String(describing: error?.localizedDescription))")
} else {
DVC.ckRecords = results!
//print(results!)
print(DVC.ckRecords.first!)
}//if error else
}//perform query block
}//queryDatabase
I've never used CloudKit, so maybe I'm wrong, but…
The two problem lines of code, p1= and p2=, attempt to create a NSPredicate using a format string. Each format string contains two var arg substitutions, aka placeholders: %K and %#. So for this to work, that format string argument must be followed by two more arguments: first a key path, which should be a string (%K), and second a object for its value (%#). For your purposes here, the key path is probably just an attribute name.
But you have only provided one more argument, a CKRecord.Reference object, which the system tries to parse for the first (attribute name, string) argument. The error you are getting is typical of what happens when the system tries to parse an object such as CKRecord.Reference when it was expecting a string. Swift may be quite type safe but old-fashioned NSPredicate var args functions are not :)
To fix this problem (and move on to the next one), you should provide the key path (argument name) argument, something like this:
let p1 = NSPredicate(format: "%K == %#", "recordID", CKRecord.Reference(recordID: recID, action: CKRecord_Reference_Action.none))
let p2 = NSPredicate(format: "%K == %#", "recordID", CKRecord.Reference(record: searchRecord, action: CKRecord_Reference_Action.none))
Since I don't understand exactly what you are doing, you may need to tweak those two lines a bit. But the point is that, given your format string, you need three arguments to NSPredicate(format:), and the middle one needs to be a string representing a key path or attribute name.

Unable to fetch from coreData using Predicate as String?

I am unable to fetch data using a string as a direct argument to NSPredicate. Is there any difference between these two,
//Working case
fetchRequest.predicate = NSPredicate(format: "role == %#","GK")
In the above case, I am able to fetch the data with the predicate.
//Not Working
predicateString = String(format:"role == %#","GK")
fetchRequest.predicate = NSPredicate(format: predicateString)
Here I am unable to fetch the data.
The only difference between the above two cases is that I'm using a String variable to define the predicate. What is wrong here?
When the %# is replaced with a string variable when instantiating a NSPredicate the string variable is automatically surrounded by quotes so in your example the predicate becomes role == 'GK'
If you want to use String(format:...) you need to add the quotes yourself but in my opinion it's better to use NSPredicate(format:...) directly instead to avoid issues like this.
Format of NSPredicate is as following, where it expects a format, and format contains equaliser.
NSPredicate(format: <String>, <args: CVarArg...>)
let predicate1 = NSPredicate(format: "role == %#", "GK")
Here, if you will check the predicate1.predicateFormat, then you will get:
"role == \"GK\"",
which equilise role with string "GK" and return result in array.
But for,
let predicateString = String(format:"role == %#","GK")
let predicate2 = NSPredicate(format: predicateString)
You replaced the equaliser with simple string, which fails to return anything. You can check with predicate1.predicateFormat which will return:
"role == GK"
Here you can use "role == 'GK'" if you want to use string.
Hope it will help.

NSPredicate matching string for Cloudkit

I need to pull the record from cloudkit matching a string.
I have a User record type, with an email field. I have multiple records with the same email, but I can't get the predicate to get me the records.
I've tried all of these:
let predicate = NSPredicate(format: "email = 'julio_ukohgsp_chevez#tfbnw.net'")
NSPredicate(format: "email == %#", argumentArray: [email])
NSPredicate(format: "email IN %#", [email])
NSPredicate(format: "email contains %#", email)
NSPredicate(format: "email = %#", email)
NSPredicate(format: "email == %#", email)
My query specifies the record type:
let query = CKQuery(recordType: "User", predicate: NSPredicate(format: "email == %#", email))
When I do a predicate with value: true, I get all records including the one I want.. I know for sure I have a User with that email, multiple in fact..
What am I missing?
Edit ..
let query = CKQuery(recordType: "User", predicate: NSPredicate(format: "email BEGINSWITH %#", email))
Does work and brings back records, but I want an exact match!
I had same issue and was able to resolve it by adding Queryable index on user field in my case. Note, after index was added I needed to write data again to CloudKit to be able to perform query.
Code for creation of predicate:
let user = "test-user"
let userPredicate = NSPredicate(format: "user == %#", user)
I've been having this same issue with one of my apps. In my case, I needed to subscribe to a list of predicates where a lot of search criteria needed to be met but the search or filter predicate gave me the most trouble. I also tried:
let word1 = "Mac"
let predicate = NSPredicate(format: "Title CONTAINS %#", word1)
This worked but only when 'word1' was a single word with no spaces and 'Title' was a 'String List' in the cloud.
The clarification that I have come across in all my research is:
You need to use the term 'self' when you create a predicate and what that does is it searches all the fields that are a searchable string. Here is the information I got from Apple's Website.
Predicates support the following variable substitution strings:
Use %# for value objects such as strings, numbers, and dates.
Use %K for the name of a field. This substitution variable indicates that the substituted string should be used to look up a field name.
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.
What I ended up doing, which there has to be a better way, is my 'Title' property is still a 'String List' in the cloud and in my code I made an array of all the words in my searchTerm and with each word in the searchTerm I made a separate NSPredicate and I used those as part of a NSCompoundPredicate. I then added the NSCompoundPredicate to my subscription and it worked. Here is my example:
var searchCriteria: [NSPredicate] = []
for word in searchTerm {
let predicate = NSPredicate(format: "self CONTAINS %#", word)
searchCriteria.append(predicate)
}
let p2 = NSPredicate(format: "Price > %d", minPrice)
let p3 = NSPredicate(format: "Price < %d", maxPrice)
let p4 = NSPredicate(format: "Category == %d", category)
searchCriteria.append(p2)
searchCriteria.append(p3)
searchCriteria.append(p4)
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: searchCriteria)
I hope this helps. There has to be a better way to do this and if anyone knows, please let me know cause I understand there isn't a lot of good explanations on how to do things with CloudKit.

Fetch All CKRecords of certain type

Let's say I have records of type A in the private CloudKit database. How do I fetch ALL records of that type, without specifying record IDs or a query predicate?
CKQuery requires a non-nil predicate on initialization (see documentation), so you have to specify a predicate, even if it's always true. Ex:
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "TheRecordType", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.queryCompletionBlock = {cursor, error in
// done
}
CKContainer.defaultContainer().privateCloudDatabase.addOperation(operation)

Resources