7

I am putting together a TableView and I need to have multiple cell classes used in the same table.

So for example how would I use more than one cell class in my cellForRowAtIndexPath method?

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    // Configure the cell...

    return cell;
}

I know I could simply replace UITableViewCell with my custom class and use if statements to adjust the class depending on the index, but isn't that a bit messy, what would be a reasonable, and possibly the most intelligent way of doing this?

gariepy
  • 3,576
  • 6
  • 21
  • 34
Josh Kahane
  • 16,765
  • 45
  • 140
  • 253
  • 1
    Possible duplicate of [2 different types of custom UITableViewCells in UITableView](http://stackoverflow.com/questions/1405688/2-different-types-of-custom-uitableviewcells-in-uitableview). – torrey.lyons Jul 25 '12 at 22:02

3 Answers3

9

Sure can. Just create new instances of your custom cells and give them a new CellId.

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

   if (condition1)
    {
        static NSString *CellIdentifier1 = @"Cell1";
        UITableViewCell *cell = 
         [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];

        // TODO: if cell is null, create new cell of desired type.  
        // This is where you    create your custom cell that is 
        // derived form UITableViewCell.
    }
    else
    {
        static NSString *CellIdentifier2 = @"Cell2";
        UITableViewCell *cell = 
         [tableView dequeueReusableCellWithIdentifier:CellIdentifier2];

        //TODO: if cell is null, create new cell of desired type

    }
    // Configure the cell...

    return cell;
}
Alex Cio
  • 6,014
  • 5
  • 44
  • 74
Brian
  • 6,910
  • 8
  • 44
  • 82
  • I've seen various posts recommending this pattern, but unfortunately it breaks with iOS 6. In the betas, `-dequeueReusableCellWithIdentifier` throws an exception if the identifier isn't registered instead of returning `nil`. – Kaelin Colclasure Jul 25 '12 at 22:19
  • Do you think it's a bug or did Apple go and break something? If it's not a bug, a lot of apps are going to be busted. – Brian Jul 27 '12 at 23:18
  • 1
    The exception message basically says the app is trying to load a reusable cell with an identifier that isn't registered. Given that Apple's official position on throwing exceptions is that they are thrown to help developers diagnose programming errors, one might infer that Apple never intended `-dequeueReusableCellWithIdentifier:` to be used with an unregistered identifier. That said, if it truly does break lots of apps, Apple might pull the exception before the final release ships… It's hard to know. In any case, prudence would seem to favor avoiding this pattern in new apps. – Kaelin Colclasure Jul 28 '12 at 00:48
8

You can do this by defining the table to have several prototype cells in its .xib file or Storyboard. Note the setting "Dynamic Prototypes" for the table view in the Xcode screen shot below:

enter image description here

Each prototype cell must be given a unique reuse identifier. In this example, the first cell type has a reuse identifier @"ScanCell", and the second @"DetailCell". Then, in your -tableView:cellForRowAtIndexPath: method you simply choose which class of cell to use by choosing which reuse identifier you pass to -dequeueReusableCellWithIdentifier:.

Here is an example taken from one of my own apps:

- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString * cellIdentifier = @"DetailCell";
    if ([indexPath section] == 0) {
        cellIdentifier = @"ScanCell";
    }
    UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier
                                                             forIndexPath:indexPath];
    if ([indexPath section] == 1) {
        CBPeripheral * peripheral = (CBPeripheral *)[self.appDelegate.peripherals objectAtIndex:[indexPath row]];
        cell.textLabel.text = [peripheral name];
        cell.detailTextLabel.text = [self.appDelegate.peripheralKeys objectAtIndex:[indexPath row]];
    }
    return cell;
}

If you want the prototype cells to have different classes, simply set their class in the .xib or Storyboard file.

Kaelin Colclasure
  • 3,925
  • 1
  • 26
  • 36
  • Even though this approach looks fine for small application, this will lead to lot of if else in case if your application need to show different types of cells in a same table view.Polymorphic approach would be the right way to handle this case. – RJR Sep 10 '13 at 15:30
  • 1
    You're my time saver.. Do not forget to set your tableViewCell's Class! It could occur error if you don't – Changnam Hong Feb 27 '17 at 07:07
3

At some point you will need to associate different cell classes with the different item types in your data source. An if statement might be the way to go. You might encapsulate this in a separate method, like this:

+(Class)cellClassForItem:(id)rowItem
{
    Class theClass = [ UITableViewCell class ] ;

    if ( [ rowItem isKindOfClass:[ ... class ] ] )
    {
        theClass = [ CellClass1 class ] ;
    }
    else if ( [ rowItem isKindOfClass:[ ... class ] ] )
    {
        theClass = [ CellClass2 class ] ;
    }

    return theClass ;
}

Or, you might have each item in your data source implement a protocol:

@protocol DataSourceItem <NSObject>
-(Class)tableViewCellClass ;
@end

Now, your delegate method would look like this (assuming the second technique)

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    id<DataSourceItem> item = (id<DataSourceItem>)[ tableView itemForRowAtIndexPath:indexPath ] ;
    Class cellClass = [ item tableViewCellClass ] ;
    NSString * cellID = NSStringFromClass( cellClass ) ;

    UITableViewCell *cell = [ tableView dequeueReusableCellWithIdentifier:cellID ] ;
    if ( !cell )
    {
        cell = [ [ cellClass alloc ] initWithStyle:... reuseIdentifier:cellID ] ;
    }

    cell.value = item ;

    return cell;
}
nielsbot
  • 15,922
  • 4
  • 48
  • 73