AndyC
AndyC

Reputation: 115

UITableView with two types of resusable cell not working properly

I am trying to create a table view which uses two types of cells - default and subtitle.

I tried to use a single reusable cell (*cell) which seemed to work fine until I got to the bottom cell which was off screen - when this came into view it was a duplicate of the first visible cell.

I thought that I could try to add a second type of cell (*cellB) and when I did it seemed to solve the problem however it would crash every so often with the following error:

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

I know that I am doing something wrong and I am pretty sure I am not implementing the reusable cells properly but after several days of frustration I would really appreciate some advice.

P.S. I have searched many previous posts but none seem to cover the issue of using two types of cells on the same table.

Thanks in advance.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
 return 1;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return  5; 
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];

 UITableViewCell *cellB = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCellB"];


switch (indexPath.section) {


 case 0:
     if (cell == nil) {
         // The only subtitle cell
         cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle
                                      reuseIdentifier:@"UITableViewCell"]; 

     }

        [[cell textLabel] setText:title];
        [[cell textLabel] setTextColor:[UIColor whiteColor]]; 
        [[cell detailTextLabel] setText:[NSString stringWithFormat:@"Entry: £%@",price]];
        [[cell detailTextLabel] setTextColor:[UIColor colorWithWhite:1.0 alpha:.8]]; 


        cell.selectionStyle = UITableViewCellSelectionStyleNone; 

     break;

 case 1:
     if (cell == nil) {

         cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:@"UITableViewCell"]; 

         UIView *cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
         cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:@"PhotoFrame.png"]];
         cell.backgroundView = cellBackView;

     }

     [lImage setFrame:CGRectMake(0, 23, 320, 200)];

     [cell.contentView addSubview:lImage];

     break;

 case 2:
     if (cell == nil) {

         cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:@"UITableViewCell"]; 

         UIView *cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
         cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:@"PaperTop.png"]];
         cell.backgroundView = cellBackView;

     }

     break;

 case 3:
     if (cell == nil) {

         cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:@"UITableViewCell"]; 


         UIView *cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
         cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:@"Paper.png"]];
         cell.backgroundView = cellBackView;

     }

     break;

 case 4:
     // I'm pretty sure this bit is wrong but if I use (cell == nil) the first cell is shown instead
     if (cellB == nil) {

         cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:@"UITableViewCellB"]; 

         UIView *cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
         cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:@"PaperBottom.png"]];
         cell.backgroundView = cellBackView;

     }

     break;

 default:
     break;
} 

 return cell; 
}

Upvotes: 1

Views: 2481

Answers (4)

Jim
Jim

Reputation: 5960

This method is called once for each cell that needs to be filled. If you scroll the table, rows will go off the screen and be queued with their reuse identifier. When rows scroll onto the table, this method is called to fill the cells as they become visible.

So first, you only want to dequeue one cell each time this method is called. By dequeuing cells of type UITableViewCell' andUITableViewCellB' you are dequeuing one that isn't going to be used. So you need to determine which type of cell you need before you dequeue one, and then dequeue the right type (by its reuse identifier).

Second, the purpose of the cell queuing mechanism is so you don't have to do things like customize the cell appearance every time it appears in the view. If a cell with that type of appearance is already in the queue, then it should come out of the queue already set up, and you only need to put the data into it. This is done for performance (speed), but it may not make a lot of difference in your case.

I may be wrong about this, and I'll correct my answer if I am, but the error message may be because the number of sections returned by numberOfSections and/or the number or rows returned by numberOfRowsInSection: is not correct and doesn't match the data source. It is trying to access an element of the data source that doesn't exist.

What kind of data source are you using, and can you show the code for numberOfSections and numberOfRowsInSection:?

UPDATE with Correction

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
 if (cell == nil) {
    cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
                                      reuseIdentifier:@"UITableViewCell"]; 

UIView *cellBackView;  // move this outside of the switch block

switch (indexPath.section) {


 case 0:

   [[cell textLabel] setText:title];
   [[cell textLabel] setTextColor:[UIColor whiteColor]]; 
   [[cell detailTextLabel] setText:[NSString stringWithFormat:@"Entry: £%@",price]];
   [[cell detailTextLabel] setTextColor:[UIColor colorWithWhite:1.0 alpha:.8]]; 
   cell.selectionStyle = UITableViewCellSelectionStyleNone; 
   break;

 case 1:

    cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
    cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:@"PhotoFrame.png"]];
    cell.backgroundView = cellBackView;
    [lImage setFrame:CGRectMake(0, 23, 320, 200)];
    [cell.contentView addSubview:lImage];
    break;

 case 2:

    cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
    cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:@"PaperTop.png"]];
    cell.backgroundView = cellBackView;
    break;

 case 3:

    cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
    cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:@"Paper.png"]];
    cell.backgroundView = cellBackView;
    break;

 case 4:
    cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
    cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:@"PaperBottom.png"]];
    cell.backgroundView = cellBackView;
    break;

 default:
    break;
} 

 return cell; 
}

Upvotes: 2

jscs
jscs

Reputation: 64002

As cgull pointed out, when [indexPath section] is 4, you're initializing cellB, but still returning cell, which may or may not have a valid cell in it.

You need to have one variable that holds the cell you're going to return, no matter the path taken through the switch:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

    NSString identifier = @"UITableViewCell";
    if( [indexPath section] == 4 ){
        identifier = @"UITableViewCellB";
    }
    // Try to dequeue the appropriate cell kind
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];


    switch (indexPath.section) {

            //other cases...

        case 4:
            // You now have only one variable that can possibly hold a cell.
            // If section is 4, it's either nil or a UITableViewCellB kind.
            if (cell == nil) {

                cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
                                  reuseIdentifier:@"UITableViewCellB"];
                // Set up new cell

            }

            break;

        default:
            break;
    } 

    return cell; 
}

Upvotes: 0

cgull
cgull

Reputation: 1417

If cellB isn't null, you're still returning cell and not cellB.

Edit:

To fix, add code in the case 4 block as follows:

case 4:
    if (cellB == nil) {
        // No changes in here
    }
    else {             //
        cell = cellB;  // Add these 3 lines
    }                  //
    break;

Upvotes: 2

Alex Deem
Alex Deem

Reputation: 4805

cgull is correct. Replace case 4 with this:

case 4:
  if (cellB == nil) {

     // assign to the correct cell
     cellB = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault
                                  reuseIdentifier:@"UITableViewCellB"]; 

     UIView *cellBackView = [[UIView alloc] initWithFrame:CGRectZero];
     cellBackView.backgroundColor = [UIColor colorWithPatternImage: [UIImage imageNamed:@"PaperBottom.png"]];
     cell.backgroundView = cellBackView;

 }

 return cellB;  // <--- return the correct cell

Upvotes: 0

Related Questions