CoreData predicate: string property length? - ios

Say if I have an entity Fragment, it has an attribute 'text' which is a string, I want to query the list of Fragment whose text is of length 5:
[NSPredicate predicateWithFormat:#"position == %# AND text.length == %d", pos, 5];
It does not work (ie returns no result), but if I remove text.length in the query it works and I'm certain that there are texts of length 5, so what do I need to change it to?
Thanks!

There is no length attribute for strings in the NSPredicate. Use regex instead.
Your predicate should look as follows:
[NSPredicate predicateWithFormat:#"position == %# AND text MATCHES %#", pos, #".{5}"];

You cannot use Objective-C functions like length in a Core Data fetch request. But you
can replace it with the "LIKE" operator, which does a simple pattern matching:
[NSPredicate predicateWithFormat:#"text LIKE %#", #"?????"];
An interesting point is that Core Data does not throw an exception or return with an error,
but just ignores the length method, i.e. it just uses the predicate "text = '5' instead.
This can be seen by activating Core Data debug output by setting the launch argument
-com.apple.CoreData.SQLDebug 3
(which is generally a good method to locate Core Data fetch problems).

I think that it's taking text.lengh as a relationship. Try to set a predicate only with position and then do a loop looking for text.length == 5.

Related

Swift - iOS NSPredicate with optional NSDecimalNumber

I'm trying to use the following NSPredicate with an NSDecimalNumber to check if a value is greater than 0.
let addPredicate:NSPredicate = NSPredicate(format:"balance > 0", argumentArray: [])
"balance" is an Optional NSDecimalNumber attribute in a Core Data entity. The result is coming up empty (no errors), even when I try balance < 0, or balance == 0. I can confirm that there are no null balance records. I suspect it is due to the "optional" nature of the attribute. Predicates comparing optional dates and NSNumber values are working with no problems.
Is there a different syntax I should be using to compare an Optional NSDecimalNumber?
The fact that it is optional should not make a difference.
There is a different syntax you can use but yours should work too.
You can try the object syntax:
NSPredicate(format:"balance > %#", argumentArray: [NSDecimalNumber.zero])
But it does exactly the same as your predicate and should return the same result.
If you tried all the options i.e. grater than 0 and lower than zero and equal to zero (the equal to zero should also return entities with nil balance!), then the most simple explanation that there are no objects at all.
I don't think that the predicate is the problem, I would suggest you to check if there is some problem creating/saving new entities or maybe they are deleted ...
Hope it helps.
Have just realised the issue was that I was attempting to use a transient attribute ("balance") in the fetch predicate. So instead I have used a filter on the fetched results and this gives the correct output.
filteredItems = (fetchedResultsController.fetchedObjects?.filter { item in
let searchResult:Bool = (item.balance!.compare(NSDecimalNumber.zero) == .orderedDescending)
return searchResult
})!

NSString localizedCompare: inconsistent results given longer strings

We're trying to use an NSFetchedResultsController to return people names and populate a UITableView in sorted order, using localizedCompare:. We're also trying to provide a section index in the UI (the right column of first characters of each section). We provide the NSFetchedResultsController with a selector on our entity which provides the section each entity should belong to (specifically, the first character of the person's name, capitalized).
When dealing with people names which utilize Unicode code points we've run into an issue. NSFetchedResultsController complains the entities are not sorted by section.
Specifically:
reason=The fetched object at index 103 has an out of order section name 'Ø. Objects must be sorted by section name'}, {
reason = "The fetched object at index 103 has an out of order section name '\U00d8. Objects must be sorted by section name'";
The issue appears to be that the comparison value returned by localizedCompare: is different for the whole "word" versus the leading character.
The following tests pass though I would expect consistent comparison results between ("Ø" and "O") vs. ("Østerhus" and "Osypowicz").
- (void)testLocalizedSortOrder300
{
NSString *str1 = #"Osowski";
NSString *str2 = #"Østerhus";
NSString *str3 = #"Osypowicz";
NSString *letter1 = #"O";
NSString *letter2 = #"Ø";
//localizedCompare:
//"Osowski" < "Østerhus"
NSComparisonResult res = [str1 localizedCompare:str2];
XCTAssertTrue(res == NSOrderedAscending, #"(localizedCompare:) Expected '%#' and '%#' to be NSOrderedAscending, but got %#", str1, str2, res == NSOrderedSame ? #"NSOrderedSame" : #"NSOrderedDescending");
//"Østerhus" < "Osypowicz"
res = [str2 localizedCompare:str3];
XCTAssertTrue(res == NSOrderedAscending, #"(localizedCompare:) Expected '%#' and '%#' to be NSOrderedAscending, but got %#", str2, str3, res == NSOrderedSame ? #"NSOrderedSame" : #"NSOrderedDescending");
//"O" < "Ø"
res = [letter1 localizedCompare:letter2];
XCTAssertTrue(res == NSOrderedAscending, #"(localizedCompare:) Expected '%#' and '%#' to be NSOrderedAscending, but got %#", letter1, letter2, res == NSOrderedSame ? #"NSOrderedSame" : #"NSOrderedDescending");
}
So, the question ultimately is, given a person name (or any other string) which utilize Unicode code points, how do we properly (in a localized manner) return a section name which will correspond with the sort order as dictated by localizedCompare:?
Additionally, what's going on with the localizedCompare: apparently treating "Ø" and "O" as NSOrderedSame when followed by additional characters?
I expect localizedCompare: is using a specific combination of NSStringCompareOptions flags that are causing this behavior.
https://developer.apple.com/documentation/foundation/nsstringcompareoptions?preferredLanguage=occ
You might get the outcome you want by using compare:options: and turning on NSDiacriticInsensitiveSearch.
For generating the section index, it might be best to strip the value of all extended characters first, and then take the first letter. Something like:
[[str1 stringByFoldingWithOptions:NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch] substringToIndex:1]
That way a name starting with an accented letter such as "Édward" will get converted to "Edward" before you take the first letter for the section.
Yeah, been there. The only solution I found was to create a second field for search that simplifies the characters (don't remember off hand the method) and store it as a second field which is used for search. Not super elegant but it worked.
Ultimately the approach which solved this was to store normalized section names in the database.
#MartinR suggested SO post lead me to https://stackoverflow.com/a/13292767/397210 which talks about this approach and was the key "ah ha" moment to solve it.
While this does not explain the goofy behavior of localizedCompare: apparently treating "Ø" and "O" as NSOrderedSame when followed by additional characters it is, IMHO, a more robust and complete solution which works for all Unicode code points, in our testing.
Specifically, the approach is:
Create (or utilize an existing) field on your entity to receive a normalized section name for the entity (let's call it sectionName).
Populate this field (sectionName) with the normalized section name*, initially, and as needed (when the person name changes, for instance).
Use this section name field (sectionName) for the sectionNameKeyPath of NSFetchedResultsController -initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:
For the sort descriptors used by the fetch request passed to the NSFetchedResultsController be sure to sort first by section name then by how to sort the contents of the section (person name, for instance), paying attention to the use of the localized version of the comparison selectors. e.g.:
[NSSortDescriptor sortDescriptorWithKey:#"sectionName" ascending:YES selector:#selector(localizedStandardCompare:)],
[NSSortDescriptor sortDescriptorWithKey:#"personName" ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)]
Test.
*Normalized Section Name
We need to be careful about assuming what the first "character" is when dealing with unicode. "Characters" may be composed of more than one character.
See https://www.objc.io/issues/9-strings/unicode/
and also Compare arabic strings with special characters ios
This is the direction I used to generate a normalized section name:
NSString *decomposedString = name.decomposedStringWithCanonicalMapping;
NSRange firstCharRange = [decomposedString rangeOfComposedCharacterSequenceAtIndex:0];
NSString *firstChar = [decomposedString substringWithRange:firstCharRange];
retVal = [firstChar localizedUppercaseString];
Hopefully this approach is clear and useful to others, and thanks for the assist, all.

Is there a way to setup NSPredicate based on actual value stored in CoreData?

I need to set up my NSPredicate base on actual value stored in CoreDate.
Assuming we have a CoreData entity: "Graduate". we want to fetch graduate whose graduation date is earlier than Date1 if his/her graduation date is not null. Otherwise, I want to check if his/her birthday is earlier than Date2.
The predicate would look like this:
"if graduationDate != null graduationDate < %# else birthday < %#"
So I actually have two predicate. The second one should only come into effect if the field which first predicate wants to check is null.
My thoughts:
I can process this in my APP instead of CoreData, but just wonder is there a way to use only one NSPredicate to sort it out.
Some posts mentioned that NSPredicate is an equivalent to SQL Where Clause, which makes sense to me. And I think what I need here is going to beyond what Where Clause is capable of.
My question is not about how to filter fetch result using NSPredicate. It's about how to setup NSpredicate using a flow control like if else clause
You can use NSCompoundPredicate to combine smaller predicates.
let graduates = NSPredicate.init(format: "graduationDate != NULL AND graduationDate < %# ", graduationDate);
let oldPeople = NSPredicate.init(format: "graduationDate == NULL AND birthday > %# ", cutOffBirthday);
let predicate = NSCompoundPredicate.init(orPredicateWithSubpredicates: [graduates, oldPeople]);

Core Data - Fetch Request results not matching the predicate

I'm pretty confused as to why the following is not working:
As you can see in the debug console, the value for newerContentAvailable is 0 even though I only want objects who have this value set to 1. But it made its way into the results anyway.
Yes, I'm using MagicalRecord, but doubtful this has anything to do with it. It's an old, mature codebase and MR_findAllWithPredicate:... just creates a fetch request on that data model and sets the predicate of the fetch.
Is there something I've not understood about Core Data? I admit it is a beast of a framework and best practices are scarce.
Would be seriously grateful for some help!
I believe the problem may be a result of the attribute name you have used: names beginning with new... seem to cause some unexpected behaviour (*). Try changing the attribute name to see if that sorts it.
(*) See for example this question and answer.
NSNumber value of NSNumber is not substituted when used with %# format. You have to get the intValue or floatValue (doubleValue etc) which returns the correct type.
The predicate should be,
NSPredicate *findPred = [NSPredicate predicateWithFormat:#"newerContentAvailable == 1"];
OR
NSNumber *number = #1; //Or any other number
NSPredicate *findPred = [NSPredicate predicateWithFormat:#"newerContentAvailable == %d", [number intValue]];

NSPredicate to Match the backslash

I want to fetch entity by name matching the string \\xxxx\\yyyy\\zzzz from core data so i have used NSPredicate like NSPredicate *myPredicate = [NSPredicate predicateWithFormat:" name LIKE %#", \\xxxx\\yyyy\\zzzz]; But returns null for some times.I doubt that issue with backslashes so please help me any one.Is any way to fetch this entity from core data?
"LIKE" in a predicate does a simple wildcard matching (with ? and *).
As a consequence, the backslash character has a special meaning and has to be escaped
twice (once for the string literal and once for the "LIKE" operator):
[NSPredicate predicateWithFormat:"name LIKE %#", #"\\\\xxxx\\\\yyyy\\\\zzzz"]
But if you don't need the wildcard matching then use "==" (or "BEGINSWITH", "CONTAINS") instead:
[NSPredicate predicateWithFormat:"name == %#", #"\\xxxx\\yyyy\\zzzz"]

Resources