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.
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:
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.