Using regex in NSPredicate to perform a NSFetchRequest - ios

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.

Related

What predicate do I need to provide to return a CNGroup containing a given string in its name?

I'm trying to filter a list of CNGroups with a given string, but I'm struggling to work out how to form the correct predicate.
I'm fairly new to working with CNContactStore and struggling to find documentation that makes sense to me. I'm ok with predicates for CoreData, but this appears to be different. I'm working on the assumption that this is even possible.
let searchText = "Work"
let predicate = NSPredicate(format: "name CONTAINS[c] %#", searchText)
do {
let groups = try ContactStore.default.groups(matching: predicate)
completion(.success(groups))
} catch {
completion(.failure(error))
}

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.

How to set NSFetchRequest propertiesToFetch with a custom function using NSExpression

I have a attribute "message" of type String in CoreData Entity, which stores text and sometimes including URLs . Now i need to fetch all the url's from the entity.
let predicate = NSPredicate(format:"message CONTAINS[cd] %#", "http")
let fetchRequest = self.getfetchRequest(entityName: "Messages", sectionKey:nil, predicate:predicate)
let expressionDescription = NSExpressionDescription()
expressionDescription.name = "urlFromMsg"
expressionDescription.expressionResultType = .StringAttributeType
let expression = NSExpression(forFunction: "toUrl", arguments: [NSExpression(forKeyPath: "message")])
expressionDescription.expression = expression
fetchRequest.propertiesToFetch = [expressionDescription]
fetchRequest.resultType = NSFetchRequestResultType.DictionaryResultType
Here toUrl is a custom function that extends String.
extension String {
func toUrl()->[String]?{
let detector = try! NSDataDetector(types: NSTextCheckingType.Link.rawValue)
let matches = detector.matchesInString(self, options: [], range: NSRange(location: 0, length: self.utf16.count))
var urls = [String]()
for match in matches {
urls.append((self as NSString).substringWithRange(match.range))
}
return urls
}
}
i am getting crash at this line
let expression = NSExpression(forFunction: "toUrl", arguments: [NSExpression(forKeyPath: "message")])
How to set the custom method properly in NSExpression.
When using a custom NSExpression in a fetch request and a SQLite-backed Core Data store, Core Data attempts to translate the expression into a SQL query. That helps with performance, but it means that not all valid NSExpressions are valid for use with Core Data.
Specifically, an NSExpression that relies on a custom function can't be used with a Core Data fetch, because Core Data can't translate arbitrary code into SQL.
You'll need to drop this NSExpression from the fetch and convert to URLs using the results of the fetch. You could also switch to a non-SQLite Core Data store, but that's generally much worse for a variety of reasons unless your persistent store contains very small amounts of data.

Do this to core data: "Select * WHERE "attribute" == "Some string" "

I did manage to do this by making a function with a loop that checks the attribute to a string. But i'm looking for a better way to do this.
In sql I do this:
Select * WHERE "attribute" == "string"
Is there a method to do this in swift?
My function looks like this:
func tableData()
{
let objects = retrieveValues("JobTime") //Retrieve a NSMutableArray
if !objects.isEmpty
{
for var index = 0; index < objects.count; ++index
{
if objects[index].valueForKey("jobTitle") as? String == transferTitle
{
print("Job title matched: \(index)")
}
else
{
print("Nothing here!")
}
}
}
}
In order to perform fetch request in CoreData you have to initialise NSFetchRequest class. In order to specify in what kind of entities you are interested you create NSPredicate class. It gives you ability to specify pretty advanced queries. In most cases the simplest way to create NSPredicate is by using format string - details about the syntax can be found Apple's Predicate Format String Syntax document.
You can find example of how you can perform fetch request in CoreData (and Swift) below.
let managedObjectContext: NSManagedObjectContext
let predicate = NSPredicate(format: "jobTitle == %#", "Programmer")
let fetchRequest = NSFetchRequest.init(entityName: "People")
fetchRequest.predicate = predicate
//fetchRequest.sortDescriptors = [] //optionally you can specify the order in which entities should ordered after fetch finishes
let results = managedObjectContext.executeFetchRequest(fetchRequest)
You can pass the query to CoreData and only retrieve what you want. The NSManagedObjectContext class has a executeFetchRequest method that you call to retrieve data from the database. You pass NSFetchRequest object to it. That object contains a NSPredicate, which defines your query.

NSPredicate to use with Transformable property

I have array of strings which I save in Core Data as Transformable. From json I am gettings needed strings like this:
if let mealTimes = dictionary["mealTimes"] as? [String]{
self.mealTimes = mealTimes
}
Now I would like to filter fetch results by strings in mealTime property. I have tried this way:
let predicate = NSPredicate(format: "mealTimes LIKE[cd] '*%#*'", "breakfast")
var breakfastContents = StaticContent.fetchStaticContentsWithPredicate(predicate, inManagedObjectContext: self.coreDataStack.context)
if (breakfastContents.count > 0) {
breakfast = breakfastContents.first!
}
The problem is that result array is empty but I know I have breakfast string in some content. So how can I fix it? I was reading something about saving transformable as NSData so it would need some great trick. I was trying to use LIKE (with and without *) command and CONTAINS.
Extension for StaticContent:
class func fetchStaticContentsWithPredicate(predicate: NSPredicate, inManagedObjectContext managedObjectContext: NSManagedObjectContext) -> [StaticContent] {
// Define fetch request/predicate
var fetchRequest = NSFetchRequest(entityName: "StaticContent")
// Assign fetch request properties
fetchRequest.predicate = predicate
fetchRequest.sortDescriptors = [NSSortDescriptor(key: "id", ascending: true)]
// Handle results
let fetchedResults = managedObjectContext.executeFetchRequest(fetchRequest, error: nil) as! [StaticContent]
return fetchedResults
}
You should refactor your data model to get rid of the "array of strings". This is not a very sound way to store this type of values.
Instead, consider a relationship with a new entity that captures those strings. Alternatively, device a scheme where you store a string and have a unique character that separates the records (e.g ; or something similar). Then any simple string predicate would work, like this:
NSPredicate(format: "mealtimes contains[cd] %#", "breakfast")

Resources