Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSFetchedResultsController prepend a row or section

I have a UITableView populated with a standard NSFetchedResultsController. However I'd like to prepend a row or a section (row preferably but either would works fine really.)

The only way I can possibly see doing this right now is to rewrite all the NSIndexPath's manually when dealing with the section/rows dealing with the data from NSFetchedResultsController to trick it into seeing section at index 0 and starting with row at index 0. This however seems like a really bad idea that would quickly get confusing so I'd like to preferable avoid that.

A good example of this would be in the official Twitter app when you start it up for the first time and I walks you through adding some people from your friends list.

Screenshot of Twitter app's suggestions page, with the relevant sections highlighted

The red section is pretty much what I'd like to achieve, and the yellow section I assume is the results from an NSFetchedResultsController in the same section (though with their custom styling it might be a separate section.)

like image 285
Philip Avatar asked Jun 09 '12 22:06

Philip


2 Answers

This is possible to do in a fairly clean way.

I'm assuming you're starting with a standard tableview set up with a standard NSFetchResultsController that uses Apple's sample code.

First you need two utility functions:

- (NSIndexPath *)mapIndexPathFromFetchResultsController:(NSIndexPath *)indexPath
{
    if (indexPath.section == 0)
        indexPath = [NSIndexPath indexPathForRow:indexPath.row+1 inSection:indexPath.section];

    return indexPath;
}

- (NSIndexPath *)mapIndexPathToFetchResultsController:(NSIndexPath *)indexPath
{
    if (indexPath.section == 0)
        indexPath = [NSIndexPath indexPathForRow:indexPath.row-1 inSection:indexPath.section];

    return indexPath;
}

These should be fairly self explanatory - they're just helpers to deal with adding the extra row when we want to use an index path from the fetched results controllers to access the table, or removing it when going the other way.

Then we need to create the extra cell:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"MyCellId";

    if (indexPath.section == 0 && indexPath.row == 0)
    {
        UITableViewCell *cell;
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
        cell.selectionStyle = UITableViewCellSelectionStyleGray;
        cell.textLabel.text = NSLocalizedString(@"Extra cell text", nil);

        return cell;
    }

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil] autorelease];
    }

    [self configureCell:cell atIndexPath:indexPath];

    return cell;
}

make sure we configure it correctly (configurecell will only be called for cells from the fetch results controller):

// the indexPath parameter here is the one for the table; ie. it's offset from the fetched result controller's indexes
- (void)configureCell:(SyncListViewCell *)cell atIndexPath:(NSIndexPath *)indexPath {
    indexPath = [self mapIndexPathToFetchResultsController:indexPath];

    id *obj = [fetchedResultsController objectAtIndexPath:indexPath];
    <... perform normal cell setup ...>
}

and tell the tableview it exists:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSInteger numberOfRows = 0;

    if ([[fetchedResultsController sections] count] > 0) {
        id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
        numberOfRows = [sectionInfo numberOfObjects];
    }

    if (section == 0)
        numberOfRows++;

    return numberOfRows;
}

and respond to selection:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:YES];

    if (indexPath.section == 0 && indexPath.row == 0)
    {
        [self doExtraAction];
        return;
    }

    ... deal with selection for other cells ...

and then remap any updates we get from the results controller:

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

    indexPath = [self mapIndexPathFromFetchResultsController:indexPath];
    newIndexPath = [self mapIndexPathFromFetchResultsController:newIndexPath];

    switch(type) {
        ... handle as normal ...
like image 60
JosephH Avatar answered Nov 06 '22 07:11

JosephH


I understand your concerns about complexity, but it is really just adding 1 to numberOfRowsInSection: and adding 1 to indexPath.row in cellForRowAtIndexPath: (beside adding the code for row 0).

Another solution would not have to be very elaborate to become even more cumbersome.

That being said, it really seems that the "heading" you are proposing is a typical candidate for a section header.

like image 33
Mundi Avatar answered Nov 06 '22 07:11

Mundi