1

I'm new to NSSortDescriptorso any hints are highly appreciated.

I've a UITableView which contains conversations. Each conversation can have several messages. The messages are displayed in another view. It's basically like iMessage. In the Conversation-TableView I want to order the conversations by the most recent messages.

For simplicity I've drawn the following abstract model: enter image description here

Each conversation has a set of messages. Each message has a date.

When I'm fetching the conversation (using an NSFetchedResultsController) I want them ordered by the date of the property messages. I've thought this must be pretty easy by just using computed properties but this doesn't work.

I tried using the key-path:

request.sortDescriptors = [NSSortDescriptor(key: "messages.date", ascending: false)]

... but this crashes with *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'to-many key not allowed here.

Edit: Regarding possible duplicate.

When I try to apply this for all elements of the NSSet I get the following error: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath ALL messages.date not found in entity <NSSQLEntity Conversation id=1>'

Using the following sort descriptor:

request.sortDescriptors = [NSSortDescriptor(key: "ALL messages.date", ascending: false)]
Chris
  • 3,057
  • 5
  • 37
  • 63
  • Possible duplicate of [Core Data: NSPredicate for many-to-many relationship. ("to-many key not allowed here")](http://stackoverflow.com/questions/4217849/core-data-nspredicate-for-many-to-many-relationship-to-many-key-not-allowed) – chedabob Jan 03 '16 at 17:48
  • I found that. But this is for `NSPredicate` and not `NSSortDescription`. This only works if you want to filter things, not for sorting unfortunately. – Chris Jan 03 '16 at 17:53
  • It still applies. `messages` is an `NSSet` so it does not have a `date` property to sort against. – chedabob Jan 03 '16 at 17:55
  • I tried it but there is nothing I can compare with? I updated the original question with the code I tried. Could you explain me how to use that for `NSSortDescriptor`? – Chris Jan 03 '16 at 18:03

3 Answers3

0

Create a category on NSDate that returns the time interval since the start of the day for the current calendar and time zone, and then use that time interval to do your sorting.

@interface NSDate (CBVAdditions)


- (NSTimeInterval)cbvTimeIntervalSinceStartOfDay;


@end

@implementation NSDate (CBVAdditions)


- (NSTimeInterval)cbvTimeIntervalSinceStartOfDay
{

    NSCalendar *calendar = [NSCalendar currentCalendar];

    NSTimeZone *timeZone = [NSTimeZone defaultTimeZone];


    NSDateComponents *dateComponents = [calendar components:(NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit) fromDate:self];


    [dateComponents setCalendar:calendar];

    [dateComponents setTimeZone:timeZone];

    NSInteger hoursComponent = dateComponents.hour * 3600;

    NSInteger minutesComponent = dateComponents.minute * 60;

    double secondsComponent = dateComponents.second;

    NSTimeInterval toReturn = hoursComponent + minutesComponent + secondsComponent;

    return toReturn;

}

Then, you can use NSSortDescriptor with a key like @"dateProperty.cbvTimeIntervalSinceStartOfDay" to do the actual sorting. For example, I show the Event class, and some code that accomplishes the sort.

@interface Event : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSDate *eventDate;

@end

@implementation Event

- (NSString *)description
{
    NSString *desc = [NSString stringWithFormat:@"%@, name = %@, eventDate = %@",[super description], self.name, [self.eventDate descriptionWithLocale:[NSLocale currentLocale]]];
    return desc;
}
@end

Event *e1 = [Event new];

e1.eventDate = [NSDate date];

e1.name = @"e1";

Event *e2 = [Event new];

e2.eventDate = [NSDate dateWithTimeIntervalSinceNow:(-1 * 22 * 3600)];

e2.name = @"e2";

Event *e3 = [Event new];

e3.eventDate = [NSDate dateWithTimeIntervalSinceNow:3600];

e3.name = @"e3";

NSArray *events = @[e1,e2,e3];

NSLog(@"Events = %@", events);

NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"eventDate.cbvTimeIntervalSinceStartOfDay" ascending:YES];

NSArray *sortedEvents = [events sortedArrayUsingDescriptors:@[sortDescriptor]];

NSLog(@"Sorted Events = %@", sortedEvents);

code presented the following output:

2013-01-15 15:50:54.221 DateSortingFun[67319:c07] Events = (
    "<Event: 0x717b050>, name = e1, eventDate = Tuesday, January 15, 2013, 3:50:54 PM Mountain Standard Time",
    "<Event: 0x717b620>, name = e2, eventDate = Monday, January 14, 2013, 5:50:54 PM Mountain Standard Time",
    "<Event: 0x717b650>, name = e3, eventDate = Tuesday, January 15, 2013, 4:50:54 PM Mountain Standard Time" ) 


2013-01-15 15:50:54.222 DateSortingFun[67319:c07] Sorted Events = (
    "<Event: 0x717b050>, name = e1, eventDate = Tuesday, January 15, 2013, 3:50:54 PM Mountain Standard Time",
    "<Event: 0x717b650>, name = e3, eventDate = Tuesday, January 15, 2013, 4:50:54 PM Mountain Standard Time",
    "<Event: 0x717b620>, name = e2, eventDate = Monday, January 14, 2013, 5:50:54 PM Mountain Standard Time" )
Chris
  • 3,057
  • 5
  • 37
  • 63
Akash
  • 461
  • 2
  • 14
  • Thanks, but you're missing the 1:n relationship between conversations and messages (that actually contains the date). That's the problem that I can't access the date property in the array of messages from the conversation. – Chris Jan 04 '16 at 08:58
0

What you want to do is to add a last message date attribute to your conversation. This will allow you to efficiently sort the conversations.

This is what's called denormalization. You need to make sure this last message date field is up-to-date. There are many approaches. One is to override willSave() on the conversation and check if there are changes to the Messages relationship or to any Messages -- when checking this, you need to be sure not to trigger any faulting of objects. If anything has changed, you'd then update the last message date attribute of the conversation.

The How to Build Efficient Data Models section in the Performance chapter of our book goes into more detail. https://www.objc.io/books/core-data/

Daniel Eggert
  • 6,665
  • 2
  • 25
  • 41
-1
NSInteger type;
NSDate* timestamp;
NSString* username;
NSString* session;
NSString* body;

NSFetchRequest * req = [[NSFetchRequest alloc] init];

[req setEntity:[NSEntityDescription entityForName:@"Message" 
inManagedObjectContext:context]];

[req setPredicate:[NSPredicate predicateWithFormat:@"conversation == %@", 
self]]; /* did that from a Conversation object.. */

NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"sent_date" 
ascending:NO];

[req setSortDescriptors:[NSArray arrayWithObject:sort]];

[sort release];

NSError * error = nil;

NSArray * messages = [context executeFetchRequest:req error:&error];

[req release];

if ([messages count] > 0) { /* sanity check */

    return [messages objectAtIndex:0];

}

return nil;
chedabob
  • 5,835
  • 2
  • 24
  • 44
Akash
  • 461
  • 2
  • 14