A NSFetchedResultsController with date as sectionNameKeyPath

16,197

Solution 1

This should do the trick for you:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
  NSString *rawDateStr = [[[self.fetchedResultsController sections] objectAtIndex:section] name];
  // Convert rawDateStr string to NSDate...
  NSDateFormatter *formatter = [[[NSDateFormatter alloc] init] autorelease];
  [formatter setDateFormat:@"yyyy-MM-dd HH:mm:ss ZZ"];
  NSDate *date = [formatter dateFromString:rawDateStr];

  // Convert NSDate to format we want...
  [formatter setDateFormat:@"d MMMM yyyy"];
  NSString *formattedDateStr = [formatter stringFromDate:date];
  return formattedDateStr;  
}

[EDIT]

Jus saw your comment and for what you are trying to achieve, you could create a transient NSDate attribute (non persistent) that is formatted in a similar way to the above code (i.e. without H:mm:ss ZZZZ) and use that attribute as your sectionNameKeyPath value.

So in a nutshell for a foo object, with fooDate and fooDateTransient attributes, you would:

  1. Get your foo.fooDate attribute

  2. Transform it using the code above (or similar) and assign the NSDate result to foo.fooDateTransient

  3. Use fooDateTransient as your sectionNameKeyPath when creating the fetchedResultsController object.

PS: I haven't tested this myself but should be worth a shot!

Good luck, Rog

Solution 2

Check out: http://developer.apple.com/library/ios/#samplecode/DateSectionTitles/Introduction/Intro.html#//apple_ref/doc/uid/DTS40009939

It works with month and year, but it's quite easy to make it work with day, month and year.

Solution 3

The following is a Swift 3 solution to sort by date but have section titles corresponding to individual days.

  1. Add a transient property called daySectionIdentifier to your entity in Core Data.
  2. Regenerate your NSManagedObject subclass. Delete the property for daySectionIdentifier that may get generated in Entity+CoreDataProperties.swift.
  3. To the Entity+CoreDataClass.swift file, add the following getter for daySectionIdentifier:

    // Transient property for grouping a table into sections based
    // on day of entity's date. Allows an NSFetchedResultsController
    // to sort by date, but also display the day as the section title.
    //   - Constructs a string of format "YYYYMMDD", where YYYY is the year,
    //     MM is the month, and DD is the day (all integers).
    
    public var daySectionIdentifier: String? {
        let currentCalendar = Calendar.current
        self.willAccessValue(forKey: "daySectionIdentifier")
        var sectionIdentifier = ""
        if let date = self.date as? Date {
            let day = currentCalendar.component(.day, from: date)
            let month = currentCalendar.component(.month, from: date)
            let year = currentCalendar.component(.year, from: date)
    
            // Construct integer from year, month, day. Convert to string.
            sectionIdentifier = "\(year * 10000 + month * 100 + day)"
        }
        self.didAccessValue(forKey: "daySectionIdentifier")
    
        return sectionIdentfier
    }
    
  4. In your UITableViewController implementation, add the following method:

    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        var sectionTitle: String?
        if let sectionIdentifier = fetchedResultsController.sections?[section].name {
            if let numericSection = Int(sectionIdentifier) {
                // Parse the numericSection into its year/month/day components.
                let year = numericSection / 10000
                let month = (numericSection / 100) % 100
                let day = numericSection % 100
    
                // Reconstruct the date from these components.
                var components = DateComponents()
                components.calendar = Calendar.current
                components.day = day
                components.month = month
                components.year = year
    
                // Set the section title with this date
                if let date = components.date {
                    sectionTitle = DateFormatter.localizedString(from: date, dateStyle: .medium, timeStyle: .none)
                }
            }
        }
    
        return sectionTitle
    }
    
  5. When constructing your NSFetchedResultsController, call the initializer with "daySectionIdentifier" as the sectionNameKeyPath parameter.
  6. Set your NSFetchedResultsController's sort descriptor to your entity's plain old "date" attribute. Importantly, the sort order based on "date" will be consistent with the sort order based on the section identifier that we just constructed.

