Upward Mobility: Give Your iOS Table Cells Some Class

You're not stuck with the stock options when creating tables

UITableView is the meat and potatoes of many iOS UIs, but if you restrict yourself to the off-the-shelf table cell styles, you’re missing out on a lot of opportunities for customization. By using a combination of variable cell heights and a custom UITableViewCell class, you can make UIs that look nothing like a standard table.

To see how you can make this happen in your applications, let’s start with the world’s most boring table example, a list of my favorite foods.

simpletable

The implementation for this is the stock table view code you’ll see in any iOS tutorial:

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

-(UITableViewCell *)tableView:(UITableView *)tableView 
                        cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSArray *myFavoriteFoods = 
                   @[@"Pizza", @"Sushi", @"Steak", @"Chinese", @"Pasta"];
    static NSString *CellIdentifier = @"Cell";

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

    // Configure the cell.
    cell.textLabel.text = myFavoriteFoods[indexPath.row];
    return cell;
}

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

So, what can we do to take this design and make it our own? We can start by designing our own class that extends UITableViewCell, and that has its own XIB file, which we’ll call FavoriteFoodCell. There’s not much to it:

@interface FavoriteFoodCell : UITableViewCell
@property (strong, nonatomic) IBOutlet UILabel *foodName;
@property (strong, nonatomic) IBOutlet UIImageView *foodImage;

@end

Next, create a new Interface Builder XIB file of type View. Set the class of the View to FavoriteFoodCell (not the FileOwner; keep that as a generic UIViewController.) We’ll add a label and an image view, so that the cell looks like this:

tablecell_in_ib

 

The label and image are wired to the appropriate outlets in the class, of course. One thing you’ll have to do to make this work is to turn off autolayout on the XIB file; you can do that from the file inspector. Now we’re ready to use our new cell:

-(NSString *) foodForIndex:(NSIndexPath *) path {
    NSArray *myFavoriteFoods =
            @[@"Pizza", @"Sushi", @"Steak", @"Chinese", @"Pasta"];
    return myFavoriteFoods[path.row];

}

-(float) heightForFood:(NSString *) food {
    UIImage *image = [UIImage imageNamed:food];

    return (160 / image.size.width ) * image.size.height;

}

-(UITableViewCell *)tableView:(UITableView *)tableView
        cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"FavoriteFoodCell";

    FavoriteFoodCell *cell =
        [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        NSArray *topLevelObjects =
            [[NSBundle mainBundle] loadNibNamed:@"FavoriteFoodCell"
                                          owner:self options:nil];
        cell = [topLevelObjects objectAtIndex:0];
    }

    // Configure the cell.
    NSString *food = [self foodForIndex:indexPath];
    cell.foodName.text = food;
    CGRect frame = cell.foodImage.frame;

    frame.size.height = [self heightForFood:food];;
    cell.foodImage.frame = frame;
    cell.foodImage.image =
        [UIImage imageNamed:food];
    return cell;
}

-(CGFloat)tableView:(UITableView *)tableView
    heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *food = [self foodForIndex:indexPath];
    return [self heightForFood:food] + 12;

}

There are a couple of things we’ve done here. First, we refactored the code to figure out the food for an index into its own method, since we’re calling it from a few different places now. We also want to adjust the height of the image so that it fills the space horizontally (the image view being 160 pixels wide, and set to aspect fill.) So we compute how tall the image needs to be at a 1:1 ratio at a width of 160.

The really interesting code happens inside the cellForRowAtIndexPath method. First off, we use a unique cell identifier, because we don’t want to accidentally reuse one of these specialized cells somewhere else. We load the nib file from the bundle, and then get the first top level object inside the nib, which is the view. Since we set the view type to be our custom table cell, it will be all set up and ready to use.  We set the image and text, and adjust the image to have the appropriate height to fill at 160 width.

Finally, we need to adjust the cell height, otherwise the images will bleed into the next cell. We do that using heightForRowAtIndexPath, returning the computed image height plus some top and bottom padding. The resulting table is much more eye-catching.

finaltable

tags: ,