Submerged Label in UITableViewCell

Apple provides several built-in table cell types in UIKit, but they are not flexible enough to be reused in most applications. On the other hand they give an excellent example about table cells design: AdvancedTableViewCells (you can type "AdvancedTableViewCells" in Xcode help to get it). Here I describe a simple cell with two labels based on a design from this example, but with a twist.

  • The first label is the main text. It could be relatively long and wrap to several lines, but user must see all the text.
  • The second label is a shorter string, something like tag or category that takes a word or two. It should be emphasized by using bold font and dark rounded rectangle as a background.
  • Now is the trickiest part: the second label should be below the first one and aligned to the right. But if the main text does not fill the whole line or wraps to the next line and does not fill it completely so there is enough space for the second label then it should be displayed in this unused space.

Here is a sample application that demonstrates possible cases:

RightLabel

I will not post all the source code here, just highlight the interesting parts. The cell class is ComboCell and here is its declaration:

@interface ComboCell : UITableViewCell {
  UIView *cellContentView;
  NSString *text;
  NSString *subtext;
}

@property(nonatomic, retain) NSString *text;
@property(nonatomic, retain) NSString *subtext;

+ (CGFloat)cellHeightWithText:(NSString *)text
                      subtext:(NSString *)subtext
                        width:(CGFloat)width;

@end

Since height is different for each cell and depends on labels and table width I've added a class method that calculates height given these parameters. In this form it could be called from the table view controller:

- (CGFloat)tableView:(UITableView *)tableView
 heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  return [ComboCell cellHeightWithText:[data objectAtIndex:(indexPath.row * 2)]
                               subtext:[data objectAtIndex:(indexPath.row * 2 + 1)]
                                 width:tableView.bounds.size.width];
}

The advantage of this setup is that code that calculates the cell height is located in the cell class so it could be reused by other table view controllers. The implementation of this function does all the work to calculate the right height and to find place for the second label. You can check the source code to see how it works but the idea behind it is simple:

  1. Calculate the first label size
  2. Calculate the second label size
  3. Create shortest string that has the same or greater width then the second label
  4. Calculate size of the first label combined with this string
  5. If height of the combined string is the same as the height of the first label then there is a space for the second label at the end of the first label

It may sound like a lot of work but I cache those shortest strings so performance is good.

There is no function in CoreGraphics to add a path for rounded rectangle, so I'm using the following function:

static __inline__ void CGContextAddRoundedRect(
    CGContextRef c, CGRect rect, int corner_radius) {
  CGFloat x_left = rect.origin.x;
  CGFloat x_left_center = rect.origin.x + corner_radius;
  CGFloat x_right_center = rect.origin.x + rect.size.width - corner_radius;
  CGFloat x_right = rect.origin.x + rect.size.width;
  CGFloat y_top = rect.origin.y;
  CGFloat y_top_center = rect.origin.y + corner_radius;
  CGFloat y_bottom_center = rect.origin.y + rect.size.height - corner_radius;
  CGFloat y_bottom = rect.origin.y + rect.size.height;
  CGContextBeginPath(c);
  CGContextMoveToPoint(c, x_left, y_top_center);
  CGContextAddArcToPoint(c, x_left, y_top, x_left_center, y_top, corner_radius);
  CGContextAddLineToPoint(c, x_right_center, y_top);
  CGContextAddArcToPoint(c, x_right, y_top, x_right, y_top_center, corner_radius);
  CGContextAddLineToPoint(c, x_right, y_bottom_center);
  CGContextAddArcToPoint(c, x_right, y_bottom, x_right_center, y_bottom, corner_radius);
  CGContextAddLineToPoint(c, x_left_center, y_bottom);
  CGContextAddArcToPoint(c, x_left, y_bottom, x_left, y_bottom_center, corner_radius);
  CGContextAddLineToPoint(c, x_left, y_top_center);
  CGContextClosePath(c);
}

Note that I declare it as static __inline__ in the header file. Alternatively you may use CG_INLINE macro.

Xcode project is stored in my samples repository, enjoy!

  • Digg
  • del.icio.us
  • Reddit
  • StumbleUpon
  • LinkedIn
  • E-mail this story to a friend!
  • Print this article!

blog comments powered by Disqus