Search code examples
objective-ciosios5uitableview

UITableView: Handle cell selection in a mixed cell table view static and dynamic cells


I am trying to mix dynamic and static cells in a grouped table view: I would like to get two sections with static cells at the top followed by a section of dynamic cells (please refer to the screenshot below). I have set the table view contents to static cells.

Mixing dynamic and static table view cells

Edit

Based on AppleFreak's advice I have changed my code as follows:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell;
    if (indexPath.section <= 1) { // section <= 1 indicates static cells
        cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; 
    } else { // section > 1 indicates dynamic cells
        CellIdentifier = [NSString stringWithFormat:@"section%idynamic",indexPath.section];
        cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    }
return cell;

}

However, my app crashes with error message

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'UITableView dataSource must return a cell from tableView:cellForRowAtIndexPath:'

for section 0 and row 0. The cell returned from cell = [super tableView:tableView cellForRowAtIndexPath:indexPath] for section 0 and row 0 is nil.

What is wrong with my code? Could there be any problems with my outlets? I haven't set any outlets because I am subclassing UITableViewController and supposedly do not any outlets for tableview to be set (?). Any suggestions on how to better do it?

enter image description here

Edit II

I have recreated my scene in storyboard (please refer to my updated screen shot above) and rewritten the view controller in order to start from a new base. I have also read the description in Apple's forum as applefreak suggested. However, I run in my first problem with the method numberOfSectionsInTableView:tableView, in which I increment the number of static sections (two) by one.

  - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [super numberOfSectionsInTableView:tableView] + 1 ; }

The app crashed with the error message:

Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayI objectAtIndex:]: index 2 beyond bounds [0 .. 1]'

Why is this code not working for me even though I followed Apple's and applefreak recommendations? It is possible that the tableView has changed a bit in iOS 6?

Solution: I got this to work now using AppleFreaks code sample in his answer below. Thank you, AppleFreak!

Edit III: Cell Selection:

How can I handle cell selection in a mixed (dynamic and static cells) cell table view? When do I call super and when do I call self tableView? When I use

[[super tableView] selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]

and try to check for the selected index paths with:

UITableView *tableView = [super tableView];
if ( [[tableView indexPathForSelectedRow] isEqual:customGrowthIndexPath] ) { .. }

I get an return value of nil.

As I can't find the source of my error, I really would appreciate your help


Solution

  • For static cells you need to call super method. I presume that you will be extending UITableViewController. See below

    - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        static NSString *CellIdentifier = @"Cell";
        UITableViewCell *cell;
    
        /* Detect cell is static or dynamic(prototype) based on the index path and your settings */
    
        if ("Cell is prototype") 
           cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
       else if ("Cell is static")
           cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];
    
      // Modify cell properties if you want
    
       return cell;
    }
    

    Also for more information on mixing cells see Mixing static and dynamic table view content discussion on apple forums.

    [EDIT]

    If you haven't read apple link above then please do so carefully. For dynamic content you will build the cell first time with the given identifier in the code itself. Next time on wards you will dequeue the cell rather than building it! It's the same old way.

    Moreover, remember that the static data source thinks there are only 2 sections(for example). You can't ask it about section 2, because it thinks there are only sections 0 and 1. If you're going to be inserting dynamic content anywhere but the end of the table, you need to lie to super when you forward the data source methods. Your numberOfRowsInSection: method, for example, should look something like this:

    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        if (section == 1) {
            return 1;
        }
        else if (section > 1){
            section--; // Basically you are forming sections for dynamic content
        }
    
        return [super tableView:tableView numberOfRowsInSection:section];
    }
    

    The key is making the adjustment for your dynamic content so that the static data source still gets the values it expects

    [EDIT]

    Here is the complete working example and tested. Just copy past all of following methods in your code and it should work straight way. You have to override all the tableview methods when you have mixed cells. Return the total number of static and dynamic sections in "numberOfSectionsInTableView" as per your requirements, and then in each of the remaining methods; if the section or row in question is static just call through to super, if it's dynamic pass back the relevant details as you would in a normal UITableViewController subclass. In this example, I am simply returning nil or hard coded values.

    For more information check this thread on apple forums.

    - (int)numberOfSectionsInTableView:(UITableView *)tableView
    {
        return 3;
    }
    
    - (NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
    {
        return nil;
    }
    
    - (NSString*)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section
    {
        return nil;
    }
    
    - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return NO;
    }
    
    - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return NO;
    }
    
    -(float)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
    {
        return 44.0f;
    }
    
    - (float)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
    {
        return 44.0f;
    }
    
    - (float)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return 44.0f;
    }
    
    - (UIView*)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
    {
        return nil;
    }
    
    -(UIView*)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
    {
        return nil;
    }
    
    -(int)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        return 5;
    }
    
    - (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    {
        if(section == 0)
            return 2;
        if(section == 1)
            return 2;
    
        return 5;
    }
    
    - (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if(indexPath.section <= 1)
            return [super tableView:tableView cellForRowAtIndexPath:indexPath];
    
        static NSString *CellIdentifier = @"Cell";
    
    
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
        if (cell == nil)
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    
    
        cell.textLabel.text = @"dynamic row";
    
        return cell;
    }
    

    [EDIT]

    You don't call super in didSelectRowAtIndexPath. It's straight forward. See below code.

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
         int row = indexPath.row;
         [tableView deselectRowAtIndexPath:indexPath animated:YES];
    
         UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
         //process the cell
    }