Core data: How to display results of a fetchreques

2019-09-09 23:08发布

问题:

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?

回答1:

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


回答2:

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.