Menu

Displaying Variably-Sized Text Cells in a UITableView

11 April 2009

For a couple of iPhone apps that i am working on (one is internal, one will hopefully be on the AppStore soon), i needed a flexible way to display text blocks of varying lengths in a list. essentially, i wanted something that looks like this:

This is an ideal candidate for a regular old UITableView, with a custom cell class, and so CaptionAndTextCell was born – a reusable UITableViewCell implementation that is shared by my two apps (and probably many future ones).

So how did i implement this? It’s pretty straight forward. We start by creating a new class descending from UITableViewCell, and call it CaptionAndTextCell. We give it two NSString fields and properties to hold the caption, and text body, respectively:

Besides storing the caption and text, the cell needs to do two core tasks: the obvious one is drawing itself in an overriden -drawRect: method; the second one to actually calculate its size based on the contained text.

Since drawing is difficult without knowing what size to draw at, we’ll start with the second task, and add a new static method to our class, named +cellHeightForCaption:text:width:.

Why a static method? Well, if you’ve looked at the API for UITableView, you will might have noticed that the UITableViewController implementation for your table is responsible for providing the height of individual cells. Because the table view only allocates cells as they need to be displayed, but – for obvious reasons – needs to know the exact height of all cells in advance, we’ll do this calculation in a handy static method hat can be called without actually creating the cell in question. This way, the calculation logic is encapsulated where it should be (inside CaptionAndTextCell), but readily callable. If you ever change how CaptionAndTextCell does its rendering (say, to use a larger font), all your logic is in one place.

All the UITableViewController needs to to to provide the height for a cell is to implement the following method, delegating the actual math to CaptionAndTextCell.

this method does a couple things. first, we grab copies of the two fonts we want to use to render caption and text, respectively. After all, the final size of the text will depend on the font.

Next, we calculate the pixel size of caption and text (cs and ts – i am keeping variable names short because this code already is hard enough to read online, as it stands), if rendered into a rectangle of width-20.0 (leaving 10 pixels of space around our text, on the sides) with standard word-wrapping. These two will determine the final height of our cell.

Note how the width is never hardcoded anywhere. this way, the cell will work fine in landscape mode, as well as on the fabled 10″ touch when it comes in June (knock on wood ;).

We also calculate the pixel size of our two texts if rendered on a single line (cs2 and ts2). The idea being that if we find both texts actually fit o the same line, we’ll draw them that way, rather than one below the other.

So depending on whether ts2.width and cs2.width fit on one line (along with some white space) or not, we return the appropriate height.

With that done, let’s look go back to our table view controller, and finish hooking up our new cell class. Any table view controller of course needs to implement – tableView:cellForRowAtIndexPath: to provide data. We just need to make a few changes to the default implementation, to create and return our custom cell class and initialize it:

As before, we obtain copies of the fonts we want to draw in, as well as the colors to use for the individual texts (we’ll draw the caption in black, but the body text in a slightly lighter gray). We also do the same math to obtain the rendered sizes of out text – except we don’t need the ts value this time around – our cell is already sized accordingly, so we can just draw the body text without regard for its actual height.

First we draw the caption, at offset 5/5 of our cell. Next, we check whether caption and body fit on one line. If so, we draw the body text to the right of the caption and are done. If not, we’ll draw it below, word-wrapped to the appropriate width.

Finally, there’s some boilerplate plumbing code, we need to implement property getters and setters, and also initialize and dealloc our cell:

and with that, we’re done. (in my app, and in the screenshots shown, i also add a slight gradient to the background, to round things off; i’ll save that as exercise for the reader.

I hope you found this a helpful overview on creating a custom UITableViewCell implementation and on measuring and rendering texts. Make also sure to keep an lookout for “FeedOne”, my first and very simplistic app that will use this class, due on the AppStore any month now… ;)