You should now have your table view grouped into sections by day (e.g., "Feb 6, 2017"), and sorted by fine-grained date.

Share:
16,197

Related videos on Youtube

Paul Warkentin
Author by

Paul Warkentin

Updated on February 07, 2020

Comments

  • Paul Warkentin
    Paul Warkentin about 4 years

    I develop an application which uses Core Data. In one UITableView, I want to display a list of my entities, sorted by the saved date of the objects. When I do this:

    fetchedResultsController = [[NSFetchedResultsController alloc]
                                initWithFetchRequest:fetchRequest
                                managedObjectContext:managedObjectContext
                                  sectionNameKeyPath:@"date"
                                           cacheName:nil];
    

    I get for each object a new section because this code groups the dates according to the seconds, too. But I want a list of the objects, grouped by date, but only according to the day, month and year. Is it possible and how?

    Thank you very much for your help!! ;)

  • Paul Warkentin
    Paul Warkentin over 13 years
    Thank you, but that is the way, how to display the title of the sections. But it doesn't group two objects with a same date. There is still a section for each object.
  • Rog
    Rog over 13 years
    Fair enough, I've edited my answer to suggest an alternative way that you could try.
  • skålfyfan
    skålfyfan about 13 years
    Can we get an update to this? I'm at the same point qPaul was in his first comment (ie. a section header for EACH object.) I sort of understand the technique here with the transient attribute but I'm confused as to where to perform items #1, and #2? I have a method which constructs and returns a proper NSFetchedResultsController. Inside this method is where I need to set item #3 for the sectionNameKeyPath. In which methods am I to do items #1, and #2? After I obtain my FetchedResults inside viewDidLoad am I to loop through all the results and explicitly set the transient Date attribute?
  • skålfyfan
    skålfyfan about 13 years
    Is the best approach to create a custom setter inside the foo Managed Object for foo.fooDate which sets itself along with the transient value? thanks!
  • Jesse Bunch
    Jesse Bunch over 12 years
    I agreed with you at first, but after trying this I realized that this runs through all the batches in my fetch and is REALLY slow on a large data set. It's a bit faster to use @Rog's solution.
  • ichanduu
    ichanduu about 11 years
    I am able to make it work for month and year.but i am not able to make it work for day,month and year,can you please help me out.
  • Ashley Mills
    Ashley Mills over 10 years
    Never use dateByAddingTimeInterval to calculate dates. Have you considered what would happen in a leap year?
  • Jacob K
    Jacob K over 10 years
    yeah this is just a basic example to give you the idea
  • Gargoyle
    Gargoyle almost 10 years
    Giving example code with bad code is always a bad idea. Your whole thing could have been done instead with a simple NSDateFormatter that has the doesRelativeDateFormatting option set to YES.
  • Jacob K
    Jacob K almost 10 years
    yeah looking back at this now, I am surprised I wrote something like that :) Progress is good!
  • alexey.metelkin
    alexey.metelkin about 9 years
    Unfortunately, transient properties couldn't be used in sectionNameKeyPath or in SortDescroptors
  • codelover
    codelover over 8 years
    Hi Rog: I am bit unclear about the transient attribute in core data, I added the column called sortdate and gave it value of the timestamp column but without time information. But whats is the advantage over this, i mean why making it transient if you can help over this.
  • chlkdst
    chlkdst about 8 years
    Hi @alexey.hippie, yup! You can't use the transient property to sort it, because it doesn't make sense to sort the same field with equal values. You have to use the NSDate attribute.
  • alionthego
    alionthego almost 6 years
    looks like a very nice solution but not working in swift 4/ Xcode 9. getter not being called at all.
  • zzmasoud
    zzmasoud about 3 years
    wow! This is such a good idea to section items by computed properties. It works great but if you declare the property as @objc public var @alionthego