Eyeball
Eyeball

Reputation: 3317

NSFetchedResultsController creates too many sections

I copied the code from apples documentation of NSFetchedResultsController. It says that the sectionNameKeyPath should be provided if i want to section my result for the UITableView in which the result is presented.

I aim to section the results based on the character that the name of the model object begins with. i.e. sorted by name.

This code does exactly that (sorts by name), but instead of partitioning up the groups of rows, every row appears in their own section. (The number of sections are equal to the number of rows basically).

I believe that I have made a silly mistake somewhere, but I spent two and a half hour now and I cannot see anything. Im just gonna paste the delegate/datasource methods of the UITableView I'm presenting the data in.

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

    NSFetchRequest *fetchRequest = [Place MR_requestAllSortedBy:@"name" ascending:YES];
    [fetchRequest setFetchLimit:0];
    [fetchRequest setFetchBatchSize:20];

    NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_contextForCurrentThread] sectionNameKeyPath:@"name" cacheName:@"places"];
    _fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;
    return _fetchedResultsController;
}


#pragma mark UITableViewDelegate
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    ListTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:listTableViewCellIdentifier];

    Place *place = [_fetchedResultsController objectAtIndexPath:indexPath];

    FAKIcon *categoryIcon = [FFPlaceHandler getIconForPlaceWithCategoryID:place.main_category andSize:CELL_ICON_SIZE];
    [categoryIcon addAttribute:NSForegroundColorAttributeName value:FF_WHITE_COLOR];

    FAKIcon *circleIcon = [FAKFontAwesome circleIconWithSize:CELL_ICON_CIRCLE_SIZE];
    [circleIcon addAttribute:NSForegroundColorAttributeName value:[FFPlaceHandler getColorForPlaceWithCategoryID:place.main_category]];

    cell.image.image = [UIImage imageWithStackedIcons:@[circleIcon, categoryIcon] imageSize:CGSizeMake(CELL_ICON_CIRCLE_SIZE, CELL_ICON_CIRCLE_SIZE)];

    cell.title.text = place.name;
    cell.addressLabel.text = place.place_contact_info.address;

    [cell setDistanceWithDistance:[place.distance_from_user floatValue]];

    cell.indexPath = indexPath;
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

    return cell;
}

#pragma mark UITableViewDatasource

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if ([[_fetchedResultsController sections] count] > 1) {
        id <NSFetchedResultsSectionInfo> sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
        return [sectionInfo name];
    } else
        return nil;
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [_fetchedResultsController sectionIndexTitles];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [_fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}


- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
    return 40.0;
}

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

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

Why are the UITableViewCell's assigned one section each and not sectioned in alphabetic order?

EDIT:

Solved it by adding a field to the place table in core data. this field holds the first character of each record. After this I changed my sectionKeyPath as seen in this method:

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

    NSFetchRequest *fetchRequest = [Place MR_requestAllSortedBy:@"name" ascending:YES];
    [fetchRequest setFetchLimit:0];
    [fetchRequest setFetchBatchSize:20];

    NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:[NSManagedObjectContext MR_contextForCurrentThread] sectionNameKeyPath:@"name_first_character" cacheName:@"places"];
    _fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;
    return _fetchedResultsController;
}

In addition i replaces > 1 with > 0 as suggested by moby! I got some pretty nice sections now :)

Upvotes: 0

Views: 209

Answers (2)

andrewbuilder
andrewbuilder

Reputation: 3799

Generally if you are using an external SDK / framework such as magical record, I would recommend that you mention this in your question.

Also worth mentioning, to be clear...

Table view data source methods are;

  • cellForRowAtIndexPath,
  • numberOfRowsInSection,
  • numberOfSectionsInTableView,
  • titleForHeaderInSection,
  • sectionIndexTitlesForTableView, and
  • sectionForSectionIndexTitle.

Table view delegate methods are;

  • heightForHeaderInSection.

If you were not using magical record, you would assign a primary (first) sort descriptor to your FRC that is identical to your sectionNameKeyPath...

For example...

NSSortDescriptor *sortDescriptorPrimary = [NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES];
NSSortDescriptor *sortDescriptorSecondary = [NSSortDescriptor sortDescriptorWithKey:<<other attribute>> ascending:YES];

then set your fetch request sort descriptors...

[fetchRequest setSortDescriptor:[NSArray arrayWithObjects: sortDescriptorPrimary, sortDescriptorSecondary, nil]];

In this way your FRC would feed data to the table view in the same order as your sections first, then order per your secondary (or other) sort descriptors.

Maybe I have worked out your problem...

You are using the magical record sort descriptor and sectionNameKeyPath of @"name".

You need to be using the first letter of each @"name".

So, without thinking about it too much, you have one choice here...

  1. Record the first letter of each @"name" attribute and use that as your primary sort descriptor and section name key path, OR

You could assess the first letter of each @"name" attribute dynamically, and use that as your primary sort descriptor and section name key path.

e.g...

NSString *nameFirstLetter = [name substringWithRange:NSMakeRange(0, 1)];

however I believe (but I am not certain) that this will not work as your FRC will probably require the necessary data before the table view calls the data source and delegate methods.

Hope this assists.

Upvotes: 1

Snowman
Snowman

Reputation: 32091

Sectioning by keypath name will create a new section for every model that has a different value for name. So if you have 20 models and they all have different name values, you will have 20 sections. If you have 20 models, and 10 of them have name A and 10 of them have name B, you will have 2 sections.

Also, in your

 - (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section {

you're checking

if ([[_fetchedResultsController sections] count] > 1) 

where instead I think you should be checking if count > 0 rather than 1.

Upvotes: 1

Related Questions