23

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!! ;)

PeeHaa
  • 71,436
  • 58
  • 190
  • 262
Paul Warkentin
  • 3,899
  • 3
  • 26
  • 35

5 Answers5

41

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

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Rog
  • 18,602
  • 6
  • 76
  • 97
  • 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. – Paul Warkentin Dec 12 '10 at 09:45
  • Fair enough, I've edited my answer to suggest an alternative way that you could try. – Rog Dec 12 '10 at 20:56
  • 2
    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 Feb 26 '11 at 18:42
  • 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! – skålfyfan Feb 26 '11 at 19:10
  • 2
    Unfortunately, transient properties couldn't be used in sectionNameKeyPath or in SortDescroptors – alexey.metelkin Feb 24 '15 at 17:58
  • 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. – codelover Nov 19 '15 at 11:04
  • 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. – chlkdst Mar 21 '16 at 09:11
  • You can not use transient properties as sort descriptors BUT you can absolutely use them as `sectionNameKeyPath`. As Michael referred Apple has an old ObjC code sample doing exactly that: https://stackoverflow.com/a/5610917/952846 – Greg de J Mar 09 '23 at 09:26
25

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.

Michael Gaylord
  • 7,282
  • 8
  • 50
  • 47
  • 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. – ichanduu Feb 28 '13 at 07:50
4

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.

Chris Chute
  • 3,229
  • 27
  • 18
  • looks like a very nice solution but not working in swift 4/ Xcode 9. getter not being called at all. – alionthego May 15 '18 at 05:16
  • 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 – zzmasoud Jan 26 '21 at 20:42
-1

I used @BoltClock's a Unicorn and @Rog's anwser when having the same issue. Simply added a transient NSString *sectionTitle to my managed object, used @"sectionTitle" as sectionNameKeyPath and created a custom getter like so:

-(NSString *)sectionTitle
{
    NSDate *_now = [NSDate date];
    NSDate *_today = [_now dateByAddingTimeInterval: -86400.0];
    NSDate *_yesterday = [_now dateByAddingTimeInterval: -172800.0];
    NSDate *_thisWeek  = [_now dateByAddingTimeInterval: -604800.0];
    NSDate *_lastWeek  = [_now dateByAddingTimeInterval: -1209600.0];
    NSDate *_thisMonth = [_now dateByAddingTimeInterval: -2629743.0]; 
   // if better precision required use something more sophisticated for month...

   double today = [_today timeIntervalSince1970];
   double yesterday = [_yesterday timeIntervalSince1970]; 
   double thisWeek = [_thisWeek timeIntervalSince1970];
   double lastWeek = [_lastWeek timeIntervalSince1970]; 
   double thisMonth = [_thisMonth timeIntervalSince1970];

   [self willAccessValueForKey:@"timestamp"];
       double ts = [self.timestamp timeIntervalSince1970];
   [self didAccessValueForKey:@"timestamp"];

   NSString *title = @"";
   if(ts >= today) title = NSLocalizedString(@"TODAY",nil);
   else if (ts >= yesterday) title = NSLocalizedString(@"YESTERDAY",nil);
   else if (ts >= thisWeek) title = NSLocalizedString(@"THIS WEEK",nil);
   else if (ts >= lastWeek) title = NSLocalizedString(@"LAST WEEK",nil);
   else if (ts >= thisMonth) title = NSLocalizedString(@"THIS MONTH",nil);

   return title;
}
Jacob K
  • 2,669
  • 1
  • 15
  • 20
  • 1
    Never use `dateByAddingTimeInterval` to calculate dates. Have you considered what would happen in a leap year? – Ashley Mills Dec 03 '13 at 11:13
  • yeah this is just a basic example to give you the idea – Jacob K Dec 03 '13 at 11:24
  • 1
    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. – Gargoyle May 27 '14 at 18:37
  • yeah looking back at this now, I am surprised I wrote something like that :) Progress is good! – Jacob K Jun 07 '14 at 14:34
-1

I think this would be better.

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

    // Replace DataClassObject with whatever object your using
    DataClassObject *tempObject = [[sectionInfo objects] objectAtIndex:0];

    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"d MMMM yyyy"];
    NSString *formattedDateStr = [formatter stringFromDate:tempObject.date];
    [dateFormatter release]

    return formattedDateStr;
}
zaid
  • 223
  • 5
  • 8
  • 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. – Jesse Bunch Jan 15 '12 at 22:41