29

What I want to do is pretty simple. In my UITableViewController, I want to load data from multiple NSFetchedResultControllers (I have multiple entities in my data model) and put data from each one into a different section in the table view. So for example, all the fetched items from the first NSFetchedResultController would go in section 0 in the UITableView, the fetched items from the other one goes into section 1, etc.

The Core Data template project doesn't demonstrate how to do this. Everything (mainly the index paths) is coded without taking sections into account (there are no sections in the default template) and everything is taken from a single NSFetchedResultController. Are there any example projects or documentation that demonstrates doing this?

Thanks

3 Answers 3

26

Assume for a moment the following in your header (code below will be slightly sloppy, my apologies):

NSFetchedResultsController *fetchedResultsController1; // first section data
NSFetchedResultsController *fetchedResultsController2; // second section data

Let the table know you want to have 2 sections:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return 2; // you wanted 2 sections
}

Give it the section titles:

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [NSArray arrayWithObjects:@"First section title", @"Second section title", nil];
}

Let the table know how many rows there are per sections:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if (section == 0) {
        return [[fetchedResultsController1 fetchedObjects] count];
    } else if (section == 1) {
        return [[fetchedResultsController2 fetchedObjects] count];
    }

    return 0;
}

Build the cell:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    ... // table cell dequeue or creation, boilerplate stuff

    // customize the cell
    if (indexPath.section == 0) {
        // get the managed object from fetchedResultsController1
        // customize the cell based on the data
    } else if (indexPath.section == 1) {
        // get the managed object from fetchedResultsController2
        // customize the cell based on the data
    }

    return cell;
}
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the detailed answer. That part seems fairly straightforward, however there are a 2 other methods that I'm not so sure about (what they do and if they need any changes) : pastebin.ca/1805761
It kind of depends on what your app does; it's difficult for me to answer without knowing a whole lot more about the app, design, etc. However, you probably have a safe choice in just having those methods send the table view a reloadData message.
Managed to get this working with a little tinkering :-) Thanks
Hi @Giao, thanks for your answer. One thing I'm missing: I need to specify something in sectionNameKeyPath: of NSFetchedResultsController(fetchRequest: , managedObjectContext: , sectionNameKeyPath: , cacheName: ). sectionNameKeyPath: should be nil? Should it be the same string of the array defined in sectionIndexTitlesForTableView?
8

Multiple fetch controllers (and possibly multiple entities) is the wrong approach. The correct solution is to use the sectionNameKeyPath param to the NSFetchedResultController to group the results into multiple sections. If you think about your entities differently perhaps they are actually the same entity and instead you can use a property itemType which you can then section on (and you must also sort on it too). E.g. say I had entities Hops and Grains then I could change those to Ingredient and have a int_16 property ingredientType which I then have an enum in code to store the values hopType = 0, grainType = 1. After all the ingredient is just a name and a weight, which both of these share.

If however your entities really have a distinct set of properties, then the correct solution is to create a parent abstract entity that has a property that you can use to section, e.g. sortOrder, sectionID or type. When you then create a fetch controller and fetch the abstract parent entity, you actually get results containing all of the sub-entities. E.g in the Notes app they have an abstract parent entity NoteContainer that has child entities Account and Folder. This enables a single fetch controller to display the account in the first cell in the section, and then have all the folders in the following cells. E.g. All iCloud Notes (is actually the account), then Notes (is the default folder), followed by all the custom folders, then the trash folder. They use a sortOrder property and the default folder is 1, the custom folders are all 2, and the trash is 3. Then by adding this as a sort descriptor they can have the cells display in the order they want. It's a bit different from your requirement because they have the 2 entities mixed into different sections, but you can still make use of it just with different sort properties.

The moral of the story is don't fight the framework, embrace it :-)

1 Comment

Worth reading. malhal, you nailed it. we could able to achieve sectioning using abstract entity.
5

Extending Giao's solution with two NSFetchedResultsControllers - we have to remember our NSFetchedResultsController do not know about our two sections and returned NSIndexPathes will be always for first section.

So when we are getting an object in cell configuration:

- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (!cell) {
        [tableView registerNib:[UINib nibWithNibName:@"cell" bundle:nil] forCellReuseIdentifier:@"cell"];
        cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    }
    [self configureCell:cell atIndexPath:indexPath];
    return cell;
}

-(void)configureCell:(UITableViewCell*)cell atIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section == 0) {
        NSManagedObject *object = [self.fetchedResults1 objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
        //use object to configure cell
    } else {
        NSManagedObject *object = [self.fetchedResults2 objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]];
        //use object to configure cell
    }
}

Updating cells while NSFetchedResultsController noticed some changes:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    NSIndexPath *customIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:(controller == self.fetchedResultsController1)?0:1];
    NSIndexPath *customNewIndexPath = [NSIndexPath indexPathForRow:newIndexPath.row inSection:(controller == self.fetchedResultsController2)?0:1];
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:customNewIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:customIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath];
            break;
        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:customIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:customNewIndexPath] withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

1 Comment

Nice answer but after relaunching the App 'performFetch' has to be called otherwise the context loses the fetched ManagedObjects. Any help?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.