Drawing round badge view in UITableViewCell using CGPath and CGContextSetBlendMode

I haven’t had a chance to talk about my DDBadgeViewCell last week, though it’s been used in my app for a while.

Everybody is doing custom badge view

Screenshot

There are many projects and samples trying to do the similar thing, they all want a customizable colored round view, and DDBadgeViewCell does too.

DDBadgeViewCell is an UITableViewCell subclass with one badge view on the right. That badge view is customizable. You can change colors, including text color, badge color, and badge color when cell is hightlighted. You can also change text, it can be any characters, and the badge width will update along with text length.

If there’re five ways to do the drawing, this must be the 6th

And my approach for creating round colored badge view is inside drawRect: with CGPath and Blending mode. the whole drawRect: can be seperated into several parts:

Step One: Color First

First, get the current graphics context from stack by calling UIGraphicsGetCurrentContext().

CGContextRef context = UIGraphicsGetCurrentContext();

And I define some colors that need to be used later.

UIColor *currentSummaryColor = [UIColor blackColor];
UIColor *currentDetailColor = [UIColor grayColor];
UIColor *currentBadgeColor = self.cell.badgeColor;
if (!currentBadgeColor) {
    currentBadgeColor = [UIColor colorWithRed:0.53 green:0.6 blue:0.738 alpha:1.];
}

And update colors based on cell status, like isHighlighted or isSelected.

if (self.cell.isHighlighted || self.cell.isSelected) {
    currentSummaryColor = [UIColor whiteColor];
    currentDetailColor = [UIColor whiteColor];
    currentBadgeColor = self.cell.badgeHighlightedColor;
    if (!currentBadgeColor) {
        currentBadgeColor = [UIColor whiteColor];
    }
} 

Calculate badge text CGSize with sizeWithFont:, and defined badge frame size with some hardcode values.

CGSize badgeTextSize = [self.cell.badgeText sizeWithFont:[UIFont boldSystemFontOfSize:13.]];
CGRect badgeViewFrame = CGRectInset(CGRectIntegral(CGRectMake(rect.size.width - badgeTextSize.width - 20, 
    (rect.size.height - badgeTextSize.height) / 2, badgeTextSize.width, badgeTextSize.height)), -7, -2);

Step Two: Prepare

Here comes the point. Save the current graphic state onto context, so we can restore it and do the blending things later.

CGContextSaveGState(context);   

Set the color for filling the path later.

CGContextSetFillColorWithColor(context, currentBadgeColor.CGColor);

Step Three: The Critical Part

People usually do drawing directly on CGContext, like using CGContextAddLine() or CGContextAddArc() etc. But I prefer the other way: creating a mutable graphics path (CGPath) first, add line or arc to it, and add final path into context, then ask contect to darw it.

Add Arc

The good thing about using mutable path here is: you can easily create a closed round path only by calling ONE function TWICE, CGPathAddArc(), one is the left arc, and the other is the right arc. And you don’t have to fill the lines between these two arcs by yourself, it was already done when second arc was added. Sounds cool, huh?

Once path drawn to context, we restore the graphic state, back to the state we haven’t drawn the round badge path.

CGMutablePathRef path = CGPathCreateMutable();
CGPathAddArc(path, NULL, badgeViewFrame.origin.x + badgeViewFrame.size.width - badgeViewFrame.size.height / 2, 
    badgeViewFrame.origin.y + badgeViewFrame.size.height / 2, badgeViewFrame.size.height / 2, M_PI / 2, M_PI * 3 / 2, YES);
CGPathAddArc(path, NULL, badgeViewFrame.origin.x + badgeViewFrame.size.height / 2, 
    badgeViewFrame.origin.y + badgeViewFrame.size.height / 2, badgeViewFrame.size.height / 2, M_PI * 3 / 2, M_PI / 2, YES);
CGContextAddPath(context, path);
CGContextDrawPath(context, kCGPathFill);
CFRelease(path);
CGContextRestoreGState(context);

Step Four: Will It Blend?

Then we save the graphic state again, set the blending mode, and draw the text on top of the badge where we done previously. And since we use kCGBlendModeClear, the CGPath we previous drawn and filled with color will have text on top that can be seeing throguh clearly.

CGContextSaveGState(context);   
CGContextSetBlendMode(context, kCGBlendModeClear);
[self.cell.badgeText drawInRect:CGRectInset(badgeViewFrame, 7, 2) withFont:[UIFont boldSystemFontOfSize:13.]];
CGContextRestoreGState(context);

And below are for the summary and detail drawning, which is much easier compare with above.

[currentSummaryColor set];
[self.cell.summary drawInRect:CGRectMake(10., 10., badgeViewFrame.origin.x - 15., 22.) 
    withFont:[UIFont boldSystemFontOfSize:18.] lineBreakMode:UILineBreakModeTailTruncation];

[currentDetailColor set];
[self.cell.detail drawInRect:CGRectMake(10., 32., badgeViewFrame.origin.x - 15., 18.) 
    withFont:[UIFont systemFontOfSize:14.] lineBreakMode:UILineBreakModeTailTruncation];

Where to Download

You can download DDBadgeViewCell project from my github.

DDBadgeViewCell can be used on iPhone 3.1 or later, but the sample program was created on iPhone SDK 4.

References

  1. jonbaer reblogged this from digdog and added:
    DDBadgeViewCell is an UITableViewCell subclass with one badge view on the right. That badge view is customizable. You...
  2. digdog posted this