I am relatively new to iOS (I apologize beforehand if I am not clear or if the problem has been addressed elsewhere) and I have gone through a lot of tutorials to learn the basics. Still, I have been facing issues with doing the following with core data:
- I understand how to fetch data from DB with core data, sort it and display it, but now I don't know how to programatically work an attribute (it's a transient attribute in a table in my DB) that needs to be computed and displayed in a static tableview cell within a tableview controller embedded in a navigation controller.
For illustration: I have a table with two variables ( InputValue (integer) and inputDate (NSDate) ) and I want to compute the average/min/max of each daily entered value per year or day. These values will be then displayed and updated each time in a tableview static cell (each computed value in a cell.)
EDIT: I added some code from the tableview controller here:
- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
{
//Configure the cell to show the user value as well as showing the date of input
PVMesswert *wert = [self.fetchedResultsController objectAtIndexPath:indexPath];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"dd.MM.yyyy"];
cell.textLabel.text= [[NSString alloc] initWithFormat:@"%@ kWh", wert.inputValue];
cell.detailTextLabel.text = [dateFormatter stringFromDate:wert.inputDate];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{ //should I have a different cell identifier for each row where I want to display a result of the calculations?
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell.
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController != nil) {
return _fetchedResultsController;
}
// Create and configure a fetch request with the PVMesswert entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"PVMessWert" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
// Create the sort descriptors array.
NSSortDescriptor *datumsort = [[NSSortDescriptor alloc] initWithKey:@"inputDate" ascending:NO];
NSSortDescriptor *valueDescriptor = [[NSSortDescriptor alloc] initWithKey:@"InputValue" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:datumsort, /*authorDescriptor,*/ nil];
[fetchRequest setSortDescriptors:sortDescriptors];
// Create and initialize the fetch results controller.
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"dateToString" cacheName:@"Root"];
_fetchedResultsController.delegate = self;
// Memory management.
return _fetchedResultsController;
}
So, in another tableview I have, I want to display things like: avg/max/min Inputvalue per year (based on attribute input date NSDat.). Should I use NSExpression and in which case, how?
You should use aggregate functions for these kind of calculations.
You could retrieve all the max/min/avg information in one fetch request that should be relatively fast.
The code for an aggregate function should look something like this:
//Function expression generator
- (NSExpressionDescription*) aggregateFunction:(NSString*)function
forKeyPath:(NSString*)keyPath
name:(NSString*)name
resultType:(NSAttributeType)resultType
{
NSExpression* keyPathExpression = [NSExpression expressionForKeyPath:keyPath];
NSExpression* funcExpression = [NSExpression expressionForFunction:function arguments:@[keyPathExpression]];
NSExpressionDescription* expressionDescription = [NSExpressionDescription new];
[expressionDescription setName:name];
[expressionDescription setExpression:funcExpression];
[expressionDescription setExpressionResultType:resultType];
return expressionDescription;
}
Your fetch request should look like this:
//someValue is an Integer64 property
//someDate is a Date property
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:@"Product"];
NSExpressionDescription* minValueField = [self aggregateFunction:@"min:"
forKeyPath:@"someValue"
name:@"minValue"
resultType:NSInteger64AttributeType];
NSExpressionDescription* avgValueField = [self aggregateFunction:@"average:"
forKeyPath:@"someValue"
name:@"avgValue"
resultType:NSInteger64AttributeType];
NSExpressionDescription* maxValueField = [self aggregateFunction:@"max:"
forKeyPath:@"someValue"
name:@"maxValue"
resultType:NSInteger64AttributeType];
NSExpressionDescription* minDateField = [self aggregateFunction:@"min:"
forKeyPath:@"someDate"
name:@"minVDate"
resultType:NSDateAttributeType];
NSExpressionDescription* avgDateField = [self aggregateFunction:@"average:"
forKeyPath:@"someDate"
name:@"avgDate"
resultType:NSDateAttributeType];
NSExpressionDescription* maxDateField = [self aggregateFunction:@"max:"
forKeyPath:@"someDate"
name:@"maxDate"
resultType:NSDateAttributeType];
[r setResultType:NSDictionaryResultType];
[r setPropertiesToFetch:@[minValueField,avgValueField,maxValueField,minDateField,avgDateField,maxDateField]];
NSArray* results = [context executeFetchRequest:r error:NULL];
Notes:
* this will retrieve the information in a single dictionary object with keys matching the name
parameter you created the expressions for.
* You cannot use a NSFetchedResultsController
to track changes in the result set in 'real time' and need to refresh the results if you need calculation to be accurate as the database change.
* To group by day and year, you will need to extract the day and year from the date field into separate fields (say day
and year
) and then group by these fields in the fetch request:
[r setPropertiesToGroupBy:@[@"year"]];//to group by year alone
and add the year
to the properties to fetch.
Edit: If you are interested in a specific year (or day in year), you must separate the year and day from the date (as I mentioned before).
Then just set a predicate to filter by the year you need:
[r setPredicate:[NSPredicate predicateWithFormat:@"year = %d",someYear]];
There are two main approaches to this:
i. Do it manually in cellForRowAtIndexPath
using an NSFetchedResultsController
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// get your managed object
NSManagedObject *managedObject = [_fetchedResultsController objectAtIndexPath:indexPath];
// do any computations
float value1 = [[managedObject valueForKey:@"value1"] floatValue];
float value2 = [[managedObject valueForKey:@"value2"] floatValue];
float average = (value1+value2)/2;
// display average in cell
cell.textLabel.text = [NSString stringWithFormat:@"Average: %f", average];
return cell;
}
ii. The other approach would be to use a Core Data framework such as Sensible TableView. These frameworks automatically fetch your data and display them, in addition to automatically managing displaying such computations in custom cells. Saves me tons of time when working with Core Data.