3

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

AlexR
  • 5,514
  • 9
  • 75
  • 130
  • Did you register the nib per the class doc? 'Important: You must register a class or nib file using the registerNib:forCellReuseIdentifier: or registerClass:forCellReuseIdentifier: method before calling this method.' – David H Oct 12 '12 at 12:11

6 Answers6

13

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
}
Paresh Masani
  • 7,474
  • 12
  • 73
  • 139
  • I have changed my code based on your advice but still have problems (see my updated question above). `cell = [super tableView:tableView cellForRowAtIndexPath:indexPath];`returns `nil` in my case. Do you have any idea what could be wrong with my code? Could there be anything messed up with my outlets? Thank you, applefreak! – AlexR Oct 12 '12 at 13:11
  • That's strange! I have done exactly same and it worked without any issues. Let me check and I will get back to you asap. Just don't forget to give identifiers. You don't need to register static cells if you are calling Super here. Did you see the apple link? – Paresh Masani Oct 12 '12 at 14:30
  • I haven't done anything different! From the image you attached, it doesn't seem that you have 2 sections static? They are three? And for Dynamic cells you don't need to add them in the XIB or Scene. You will do it from the code. I have all static cells type custom with different images and labels in it. I don't have reuseidentifier setup for static cells and neither register them explicitly. It works straight way. – Paresh Masani Oct 12 '12 at 14:40
  • How do you set the table cell style after retrieving a cell with `cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]`? If you don't set up your dynamic (= prototype) cells in Interface Builder, how do you it in code? How do you assign the reuse identifier to these cells? Thank you! – AlexR Oct 12 '12 at 15:15
  • I have edited my question: My app crashes in the method `- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView `when I return a number of sections which is higher than the number of static sections. I am using iOS 6. – AlexR Oct 13 '12 at 13:00
  • Okay. I have created sample code for you. Edited the answer. Just copy paste all of the methods into your code and it should work without any problems. Then just change this method to meet your requirements and as suggested. – Paresh Masani Oct 13 '12 at 20:44
  • Yes, my app still crashes very early when `numberOfSectionsInTableView:tableView` is being executed and the number of returned sections (which I have incremented by one dynamic row) does not match the number of static rows defined in Interface Builder. Did you try our code in iOS 6? – AlexR Oct 17 '12 at 12:22
  • Yes it is tested on iOS 6.0!! Did you override all the methods? – Paresh Masani Oct 17 '12 at 13:10
  • applefreak, thank you for your patience! I used your code snippet and sample in a new table view controller and got it to work now. Thank you so much! As I am now trying to handle **cell selection** in this mixed table, I have encountered some problems and added a new section (**Edit III**) to my question. Do you have any recommendation on how to handle cell selection in case of a mixed table view with static and dynamic cells? – AlexR Oct 22 '12 at 11:00
  • See edited ans. Please ask any new question in different thread. This is becoming messy! – Paresh Masani Oct 22 '12 at 11:29
  • Thank you for the advice about creating a new thread for this question, I was unsure if I should create one or not :-)... One final question about the code snippet you have added: Is it necessary to call `[tableView deselectRowAtIndexPath:indexPath animated:YES];` in `tableView:didSelectRowAtIndexPath:`? Usually `tableView:didDeSelectRowAtIndexPath:` is called automatically after a cell has been deselected (?). – AlexR Oct 22 '12 at 11:34
  • If you don't deselect the cell then it will stay blue when selected. Try removing it and see the difference when selecting and deselecting cells. You can remove it if you want your cell stay blue after selection. – Paresh Masani Oct 22 '12 at 12:23
  • Hello! I got it, but only with UITableViewCells, not custom cells. Do you create a section for your dynamic cells using the storyboard? Here you have my question, can you help me? http://stackoverflow.com/questions/12858363/uitableview-handle-cell-selection-in-a-mixed-cell-table-view-static-and-dynamic – Ricardo Jan 19 '13 at 12:47
  • @Ricardo what if I need it to work with dynamic custom cells? – Mazen Kasser Feb 03 '14 at 23:33
  • I don't remember how but finally I got it. – Ricardo Feb 04 '14 at 21:13
  • @MazenKasser you have to register class of nib for your cell identifier. UITableView have two methods for it -registerClass:forCellReuseIdentifier: -registerNib:forCellReuseIdentifier: Than you can create xib with your dynamic cell prototype. – Sound Blaster Nov 30 '14 at 17:18
  • @SoundBlaster thanks for your reply, but already implement different approach. – Mazen Kasser Nov 30 '14 at 20:44
