Core Data - Fetch Request results not matching the predicate - ios

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]];

Related

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.

XCode Magical Record, MR_findByAttribute - looking for strings with certain word (regex?)

I have an API, from which I load Shop objects.
Shop object has NSString *assortment field, containing products you can buy in shop, in my syntax it's eg. "water,milk,potatoes,bread,ketchup".
NSString *assortment = #"water, milk, potatoes, bread, ketchup";
I load shops from my local database to an NSArray *shops.
NSArray *shops = [Shop MR_findAll];
I want to find all objects in my shops array (using MR_findByAttribute maybe?) that contain word "bread" in their assortment field.
I know I can split my string and check for every single assortment position - but that sounds not intuitive to me. I feel like there's better solution, maybe:
NSArray *shopsWithBread = [Shop MR_findByAttribute:#"assortment" withValue:#"<bread regex?>"];
Do you know any nice way to do it? Do you have maybe another good idea how to find all those shops? Maybe you're sure it can't be done this way and I have to write search engine myself?
Thank you in advance for all answers.
Karol
Ok, I've found the solution - I'm using predicate.
NSPredicate *breadSearchPredicate = [NSPredicate predicateWithFormat:#"assortment contains[cd] %#", #"bread"];
NSArray *shopsWithBread = [Shop MR_findAllWithPredicate:breadSearchPredicate];
assortment is my field name; contains[cd] is a kind of instruction, checking for containing a string give - here #"bread".
NSPredicate - nicely explained here: http://nshipster.com/nspredicate/

Queries with Mobile Services Azure iOS

I am trying to write a query that gets a row when the id is equal to 1 (at the moment).
NSPredicate *predicate = [NSPredicate predicateWithFormat:[apt[#"id"] isEqualToNumber:1]];
So where id (from another database) is equal to 1. Now this is complaining all sorts of things but its saying it needs to be a BOOL. Is NSPredicate needing a BOOL?
Not sure I understand, or is there another way I can query? Can I use T-SQL in Mobile Services SDK Azure for iOS?
The predicate with format expects a string, you are sending in a bool. [apt[#"id"] isEqualToNumber:1] returns a bool.
Use the predicate in this manner (I have little experience with formatting predicates, so this will likely not actually work for you to copy and paste in).
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"id == 1"];
This will work if your objects in the array you are filtering have a property called id.
Something I understand a littler better is predicates with blocks. This could work for you too (I'm not entirely sure of your data structure, so you are gong to have to modify to suit your needs.
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary *bindings) {
return [object[#"id"] isEqualToNumber:1]; // Return YES for each object you want in filteredArray.
}]

CoreData predicate: string property length?

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.

NSPredicate/TableView only displays one item, why?

I'm working on an app where I have two entities, Post <-->> StreamType. When I create posts I assign StreamType:s like this:
// streamType == one of my default streamTypes
[post addStreamTypesObject:streamType];
My predicate for finding posts that have a certain StreamType looks like this:
predicate = [NSPredicate predicateWithFormat:#"ANY streamTypes.type = %#", [NSNumber numberWithInt:self.pageType]];
I'm not sure why this happens. Any ideas?
Edit
What I basically want is to fetch all Posts that have the right StreamType. Seemed after all that my fetchrequest only returns 1 item from the database. So probably nothing wrong with my tableview.
Edit 3
The problem was with my relationship, should be many-to-many, not one to many. Therefore it only returned one Post item.
First: test if the other code is ok. Simply, remove the predicate (comment the setPredicate line). You should see ALL objects in your tableview.
Right?
Second: check if self.pageType is set correctly. I don't see in your code how you set self.pageType
Test your predicate, add an NSLog like this and check if the result is ok:
NSLog(#"ANY streamTypes.type = %d", self.pageType);
Third:
As far as I understand, you have this situation:
One Post has only one stream type
One stream type has multiple posts.
The ANY keyword is used in situations where you want, for example, obtain all stream types where a particular condition is satisfied at least one time. for example (assuming you have a "content" instance variable on your post, containing the text of the post)
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY post.content CONTAINS[cd] %#", #"aWord"];
In this case, you will obtain all stream types in which there are posts containing "aWord" particular word in the text.
Your case is simpler. I think that you should simply use:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"streamTypes.type = %d", self.pageType];
Try and let me know
I was just having the same issue using Core Data with Swift and just wanted to add this answer in incase anybody else is having a similar issue.
This was my NSPredicate code:
let predicate = NSPredicate(format: "routine == %#", self.selectedRoutine)
Routine holds multiple exercise objects which I was trying to return. An exercise can only have one routine but a routine can have many exercises.
Turns out I'd forgotten to select 'To Many' as the relationship type for the exercises relationship in my routine entity using the Data Model inspector. It was set as 'To One'.

Resources