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);
Related
I need to fetch an object using Core Data. The object has a property datesArray(Array of NSDate objects stored as NSData) which I use to store array of dates. I need to check if the array contains todays date and then use the object.
NSFetchRequest * request = [NSFetchRequest fetchRequestWithEntityName:#"MyEntity"];
NSError * error;
NSArray * fetchedArray = [context executeFetchRequest:request error:&error];
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"entity.datesArray CONTAINS %#",[NSDate date]];
for (MyEntity * entity in fetchedArray) {
NSMutableArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:entity.datesArray];
[array filterUsingPredicate:predicate];
if (array.count >0) {
[_myMutableArray addObject:entity];
}
}
You really should not be storing dates like this. You are losing the value of using Core Data. If you have an array of something then that should be on the other side of a relationship which you can then retrieve efficiently from the underlying persistent store.
I suspect that your predicate lacks precision as a date object is down to the nanosecond. If you are looking to match something from "today" then you need to work on something with less precision. Perhaps a string in a specific format or store it as a number and then search within a range (less than X and greater than Y type of search).
Again, storing the actual dates in managed objects makes this question much easier but you are still going to be dealing with a precision problem.
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.
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.
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. :-)
say i have NSManagedObject A, that has a many-to-many relationship to NSManagedObject B.
I have one saved instance of A and B. (not yet related)
Now I want A to save the instance of B twice in its relationship, which is of course a set.
Though, since its a set it stores only one reference of B.
see: (not syntax checked)
NSArray *tmpArray = [NSArray arrayWithObjects: B1, B1, nil];
[A setB: [NSSet setWithArray: tmpArray]];
-> only one B is stored in that relationship..
Is it possible to keep track of both B's ?
You can't do this. Core Data is not maintaining an array but an object-graph.
An object-graph store the relationships between objects. Since each object is unique, it makes no sense to have a duplicate relationship because that conveys no information. Suppose you have an object Person instance Jane that has a brothers relationship which contains three objects, Steve,John and Mike. It would be logically nonsensical to have two relationships to Steve because the real Steve that the object models isn't Jane's brother twice. Even if Jane did have two brothers named Steve, they would still be seperate individuals requiring their own objects to represent them in the object-graph.
If you find yourself thinking you require duplicate relationships, then you've probably misunderstood something about how the object-graph works.
I had a similar issue and searched but could not find anything. Indeed I was thinking about the relationship incorrectly.
The app is arranging a bunch of Items on a Board in sequence with repeats.
EX. Do A, Do B, Do C, Do A again, Do D
I created a good old-fashioned join table with the Item, Board and board position called BoardItemPositon.
From the Board entity you can hide all of the join table messiness and get and set an array.
#implementation Board
#dynamic boardItems;
- (void)setItems:(NSArray *)items{
//the ordered set of links
NSMutableOrderedSet *boardItemSet = [NSMutableOrderedSet new];
int i = 1;
for (Item *item in items) {
BoardItemPosition *boardItemPosition = (BoardItemPosition *)[NSEntityDescription insertNewObjectForEntityForName:#"BoardItemPosition"
inManagedObjectContext:[self managedObjectContext]];
NSNumber *num = [NSNumber numberWithInt:i++];
[boardItemPosition setItemPosition:num];
[boardItemPosition setItem:item];
[boardItemSet addObject:boardItemPosition];
}
//delete the old links
for (BoardItemPosition *boardItemPosition in [self boardItems]) {
[self.managedObjectContext deleteObject:[self.managedObjectContext objectWithID:boardItemPosition.objectID]];
}
//set the new links
[self setBoardItems:boardItemSet];
}
- (NSArray *)items{
//pull out item and add to array
NSMutableArray *itemArray = [NSMutableArray new];
for (BoardItemPosition *boardItemPosition in [self boardItems]) {
[itemArray addObject:[boardItemPosition item]];
}
return itemArray;
}