Parsing UUID in NSPredicate - ios

I am trying to get data from the Realm database. I am using NSPredicate. And it was working well. But today I have to get data from Object who has string Id. This Id is in UUID. So when ever I try to get the value using UUID(the String ID), it gives me error like so
nable to parse the format string "Id == BD1698EE-C57D-4B8D-9D54-1D4403B2136F"'
This is the error statement. Whereas I have the following line in the code.
let resultPredicateShoppingListDetail = NSPredicate(format: "Id == \(shoppingListModel.Id)")
It does not make sense to me. Why this is happening?

Don't use string interpolation to build a predicate format, it is very
difficult to get the necessary quoting correct. As an example, this would work (note the additional
single quotes):
let uuid = "BD1698EE-C57D-4B8D-9D54-1D4403B2136F"
print(NSPredicate(format: "id == '\(uuid)'"))
// id == "BD1698EE-C57D-4B8D-9D54-1D4403B2136F"
but also fail if the uuid string contains a single quote.
Better use the %# var arg substitution for strings:
let uuid = "BD1698EE-C57D-4B8D-9D54-1D4403B2136F"
print(NSPredicate(format: "id == %#", uuid))
// id == "BD1698EE-C57D-4B8D-9D54-1D4403B2136F"
In your case (assuming that shoppingListModel.Id is a String or NSString):
let resultPredicateShoppingListDetail = NSPredicate(format: "Id == %#", shoppingListModel.Id)
Even better, use the %K keypath var arg substitution and the #keyPath
compiler directive to insert the correct key path:
let resultPredicateShoppingListDetail = NSPredicate(format: "%K == %#",
#keyPath(ShoppingList.Id), shoppingListModel.Id)
For more information about %K and %# in predicates, see
“Predicate Format String Syntax” in the “Predicate Programming Guide.”

Related

Generics method with predicated core data search won’t return correct result

I’m trying to have a generic method that search entities for a chosen string inside an attribute to see how many such entities are there, which then will decide what number or lack of number to attach to the string as it was returned. I’ve shown it in the picture. The entity type, the search string, and the attribute are all determined by input parameters. The problem is I cannot seem to make this generic method work consistently. On some projects it works, on others it doesn’t and the predicated search either returns an empty string or incomplete result. Can anyone help me figure it out? I’ve been scratching my head since last month.
Code:
private func getDefaultNameFor<T>Centityt T, defaultString: String, attribute: String)—>String{
var count = 0
var defaultName = defaultString
let type = T.self
let fetchRequest = NSFetchRequest<NSManagedObject>(entityName: “\(type)”)
let pred = NSPredicate(format: “%# CONTAINS[cd] %#", attribute, defaultString)
fetchRequest.predicate = pred
var untitledEntities: [NSManagedObject] = []
do {
untitledEntities = try context.fetch(fetchRequest)
}catch{
print("error fetching existing default names: \(error)”
}
count = untitledEntities.count
print("pred rEturns: " untitledEntities.description)
if count !=0 {
defaultName = defaultString + String(count)
}
print("default name: " defaultName)
return defaultName
}
Keypaths are substituted with %K instead of %#. (format: “%K CONTAINS[cd] %#", attribute, defaultString) worked.
Nothing wrong with using the generic, although I think not using it might still be preferable. This was a very basic sort of error I hope I had caught earlier by reviewing the predicate formatting guide.
Here’s how I solved it:
1: as suggested, I changed it from generic to taking a simple string as entity name, but the search still returns empty. I checked again at the pred but I don’t think it was formatted wrong. Am I missing something?
2: okay so I just adjusted the pred to be a simple format: “name CONTAINS[cd] ‘untitled’” and it worked fine. So why is it not working in (format: “%# CONTAINS[cd] %#", attribute, defaultString) ? I even tried to add ‘’ around the second %# but result was still empty.
3: (format: “name CONTAINS[cd] %#", defaultString) is working fine. So I guess the problem is the first substitute not functioning as keypath in the predicate format. So how to make it function as so?
4: Solved it! Keypaths are substituted with %K instead of %#. (format: “%K CONTAINS[cd] %#", attribute, defaultString) worked. This is such a basic error. I cannot believe I’ve been bugged with this for a month almost.

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.

Using regex in NSPredicate to perform a NSFetchRequest

I try to perform an NSFetchRequest with this NSPredicate:
let searchString: NSString = "приш[её]л"
let predicate = NSPredicate(format: "text MATCHES[cd] %#", searchString)
let fetchRequest = NSFetchRequest(entityName: "Data")
fetchRequest.predicate = predicate
do {
let objects = try context!.executeFetchRequest(fetchRequest) as! [NSManagedObject]
return objects
} catch {
print("Error")
}
But no results, though actually 'text' contains "пришёл"
I've found answer in this qustion: Setting up a CoreData regular expression predicate for a comma-separated list
let searchString = "приш[её]л"
let format = "(text MATCHES %#)"
let pattern = NSString(format: ".*(\\b%#\\b).*", searchString)
let predicate = NSPredicate(format: format, pattern)
You need to be aware that using MATCHES[cd] to perform regex (regular expression) matching is very expensive if you have a lot of data in your database.
You should try to avoid regular expressions if you can. And in this particular case it seems like using normalized text would allow you to use a predicate of the form %K BEGINWITH[n] %# or %K ==[n] %# wich is very fast.
To use normalized strings, you’d have both an attribute text and a normalized version of it, e.g. textNormalized which you could update in the setter for text. You’d then use textNormalized in the [n] predicates.
You can normalize text with
extension String {
public var normalizedForSearch: String {
let transformed = applyingTransform(
StringTransform("Any-Latin; Latin-ASCII; Lower"),
reverse: false)
return transformed as String? ?? ""
}
}
Be sure to check the chapter on Text in our book: https://www.objc.io/books/core-data/ — it describes how to use normalized versions of your text to make sure searches like these are efficient.

Resources