iosobjective-cuitableviewnsfetchedresultscontroller

Grouping dates using NSFetchedResultsController in numberOfSectionsInTableView


I have an UITableViewController that shows a list using NSFetchedResultsController. This is a list of myObject and has a attribute date.

I have created a method - (NSFetchedResultsController *)fetchedResultsController to get all Entries sorted by date.

The result is this: enter image description here

After this I have added the - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section so that time + date would be shown in dateStyle:NSDateFormatterMediumStyle.

As shown below, you can see that the dates are printed properly, but the 2 lines with MAR 12, 2014 and the other 2 with MAR 14, 2014 are not grouped together.

enter image description here

My guess is that I need to change the numberOfSectionsInTableView method, as it is still looking at the dates + time, and not only to the dates, but I have no idea how.

My code:

   - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    {
        NSInteger count = [[self.fetchedResultsController sections] count];
        return count;
    }

One possibility is to add an attribute to my Object like 'sectionIdentifier' to store only dates in it and use that. I would like to know if anyone has another idea how to approach this. Is it possible to get these dates grouped together my adding code to - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView?


Solution

  • I like controlling it with the FRC, and leave the delegate calls to change it if they want different/custom functionality. I also believe that the FRC should generally be constructed as a class method on the entity being observed.

    You can make the formatted string be a full-on attribute of the object, but that seems wasteful.

    You could also make it be a true transient attribute. That may be more proper, but I was using an existing model, and didn't want to change the model. Since that project, I have just kept with this methodology for any other similar cases.

    Anyway, this example is almost taken straight from an existing app. I had to change some things, so I hope I didn't leave out anything.

    Create the fetch request used by your FRC, so that it sorts based on the date property of the object. This allows normal core data fetching on a standard property.

    + (NSFetchedResultsController *)
        fetchedResultsController:(NSManagedObjectContext *)context
    {
        NSFetchRequest* request = [NSFetchRequest
            fetchRequestWithEntityName:[self entityName]];
        request.sortDescriptors = @[[NSSortDescriptor
            sortDescriptorWithKey:@"date"
                        ascending:YES]];
        request.fetchBatchSize = 30;
        request.returnsObjectsAsFaults = NO;
    
        NSString *cacheName = [[self entityName]
            stringByAppendingString:@"-all-bydate"];
        return [[NSFetchedResultsController alloc]
            initWithFetchRequest:request
            managedObjectContext:context
              sectionNameKeyPath:@"dateAsSectionName"
                       cacheName:cacheName];
    }
    

    Add an instance variable to your subclass... you will manage it yourself...

    @implementation MyManagedObjectSubclass {
        NSString *dateAsSectionName_;
    }
    

    Add an accessor method. We only create the formatter once. Change it to your own liking.

    - (NSString*)dateAsSectionName
    {
        static NSDateFormatter *dateFormatter;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            dateFormatter = [[NSDateFormatter alloc] init];
            dateFormatter.dateFormat = @"EEE, MMM d, h:mm a";
            dateFormatter.dateFormat = @"EEEE MMMM d";
        });
    
        if (dateAsSectionName_ == nil) {
            NSDate *date = [self primitiveDate];
            dateAsSectionName_ = [dateFormatter stringFromDate:date];
        }
        return dateAsSectionName_;
    }
    

    Clear out our section name when the date changes, so we will re-compute its value the next time the accessor is called.

    - (void)setDate:(NSDate*)date
    {
        if ([[self primitiveDate] isEqualToDate:date]) return;
    
        [self willChangeValueForKey:@"date"];
        [self setPrimitiveDate:date];
        [self didChangeValueForKey:@"date"];
    
        dateAsSectionName_ = nil;
    }
    

    Tell KVO that dateAsSectionName depends on date.

    + (NSSet *)keyPathsForValuesAffectingDateAsSectionName
    {
        return [NSSet setWithObject:@"date"];
    }