NSFetchedResultsController only sorting by 1st sort descriptor - core-data

I'm seeing an issue where the NSFetchedResultsController is only sorting by the first NSSortDescriptor in the sortDescriptors array when the data changes. It's really infuriating.
I'm using an NSFetchedResultsController to manage a tableview that is displaying a list of items. These items have an inherent order based on the number property, but a user can favorite an item. Favorited items are displayed at the top of the table view, sorted by the number property.
So, the model looks something like this:
#interface Thing : NSManagedObject
#property (nonatomic, retain) NSNumber *number;
#property (nonatomic, retain) NSNumber *favorite;
#end
#implementation Thing
#dynamic number;
#dynamic favorite;
#end
And I'm configuring my NSFetchedResultsController like so:
- (void)loadView {
...
//
// configure fetched results controller for the things table view
NSFetchRequest *fetchThings = [[NSFetchRequest alloc] init];
fetchChannels.entity = [NSEntityDescription entityForName:NSStringFromClass([Thing class])
inManagedObjectContext:[DataManager sharedInstance].managedObjectContext];
fetchThings.sortDescriptors = #[
[NSSortDescriptor sortDescriptorWithKey:#"favorite" ascending:NO],
[NSSortDescriptor sortDescriptorWithKey:#"number" ascending:YES] ];
_fetchController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchThings
managedObjectContext:[DataManager sharedInstance].managedObjectContext
sectionNameKeyPath:nil
cacheName:nil];
NSError *error = nil;
if (![_fetchController performFetch:&error]) {
NSLog(#"error performing fetch! %#", error.localizedDescription);
}
}
When the table is initially loaded, _fetchController correctly sorts the items, so you could end up with something like this:
- Thing: favorite = YES, number = 2
- Thing: favorite = YES, number = 3
- Thing: favorite = NO, number = 1
- Thing: favorite = NO, number = 4
But if you were to un-favorite Thing Number 2, it only sorts by the 1st sort descriptor, and the list looks like this:
- Thing: favorite = YES, number = 3
- Thing: favorite = NO, number = 2
- Thing: favorite = NO, number = 1
- Thing: favorite = NO, number = 4
Has anyone run into this issue or found a work around for it?
Update
It would appear that if I favorite everything, then unfavorite everything, the sorting works itself out. This leads me to believe this could be a faulting issue? Unfortunately, I'm not sure how to work around that, either.

OK, I figured it out, and it's my own fault.
Just because the field represents a BOOL doesn't mean it's actually a BOOL. The favorite field in the Thing model is actually an NSNumber, and as such, has 3 states, #(YES), #(NO), and nil. Once I made sure I was initializing the favorite field properly the sorting started working as expected again.

Related

Split NSString into multiple entries to NSMutableDictionary as key/value?

Ok, so I have this problem I have been spinning my head around for some time now.
I have a NSString like the following:
NSString* foo = #"Brand: [Ford], Model: [Focus], Color: [black]";
which I would like to transfer into a NSMutableDictionary with Brand, Model, Color as keys and Ford, Focus, black as values (without the brackets [] ), but cannot seem to find any solution to this. How do I come about accomplishing the given scenario?
Edit:
Right now I use
NSArray *stringComponents = [foo componentsSeparatedByString#","];
which gives me an array like
stringComponents = [#"Brand: [Ford]",
#"Model: [Focus]",
#"Color: [black]",];
that I need to get into the syntax proposed by Sam, but how?
NSDictionary is basically just an array that stores the values of the objects and keys, so you'd do something such as:
NSArray *keys = [NSArray arrayWithObjects:#"Brand", #"Model", #"Color", nil];
NSArray *objects = [NSArray arrayWithObjects:#"Ford", #"Focus", #"black", nil];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects
forKeys:keys];

Core data Infinite Drill Down

I am currently working on a table list with categories and subcategories, where you can navigate using a drilldown to explore the child categories until you get a detail view.
I got inspiration from iphonesdkarticles.com.
The solution in this blog for the infinite drilldown was populating an array with a plist, and a single UITableView to do the drilldown.
I wanted to use core data with the NSFetchedResultsController instead of the plist.
I got the first list of categories, but when I click one of them, I got an empty table.
I don't know if using the NSFetchedResultsController in this scenario is the most appropriate solution. Maybe I am doing something wrong when I use didSelectRowatIndex:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
Categories *category = (Categories *)[fetchedResultsController objectAtIndexPath:indexPath];
MasterViewController *theViewController = [[MasterViewController alloc] initWithStyle:UITableViewStylePlain];
theViewController.CurrentLevel += 1;
theViewController.CurrentTitle = categories.name;
detailViewController.category = category;
[self.navigationController pushViewController:theViewController animated:YES];
}
To create infinite drill down in Core Data, just make an entity a relationship of itself.

How to use core data's add & remove (NSSet) accessor methods?

In this test Core Data project, I have a one to many relationship from "Customer" to "Products" and this relationship is named 'products'. Customer's attribute is 'name' and Product's attribute is 'item'. I've subclassed the entities and Xcode has produced the following for Customer:
#interface Customer : NSManagedObject
#property (nonatomic, retain) NSString * name;
#property (nonatomic, retain) NSSet *products;
#end
#interface Customer (CoreDataGeneratedAccessors)
- (void)addProductsObject:(Products *)value;
- (void)removeProductsObject:(Products *)value;
- (void)addProducts:(NSSet *)values;
- (void)removeProducts:(NSSet *)values;
#end
To add, let's say, one customer (John Doe) and two items (Widget 1 & Widget 2), I can use the accessor method addProductsObject as follows:
...
// Add (1) customer object
Customer *custObj = [NSEntityDescription insertNewObjectForEntityForName:[entity name] inManagedObjectContext:context];
[custObj setValue:#"John Doe" forKey:#"name"];
// Add (2) products for John Doe
for (int foo=0; foo<2; foo++) {
self.product = [NSEntityDescription insertNewObjectForEntityForName:#"Products" inManagedObjectContext:context];
NSString *objString = [NSString stringWithFormat:#"Widget %d", foo];
self.product.item = objString;
[custObj addProductsObject:self.product];
}
...
This works fine but, if possible, I'd like to make use of the addProducts accessor.
I'm assuming that the generated accessor method addProducts is there to facilitate the insertion of a 'set' of objects with something like:
...
NSSet *itemSet = [[NSSet alloc] initWithObjects:#"Widget 1", #"Widget 2", nil];
[custObj addProducts:itemSet];
...
but this fails. In this try, I understand a product object hasn't been explicitly created and, as such, an explicit product assignment hasn't taken place but I thought, perhaps, the accessor would take care of that.
What, therefore, is the correct usage of addProducts, and for that matter, removeProducts?
Thanks.
The set you are passing to addProducts contains NSString, not Products.
NSMutableSet* products = [NSMutableSet set];
Products* product = [NSEntityDescription insertNewObjectForEntityForName: #"Products" inManagedObjectContext: context];
product.item = #"Widget 1";
[products addObject: product];
product = [NSEntityDescription insertNewObjectForEntityForName: #"Products" inManagedObjectContext: context];
product.item = #"Widget 2";
[products addObject: product];
[customer addProducts: products];
Per se, the accessor have no way to know what the strings you gave it in first place were for. You have to provide a set containing the right kind of entities.
That said, you can define your own accessor, taking a set of strings and inserting properly initialized Products in your relationship : addProductsWithStrings: per example.

Sum a CoreData attribute

I've an entity "Expense" with an attribute (float) called "value". I've a table view populated from CoreData with NSFetchedResultsController.
I'm trying to show in a label (or table header) the total sum of "values" of all my expenses, but I can't implement a solution after reading Apple Docs and googling different forums. For sure beginner disorientation.
Appreciate any indications about the best way to implement this king of operation, or any kind of code that shows similar solution.
first of all. You should use Decimal (the core data name for nsdecimalnumber) and NSDecimalNumber if you want to calculate and store a currency.
I implemented the code you need with float. But you should really change it to NSDecimalNumber. Read this to know why you should do this
If you want to add the expense value to the section header it's easy. You basically take the expense of all objects in the section and sum it up.
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
float expense = 0;
for (NSManagedObject *object in [sectionInfo objects]) {
expense += [[object valueForKey:#"expense"] floatValue];
}
return [NSString stringWithFormat:#"%# [Expense: %f]", [sectionInfo name], expense];
}
Even if you don't use sections in your table this will work. But you should change the returned string then.
I think you should be able to understand it. There is also a more general way to do this. I wrote it a little bit more verbose for you. It uses 3 lines instead of 1 in the for-loop but it does exactly the same thing.
float expense = 0;
for (NSManagedObject *object in [self.fetchedResultsController fetchedObjects]) {
NSNumber *objectExpenseNumber = [object valueForKey:#"expense"];
float objectExpense = [objectExpenseNumber floatValue];
expense = expense + objectExpense;
}
NSLog(#"Expense: %f", expense);
Not really much to explain.
edit: this would be the code if you use NSDecimalNumber
NSDecimalNumber *expense = [NSDecimalNumber decimalNumberWithString:#"0"];
for (NSManagedObject *object in [self.fetchedResultsController fetchedObjects]) {
NSDecimalNumber *objectExpenseNumber = [object valueForKey:#"expense"];
expense = [expense decimalNumberByAdding:objectExpenseNumber];
}
NSLog(#"Expense: %#", expense);

Core Data fetches based on properties of 'ordered' relationships

My app has smart folder like functionality: a predicate is setup with a NSPredicateEditor and used to fill the folder with a fetch request.
The entity used in the search has a to-many relationship. The relationship is ordered, in the sense that an index is stored in the destination entity for sorting purposes.
My problem is that I would like to build in a rule based on the last values in the ordered relationship, but I can't figure out how to build a predicate to do this, because the relationship is not an array. Core data doesn't actually know about the order.
I have a readonly property on the class that returns the ordered items, but this doesn't seem to help with the fetch request because the property is not available in the core data store.
The only option I can think of is to de-normalize and store the last items in the relationship ordered in a separate property. Is that the only solution?
Well, assuming I have understood the problem correctly, I'd do it like this. Lets say you've got two entities, TopEntity has a (NSString *)name property and a to-many relationship to MyEntity which has a (NSString *)data property and (NSInteger)order property.
Lets say you want the TopEntity objects which match a given string, and whose MyEntity orders are satisfy a certain condition, then you can do it with two predicates and an NSFetchRequest like so....
NSManagedObjectContext *context = [self managedObjectContext];
// Create some top level entities
TopEntity *aTop = [TopEntity insertInManagedObjectContext:context];
aTop.name = #"This is Your Name";
TopEntity *bTop = [TopEntity insertInManagedObjectContext:context];
bTop.name = #"This aint a Name";
TopEntity *cTop = [TopEntity insertInManagedObjectContext:context];
cTop.name = #"This is My Name";
// Add some data
NSInteger i, len = 30;
for(i=0; i<len; i++) {
// Create a new object
MyEntity *entity = [MyEntity insertInManagedObjectContext:context];
entity.orderValue = i;
entity.data = [NSString stringWithFormat:#"This is some data: %d", i];
if(i < 10) {
[aTop addObjectsObject:entity];
[entity addTopObject:aTop];
} else if (i < 20) {
[bTop addObjectsObject:entity];
[entity addTopObject:bTop];
} else {
[cTop addObjectsObject:entity];
[entity addTopObject:cTop];
}
}
// Save the context
NSError *error = nil;
[context save:&error];
// A predicate to match against the top objects
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name BEGINSWITH %#", #"This is"];
// A predicate to match against the to-many objects
NSPredicate *secondPredicate = [NSPredicate predicateWithFormat:#"ANY objects.order < %d", 5];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:[NSEntityDescription entityForName:#"TopEntity" inManagedObjectContext:context]];
[fetch setPredicate:predicate];
NSArray *result = [[context executeFetchRequest:fetch error:&error] filteredArrayUsingPredicate:secondPredicate];
for(TopEntity *entity in result) {
NSLog(#"entity name: %#", entity.name);
}
So, essentially you can just wrap the results of your fetch request with another predicate and use the ANY keyword.
I've got no idea how efficient that is, but it works for this case. Running the above will output "This is Your Name" i.e. it matches the first TopEntity.
I don't think there's a way to limit to n results in a predicate, only at the fetch request level.
Aside from referencing the last n items in a relationship as you mentioned, you might try a boolean attribute "lastN" and flip them on/off when you curate the order of the list (say, during user-initiated sort or drag-and-drop reordering).
Alternatively, you could create a separate fetch request for each searched thing that sorts by your sort key, ordered descending, and is limited (via -setFetchLimit: ) to n results.
Tracking this as a relationship or an attribute is somewhat "messy" whereas the fetch limit is more expensive (because of multiple round trips). If your reordering is done by one-off user actions, it might be better performance-wise to use the relationship or attribute approach since the work is amortized rather than done all at once in a series of fetches. I haven't found a better way myself and will follow this one closely. :-)

Resources