iPhone UITableView with animated expanding cells tutorial

Hello everyone,

This is a tutorial which will go over creating a UITableview where the cells will expand and contract with user interaction.

This is a preview of the project:
- Before any cell is selected -
screen shot

- After a cell is selected –
screen shot

Prerequisits: Before beginning this tutorial you should be familiar with basics of development and especially the basics of working with UITableViews.

Lets get Started
First download the sample project at this location: Sample Expanding Table Cell Project
This project is a very stripped down example of something that might resemble a table of comments which contain varying amounts of text for each comment. Since the cells are able to be expanded you can initially display more comments and provide a uniform look where the user can choose which comment he/she wants to expand further. The project contains one view controller which has the table view. There is a custom table cell which contains an image which might represent the user(my rabbit) and a UILabel which will hold the comment text.
NOTE: The reason I used a UILabel rather than a UITextView is that a label provides an automatic “…” when it truncates the text and its more straight forward to calculate the text height than a text view. That said a UITextView might be a little more appropriate.
ALSO: This project has no memory management and there are likely things that can be improved but I leave that to you.

The user tableview will allow the following:
1.selection of cells that can be expanded and expand the cell to the point where all of its text is displayed
2. if a cell is already expanded and you select another cell the new selected cell will expand and the previous cell will be contracted
3. If a cell is already expanded and is selected again it will contract back to the set size

How it works:

Summary: Its really simple in concept. If a user selects a cell which has more text than can be shown we store that cell index and tell the tableview to reload that cell with its calculated height. If the user selects a new cell reload the original expanded cell and then reload the new one calculating its height based on its content. IOS does the animation for us so we really only have to make sure that our logic adds up through a few of the tableview delegate functions.

The pieces:

ExpandingTableViewProjectViewController.h:

 //This array will store our coments
NSMutableArray *textArray;

 //This is the index of the cell which will be expanded
NSInteger selectedIndex;

In my example I only have an array to store the comments and an int to store the index of the currently expanded cell. Thats it!

ExpandingTableViewProjectViewController.m

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

.............

    //If this is the selected index then calculate the height of the cell based on the amount of text we have
    if(selectedIndex == indexPath.row)
    {

        CGFloat labelHeight = [self getLabelHeightForIndex:indexPath.row];
         cell.commentTextLabel.frame = CGRectMake(cell.commentTextLabel.frame.origin.x,
                                                     cell.commentTextLabel.frame.origin.y,
                                                     cell.commentTextLabel.frame.size.width,
                                                     labelHeight);

    }
    else {
            //Otherwise just return the minimum height for the label.
            cell.commentTextLabel.frame = CGRectMake(cell.commentTextLabel.frame.origin.x,
                                                         cell.commentTextLabel.frame.origin.y,
                                                         cell.commentTextLabel.frame.size.width,
                                                         COMMENT_LABEL_MIN_HEIGHT);

     }
    cell.commentTextLabel.font = [UIFont fontWithName:@"Helvetica" size:14.0f];
    cell.commentTextLabel.text = [textArray objectAtIndex:indexPath.row];

    return cell;
}

Here is where we set up the cells and the thing to note is that if we are setting up the selected cell we want to set the comment label to the size of its text. Otherwise the cell is not selected and the comment label should only be the set size regardless of the amount of text.

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    //If this is the selected index we need to return the height of the cell
    //in relation to the label height otherwise we just return the minimum label height with padding
    if(selectedIndex == indexPath.row)
    {
        return [self getLabelHeightForIndex:indexPath.row] + COMMENT_LABEL_PADDING * 2;
    }
    else {
        return COMMENT_LABEL_MIN_HEIGHT + COMMENT_LABEL_PADDING * 2;
    }
}

Just like in the cellForRowAtIndex function we only return the height of the cell in relation to the to the comment text if its the selected index.

-(NSIndexPath*)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    //We only don't want to allow selection on any cells which cannot be expanded
    if([self getLabelHeightForIndex:indexPath.row] > COMMENT_LABEL_MIN_HEIGHT)
    {
        return indexPath;
    }
    else {
        return nil;
    }
}

I am using this function as a way of disabling user interaction of a cell if the comment text is not big enough to require expanding.
NOTE: You may not want this but if you were to remove this function you would have to do more work in the cellForRowAtIndex function as a comment may have less text than the minimum size and can result in contracting cells with the current logic.

The final piece

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{

    //The user is selecting the cell which is currently expanded
    //we want to minimize it back
    if(selectedIndex == indexPath.row)
    {
         selectedIndex = -1;
         [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

         return;
    }

    //First we check if a cell is already expanded.
    //If it is we want to minimize make sure it is reloaded to minimize it back
    if(selectedIndex >= 0)
    {
        NSIndexPath *previousPath = [NSIndexPath indexPathForRow:selectedIndex inSection:0];
        selectedIndex = indexPath.row;
        [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:previousPath] withRowAnimation:UITableViewRowAnimationFade];
    }

 
    //Finally set the selected index to the new selection and reload it to expand
    selectedIndex = indexPath.row;
    [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

When the user selects a cell there are three cases which are handled in the function above.

1. The first condition is when the cell selected is already expanded. We set the selected index to -1 indicating that no cell will be expanded and we reload that cell and return early.

2. The second condition is when there is already a cell expanded but the user has selected another expandable cell. In this case we store the previous index and reload it so it returns to its minimum size and then we reload the new selected index to its real size. This will obviously minimize the old while maximizing the new.

3. The final case where the two “if”s fail is when there is no cell currently expanded so we are only storing the new selected index and reloading it to expand.

Thats it
Thats basically the whole project. To recap all I am doing is keeping track of a selected index and ensuring that the tableview only dynamically changes the height of the selected cell based on the amount of text.

I hope this tutorial is a help to some out there. The sample project has no memory management and there are probably things that I could have done better but I put it together for the purpose of being simple and easy to understand.

Link to project: Sample Expanding Table Cell Project

If this was helpful to you or you would like more clarification I would appreciate a comment. Also feel free to send me an email: Email Me

Enjoy
-Ryan

If you don’t have a reason to work hard then you haven’t tried kiteboarding