views:

210

answers:

2

In my NSFetchedResultsController, I set a sortDescriptor that sorts based on the date property of my managed objects. The problem that I have encountered (along with several others according to Google) is that nil values are sorted at the earliest end rather than the latest end of the date spectrum. I want my list to be sorted earliest, earlier, now, later, latest, nil. As I understand it, this sorting is done at the database level in SQLite and so I cannot construct my own compare: method to provide the sorting I want.

I don't want to manually sort in memory, because I would have to give up all of the benefits of NSFetchedResultsController. I can't do compound sorting because the sectionNameKeyPaths are tightly coupled to the date ranges. I could write a routine that redirects indexPath requests so that section 0 in the results controller gets mapped to the last section of the tableView, but I fear that would add a lot of overhead, severely increase the complexity of my code, and be very, very error-prone.

The latest idea that I am considering is to map all nil dates to the furthest future date that NSDate supports. My left brain hates this idea, as it feels more like a hack. It will also take a bit of work to implement, since checking for nil factors heavily into how I process dates in my app. I don't want to go this route without first checking for better options. Can anyone think of a better way to get around this problem?

Update

A possible better way to get around this problem would be to switch to a binary persistent store. From what I have read in the documentation, sorting is done in Objective-C with the binary persistent store, so I could supply my own compare method for the date sorting. I have two concerns with this approach. First, what kind of performance hit am I going to see by moving to a binary store? Second, how difficult is it going to be to roll out this change in an app update?

A: 

I don't know if it is possible to change where the nil dates are sorted to in the NSFetchedResultsController. However, I do have a suggestion along the lines of your "hack" of changing nil dates to a future time. Specifically, I would create a new transient property for your entity that returns a modified date. Basically, the custom getter for that property would retrieve the date and then check if it is nil. If so, it would return the latest future date that NSDate supports. This would allow the NSFetchedResultsController to sort the results in the way you want.

Using transient properties is how lists with sections using the first letter of a person's last name (a la the Contacts App) are created. A method for doing that is described here.

For example:

// THIS ATTRIBUTE GETTER GOES IN YOUR OBJECT MODEL FOR THE 
// TRANSIENT PROPERTY modifedDate
- (NSDate *) modifiedDate {
  [self willAccessValueForKey:@"date"];
  NSDate * date = [self date];
  if(date == nil)
    date = // Set as furthest future date.
  [self didAccessValueForKey:@"date"];
  return date;

}

Tim Isganitis
As I understand it, a transient property can be used for the sectionNameKeyPath (and in fact, I do use a transient property for that) but cannot be used for the sort descriptor as the sorting is done by SQLite at the database level and that transient property does not exist in the database. Please feel free to correct me if I have misunderstood this.I will on the other hand use a transient property to "interpret" the hack date and return nil when that particular date is encountered. This will prevent me from having to process the fake date in other parts of my code.
glorifiedHacker
My mistake. You are correct that a transient property cannot be used as a sort descriptor.
Tim Isganitis
If sorting must be done in the database, then you could add a hasDate boolean property to your object model and sort by that first and then by the date. However, in addition to adding redundancy to the database, I also assume this is the "compound sorting" approach you say you want to avoid in your original question.
Tim Isganitis
Yes, that's what I meant by compound sorting. The sectionNameKeyPath has to match the sort ordering of the first sort descriptor in the fetch request, so sorting by hasDate first would give me at best two sections in my tableView.
glorifiedHacker
A: 

I decided to go with the "hack" fix, because I didn't want to incur the overhead of having the entire object graph loaded in memory that comes with using the binary store. Instead of storing nil when the user doesn't set a date, I store the result of a "noDate" class method.

glorifiedHacker