3

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?

Eloy
  • 119
  • 1
  • 9
  • you may want to add a section property based on `NSString` that holds the date in the format you want while keeping the real date stored as `NSDate` – Volker Mar 26 '14 at 18:55
  • I am not sure what you mean, I have tried to add to a NSString, but that doesn't work. If I use NSLOG I get this [_bds fetchedResultsController ]sections] ( "<_NSDefaultSectionInfo: 0x1097ce5d0>", "<_NSDefaultSectionInfo: 0x1097d0230>", "<_NSDefaultSectionInfo: 0x1097d0b20>", "<_NSDefaultSectionInfo: 0x1097ceca0>", "<_NSDefaultSectionInfo: 0x1097cff00>", "<_NSDefaultSectionInfo: 0x1097d0010>", "<_NSDefaultSectionInfo: 0x1097d0600>", "<_NSDefaultSectionInfo: 0x1097d0790>", "<_NSDefaultSectionInfo: 0x1097d1770>", "<_NSDefaultSectionInfo: 0x1097d1370>" – Eloy Mar 26 '14 at 18:59
  • Your data model needs to be expanded to have a string... – Volker Mar 26 '14 at 19:00
  • What I try to say is that NSString *foo = [self.fetchedResultsController sections] doesn't work, or did you mean something else? – Eloy Mar 26 '14 at 19:01
  • I mean you need a string in your data model that contains the date exactly as you need it for the sections... – Volker Mar 26 '14 at 19:01
  • ok yes, that is what I meant with adding the sectionIdentifier. – Eloy Mar 26 '14 at 19:02
  • But it seemed redundant to have a attribute with date + one with the date/string in it, that is why I wanted to know if there is another way for it. – Eloy Mar 26 '14 at 19:03
  • And ofcourse thank you! – Eloy Mar 26 '14 at 19:07
  • 1
    the problem is that dates are stored as offset in seconds to a reference which makes working with them sometimes ahrd. your welcome. – Volker Mar 26 '14 at 19:09
  • Yes I've noticed and was hoping that there was an easier solution for it. :-) – Eloy Mar 26 '14 at 19:14
  • possible duplicate of [A NSFetchedResultsController with date as sectionNameKeyPath](http://stackoverflow.com/questions/4418703/a-nsfetchedresultscontroller-with-date-as-sectionnamekeypath) – Wain Mar 26 '14 at 19:20

3 Answers3

7

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!

Teng L
  • 297
  • 3
  • 17
1

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"];
}
Jody Hagins
  • 27,943
  • 6
  • 58
  • 87
-2

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

Eloy
  • 119
  • 1
  • 9