ghostrider
ghostrider

Reputation: 5259

multiple nsfetchedresultscontrollers or a single one

I am using core data and I am having a UITableView with dynamic number of sections.

I have an entity called dates - and it has let's say a title and a relationship which points to another entity - the id of that entity is the section data will be presented.

Which of the best is the best approach and why?

A. Have an array of NSFetchedResultControllers - in each section I filter the data using a predicate. Then I just present the data to each section.

B. I have a single NSFetchedResultController and I fetch all the data - then inside my CellForRow I check whether I should present them or not.

C. I remove the relationship and I add an extra attribute called sectionId in my entity and use either A or B.

What is best approach in terms of UI performance?

EDIT :

Example - I have

Entity 1 : Data

Data Id : 0, Title : First, (Relationship) section : 0
Data Id : 1, Title : Second, (Relationship) section : 0
Data Id : 2, Title : FisrtB, (Relationship) section : 1

Entiry 2 : SectionsName

SectionId : 0 , Name : TitleA , etc (to-many- relationhsip to Data)
SectionId : 1 , Name : TitleB , etc (to-many- relationhsip to Data)

So, question is actually :

Upvotes: 0

Views: 65

Answers (3)

pbasdf
pbasdf

Reputation: 21536

Edited to reflect changes to original question

You almost certainly should use Option B from your original question (Option A in your subsequent edit): a single NSFetchedResultsController, based on the Data entity but with table view sections determined by the section relationship.

Your fetched results controller can do all the hard work of dividing the objects up into the correct sections: ensure that the fetch underlying the FRC is sorted first by section.sectionId (or section.name if you prefer), and specify the FRC's sectionNameKeyPath as section.sectionId (or section.name). The boilerplate FRC/tableView code will then automatically put objects into the correct sections.

The aforementioned boilerplate code:

#pragma mark - TableView Datasource delegate

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

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

-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    Data *data = [self.fetchedResultsController objectAtIndexPath:indexPath];
    ....
    return cell;
}

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

#pragma mark - Fetched results controller

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

    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Data" inManagedObjectContext:self.context];
    [fetchRequest setEntity:entity];

    // Edit the sort key as appropriate.
    NSSortDescriptor *sectionSort = [[NSSortDescriptor alloc] initWithKey:@"section.sectionId" ascending:YES];
    // Add any other sort criteria
    ....
    NSArray *sortDescriptors = @[sectionSort, ...];
    [fetchRequest setSortDescriptors:sortDescriptors];

    // 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.context sectionNameKeyPath:@"section.sectionId" cacheName:nil];
    self.fetchedResultsController = aFetchedResultsController;
    aFetchedResultsController.delegate = self;
    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    
    return _fetchedResultsController;
}

#pragma mark - FRC delegate methods

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView beginUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
       atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
{
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
        break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;

        default:
            break;
     }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
   atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
  newIndexPath:(NSIndexPath *)newIndexPath
{
    UITableView *tableView = self.tableView;

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeMove:
            [tableView moveRowAtIndexPath:indexPath toIndexPath:newIndexPath];
            break;
    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView endUpdates];
}

Upvotes: 1

Mundi
Mundi

Reputation: 80265

It is almost certainly better to use only one fetched results controller. (I agree with @Lee on this point, but I do not understand why he is recommending option B, which includes more FRCs.)

To summarize your data model:

Section <------->> Date

You can simply fetch the section and just adjust the datasource methods:

// number of sections
return self.fetchedResultsController.fetchedObjects.count;

// number of rows in section
Section *section = [self.fetchedResultsController objectAtIndexPath:
  [NSIndexPath indexPathForRow:section inSection:0]];
return section.dates.count;

// cellForRowAtIndexPath
/* create a convenience function in the Section class to return a
   sorted array from the NSSet "dates". */
Section *section = [self.fetchedResultsController objectAtIndexPath:
  [NSIndexPath indexPathForRow:indexPath.section inSection:0]];
Date *date = section.sortedDates[indexPath.row];
/* configure the cell based on the date object */

So, A is the better option.

Upvotes: 1

Lee J Pollard
Lee J Pollard

Reputation: 117

It seems that though you only need one attribute from the entity 2 (sectionID), you still need the relationship in order to get that entity and eventually get that attribute (sectionID). Therefore I suggest option B. The less FRCs the better. You will only need one here. In your CellForRow you will get the current entity in your fetched results array by using the indexPath. Once you have the current entity you can then now access entity 2, which is a property of the first entity. Once you have entity 2, you now have the attribute you've wanted, which is the section id of entity 2. You can now display that attribute (section id) on your table view.

Upvotes: 1

Related Questions