OutOfBoundsException
OutOfBoundsException

Reputation: 766

Core Data family members grouped by surname

This is my first attempt on Core Data so I would like your guidance.

My example project is quite simple, I would like to create an iOS app that displays a list of people. What I am looking for is to group all members of the same family together. Some people in the list do not belong to a "family". So, UITableView is going to be a mixture of groups and rows.

Here is my Model so far. enter image description here

and here I am adding data in the context

    Family *newFamily = [NSEntityDescription insertNewObjectForEntityForName:@“Family” inManagedObjectContext:self.managedObjectContext];
    [newFamily setSurname:[dict objectForKey:@“surname”]];

    NSMutableSet *members = [newTeam mutableSetValueForKey:@"members"];

    for (NSDictionary *member in [dict objectForKey:@"members"]) {
        Member *newMember = [NSEntityDescription insertNewObjectForEntityForName:@"Member" inManagedObjectContext:self.managedObjectContext];
        [newMember setFirstName:[member objectForKey:@"firstName"]];
        [newMember setAge:[member objectForKey:@“age”]];

        [members addObject:newMember];
    }

//Save context ...

In UITableViewController I am able to read and display just families'/groups' name. I am confused on how do you display all the objects of a section.

- (NSFetchedResultsController *)fetchedResultsController
{
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Family" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"surname" ascending:NO];

    [fetchRequest setSortDescriptors:@[sortDescriptor]];

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"surname" cacheName:@"Master"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    }

    return _fetchedResultsController;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [[self.fetchedResultsController sections] count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    // I DON'T KNOW WHAT TO DO HERE ...
//    return 0;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
    return [sectionInfo name];
}

Upvotes: 0

Views: 64

Answers (1)

pbasdf
pbasdf

Reputation: 21536

First, you need to amend your model to include the inverse relationship, from Member to Family, which presumably is "to-one". Name that relationship "family". (This inverse relationship is needed in this case, see below, but you should almost always include inverses for all relationships, regardless of whether you think you need them.)

Next, amend your FRC configuration as follows: the underlying entity should be Member, since each row of your tableView will represent a Member object. The sectionNameKeyPath should be "family.surname" so the FRC will automatically put the Member objects into sections based on the surname attribute of the related Family object. It is important that the sort order for the FRC matches up with the sections (ie all Member objects in the same section must be sorted together), so amend the sort descriptors to use family.surname (you could then sort by firstName if desired):

- (NSFetchedResultsController *)fetchedResultsController
{
    if (_fetchedResultsController != nil) {
        return _fetchedResultsController;
    }

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Member" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    // Edit the sort key as appropriate.
    NSSortDescriptor *surnameSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"family.surname" ascending:NO];
    NSSortDescriptor *firstNameSortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"firstName" ascending:NO];

    [fetchRequest setSortDescriptors:@[surnameSortDescriptor, firstNameSortDescriptor]];

    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"family.surname" cacheName:@"Master"];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    }

    return _fetchedResultsController;
}

Finally, there is boilerplate code for linking an FRC to a tableView (see the Apple Docs here). The piece you are missing is:

- (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {
    if ([[self.fetchedResultsController sections] count] > 0) {
        id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];
        return [sectionInfo numberOfObjects];
    } else
        return 0;
}

Your configureCell method will likewise need to determine the correct Member object to display using

Member *currentMember = [self.fetchedResultsController objectAtIndexPath:indexPath];

Note that the FRC will log a warning message if there are Member objects that are not in a Family, since the sectionName will be nil. But it will group these all together - you might want to amend the titleForHeaderInSection to use a different name for that section.

Also, note that the FRC's sections are determined by inspecting the family.surname for each Member object. If there are Family objects that have no members, those Family objects will not appear in the table view (i.e. no empty sections).

Upvotes: 1

Related Questions