1

BEST & YET EASIEST Way for using Custom Dynamic Cells with Static Cells:

1) Place Container View in the Header of a Dynamic TableView 2) Assign the Static TableView to this Container and Untick the "Scrolling Enabled"

Mazen Kasser
  • 3,559
  • 2
  • 25
  • 35
0

If there is no cell to dequeue, then cell will be nil, and therefore you have to alloc/init a new one. Please, check the value of cell after you try to dequeue one, and create a new cell if it is nil.

khose
  • 743
  • 1
  • 6
  • 19
  • 1
    I am using iOS 6 and according to Apple's [documentation](http://developer.apple.com/library/ios/#documentation/uikit/reference/UITableView_Class/Reference/Reference.html#//apple_ref/occ/instm/UITableView/cellForRowAtIndexPath:) `dequeueResusableCellWithIdentifier:forIndexPath` always returns an instantiated cell: "_Return type: A UITableViewCell object with the associated reuse identifier. This method always returns a valid cell._" – AlexR Oct 12 '12 at 11:56
  • Ok, didn't know you were using iOS6 :) – khose Oct 12 '12 at 13:50
0
UITableViewCell *cell = [self dequeueReusableCellWithIdentifier:@"cellIdentify"];
if (cell == nil)
    cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cellIdentify"];

Just you dont call alloc. If you dont want to reuse, just dont call dequeueReusableCellWithIdentifier.

Evgeniy S
  • 1,464
  • 12
  • 32
0

Thank you applefreak for comprehenisve answer. I'll add my suggestion here that you don't need any section calculations if you add a dummy sections for the static ones. You can even add placeholder elements there. This way you don't have to recreate NSIndexPaths. One important thing is to override each and every method that uses NSIndexPaths as these would crash the app due to array index mismatch.

Also, I gave up using the dequeue mechanism completely and created the cell statically as there weren't that many cells for the dynamic content I had. This makes tableView:cellForRowAtIndexPath: very simple.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.section == 0)
    {
        return _locationCells[indexPath.row];
    }
    else
    {
        return [super tableView:tableView cellForRowAtIndexPath:indexPath];
    }
}

Just remember the placeholder sections.

mkko
  • 4,262
  • 3
  • 25
  • 29
  • How did you create _locationCells and where? – Mazen Kasser Apr 04 '14 at 05:04
  • I'm creating them in the view controller's `viewDidLoad` method. I use `initWithStyle:reuseIdentifier:` and set the identifier to `nil` as there is nothing to reuse. In another part of my app I use the table view's `dequeueResusableCellWithIdentifier:forIndexPath` as the cell is loaded from a nib file. – mkko Apr 04 '14 at 09:41
  • Thanks, make sense, but unfortunately I don't have time left to test this. So you mean by initWithStyle:reuseIdentifier you can create custom cells. – Mazen Kasser Apr 05 '14 at 01:33
  • Yeah, if by custom you mean empty or predefined cells. However, I find it many times easier to use custom cell classes with nibs when there's more than one subview to place. – mkko Apr 06 '14 at 07:29
0

This can be simply solved by adding "fake" sections for the ones that will have dynamic cells. The only thing that will have to change is the cellForRowAt function, where you dequeue the dynamic cells for the "fake" sections instead of letting them be used from what you have in the storyboard. And for the rest of the cells u call

super.tableView(tableview, cellforRowAt: indexPath)

No need to override all the functions of the tableViewController

Eddy
  • 274
  • 3
  • 17