Question

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?

Was it helpful?

Solution 2

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

OTHER TIPS

Worked out the answer in Swift 4.2.

There are two steps:

Step 1: create extension on your NSManagedObject (example uses Entry as a managed object), and expose it with @objc:

extension Entry {
    @objc var isoDate: String { get {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd"
        // insert settings for TimeZone and Calendar here
        return formatter.string(from: Entry.date!)
    }}
}

Step 2: Use the property isoDate as the value for sectionNameKeyPath:

let fetchedResultController = NSFetchedResultsController(fetchRequest: expenseFetchRequest(),
                                                             managedObjectContext: coreDataStack.mainContext,
                                                             sectionNameKeyPath: #keyPath(Expense.dateISO),
                                                             cacheName: nil)

By now, your FRC should be able to put the objects into sections!

Reading both post, adding an attribute seems to be the answer

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top