2
votes

I have two entities: Patient and Job. Patient has a to-many relationship to Job called 'jobs' and Job has a to-one relationship to Patient called 'patient'. Job has attributes called 'dueDate' (Date) and 'completed' (BOOL) and Patient has attributes 'firstName' and 'lastName' (both Strings).

I am trying to create a fetch request / predicate for my NSFetchedResultsController that we grab all Jobs that have not been completed (i.e. completed == NO) and section them by Patient name. Here is my code:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Job" inManagedObjectContext:moc];
[fetchRequest setEntity:entity];

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(completed == NO)"];
[fetchRequest setPredicate:predicate];

NSSortDescriptor *patientDescriptor = [[NSSortDescriptor alloc] initWithKey:@"patient" ascending:YES];
NSSortDescriptor *dueDateDescriptor = [[NSSortDescriptor alloc] initWithKey:@"dueDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:patientDescriptor, dueDateDescriptor, nil];

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:moc sectionNameKeyPath:@"patient" cacheName:@"Jobs"];

Here is my titleForHeaderInSection method:

NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:section];
NSString *firstName = [[(Job *)[fetchedResultsController objectAtIndexPath:indexPath] patient] firstName];
NSString *lastName = [[(Job *)[fetchedResultsController objectAtIndexPath:indexPath] patient] lastName];

return [NSString stringWithFormat:@"%@ %@", firstName, lastName];

This doesn't seem to work. Am I going about this the wrong way?

3
In general, it would help if you described exactly what "doesn't seem to work" means. Your tableView shows up blank? Your tableView loads, but the results aren't sorted correctly? Your app crashes? In this case, I can guess as to what's going on, but my point still stands.glorifiedHacker

3 Answers

3
votes

First, you don't seem to attach the sortDescriptors to fetchRequests. This may or may not be connected with the problem.

Second, you can accomplish this in an easier way. Make it like this:

sectionNameKeyPath:@"patient.name"

"name" should be a property or method of the Patient object. An easy way to implement this would be to have a category method on patient:

- (NSString *)name {
    return [NSString stringWithFormat:@"%@ %@", self.firstName, self.lastName];
}

Actually, you can't accomplish your purpose with something as simple as this, read mzarra's answer for correct answer. NSFetchedResultsController has this critical comment:

If the controller generates sections, the first sort descriptor in the array is used to group the objects into sections; its key must either be the same as sectionNameKeyPath or the relative ordering using its key must match that using sectionNameKeyPath.

But, you cannot sort on the results of a method call, you'd need a property of the object. So, your best bet is probably just have a "name" property on "patient", and use that property for both sorting and sectionNameKeyPath.

6
votes

How is it not working? It helps to describe what results you are seeing.

You are not adding your sort descriptors to your NSFetchRequest, at least in the sample you provided.

Your sort descriptors are ineffective. It appears that Patient is a relationship so sorting against the relationship will not work. You would want to do a sort like the following:

NSSortDescriptor *lastNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"patient.lastName" ascending:YES];
NSSortDescriptor *firstNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"patient.firstName" ascending:YES];
NSSortDescriptor *dueDateDescriptor = [[NSSortDescriptor alloc] initWithKey:@"dueDate" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: lastNameDescriptor, firstNameDescriptor, dueDateDescriptor, nil];

[fetchRequest setSortDescriptors:sortDescriptors];

[lastNameDescriptor release], lastNameDescriptor = nil;
[firstNameDescriptor release], firstNameDescriptor = nil;
[dueDateDescriptor release], dueDateDescriptor = nil;

You do not need to cast [fetchedResultsController objectAtIndexPath:indexPath] as it returns id.

What are you getting back from the call to [fetchedResultsController objectAtIndexPath:indexPath]? Put a breakpoint here and check the value and make sure you are getting back a NSManagedObject instead of nil. Putting a breakpoint in that method will also confirm that you are getting called.

Your secondKeypathName will not work as mentioned above. You probably want to set it to @"patient.lastName" so that it will match the initial sort I described above.

Your -tableView: titleForHeaderInSection: should be accessing the cache provided by the NSFetchedResultsController instead of assuming that there is going to be a row in the section:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 
{
    id sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo name];
}

Finally, if you want the section to truly display the "lastname, firstname" format then you will need to create a non-transient derived value property on your Patient entity that is the fullName so that you can create your cache based on it. This derived value would then need to be updated any time that the first name or last name were changed.

2
votes

In addition to not assigning your sortDescriptors to your fetchRequest, I believe you have a problem with your predicate. Since you are dealing with Core Data, the boolean value for your "completed" attribute is stored in an instance of NSNumber. Something like the predicate below would be better:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"completed = %@", [NSNumber numberWithBool:NO]];