A NSFetchedResultsController with date as sectionNameKeyPath
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:
Get your
foo.fooDate
attributeTransform it using the code above (or similar) and assign the
NSDate
result tofoo.fooDateTransient
Use
fooDateTransient
as yoursectionNameKeyPath
when creating thefetchedResultsController
object.
PS: I haven't tested this myself but should be worth a shot!
Good luck, Rog
Solution 2
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.
- Add a transient property called
daySectionIdentifier
to your entity in Core Data. - Regenerate your
NSManagedObject
subclass. Delete the property fordaySectionIdentifier
that may get generated inEntity+CoreDataProperties.swift
. -
To the
Entity+CoreDataClass.swift
file, add the following getter fordaySectionIdentifier
:// 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 }
-
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 }
- When constructing your
NSFetchedResultsController
, call the initializer with"daySectionIdentifier"
as thesectionNameKeyPath
parameter. - 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.
Related videos on Youtube
Paul Warkentin
Updated on February 07, 2020Comments
-
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 over 13 yearsThank 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 over 13 yearsFair enough, I've edited my answer to suggest an alternative way that you could try.
-
skålfyfan about 13 yearsCan 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 about 13 yearsIs 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 over 12 yearsI 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 about 11 yearsI 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 over 10 yearsNever use
dateByAddingTimeInterval
to calculate dates. Have you considered what would happen in a leap year? -
Jacob K over 10 yearsyeah this is just a basic example to give you the idea
-
Gargoyle almost 10 yearsGiving 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 almost 10 yearsyeah looking back at this now, I am surprised I wrote something like that :) Progress is good!
-
alexey.metelkin about 9 yearsUnfortunately, transient properties couldn't be used in sectionNameKeyPath or in SortDescroptors
-
codelover over 8 yearsHi 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 about 8 yearsHi @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 almost 6 yearslooks like a very nice solution but not working in swift 4/ Xcode 9. getter not being called at all.
-
zzmasoud about 3 yearswow! 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