Ad

Our DNA is written in Swift
Jump

Custom-Colored Disclosure Indicators

Sometimes you may want to have a different background color on your table views than Apple White. One problem you will most likely be facing is that this makes it impossible to use the regular table view cell accessories. Black arrows on black background are kinda hard to see.

You will have to draw your own. And in this post I’m going to show you how I did it.

Just today I discovered a mechanism that makes it even easier to roll your own custom-colored accessory view. The property name containing the word “view” is somewhat misleading in this case. You might be tempted to create your own UIView subclass and override the drawRect to draw there. This has one disadvantage though, you cannot switch colors when the cell gets highlighted.

Update Feb 1, 2013: DTCustomColoredAccessory presented in this article is now part of our Open Source DTFoundation and covered under a 2-clause BSD license. You can either uses it with attribution for free or you can purchase a non-attribution license in our parts store.

UIView does not possess the properties selected , enabled and highlighted. For these you have to turn to UIControl which does. And I discovered by trying it out that if you don’t use a UIView for your custom-colored accessory, but UIControl instead, then these properties get happily triggered by the table view itself.  All you have to do is choose the correct drawing color based on the current state.

Here is my implementation of a disclosure indicator that can have any color for the regular and the highlighted state.

DTCustomColoredAccessory.h

@interface DTCustomColoredAccessory : UIControl
{
	UIColor *_accessoryColor;
	UIColor *_highlightedColor;
}
 
@property (nonatomic, retain) UIColor *accessoryColor;
@property (nonatomic, retain) UIColor *highlightedColor;
 
+ (DTCustomColoredAccessory *)accessoryWithColor:(UIColor *)color;
 
@end

DTCustomColoredAccessory.m

#import "DTCustomColoredAccessory.h"
 
@implementation DTCustomColoredAccessory
 
- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
		self.backgroundColor = [UIColor clearColor];
    }
    return self;
}
 
- (void)dealloc
{
	[_accessoryColor release];
	[_highlightedColor release];
    [super dealloc];
}
 
+ (DTCustomColoredAccessory *)accessoryWithColor:(UIColor *)color
{
	DTCustomColoredAccessory *ret = [[[DTCustomColoredAccessory alloc] initWithFrame:CGRectMake(0, 0, 11.0, 15.0)] autorelease];
	ret.accessoryColor = color;
 
	return ret;
}
 
- (void)drawRect:(CGRect)rect
{
	// (x,y) is the tip of the arrow
	CGFloat x = CGRectGetMaxX(self.bounds)-3.0;;
	CGFloat y = CGRectGetMidY(self.bounds);
	const CGFloat R = 4.5;
	CGContextRef ctxt = UIGraphicsGetCurrentContext();
	CGContextMoveToPoint(ctxt, x-R, y-R);
	CGContextAddLineToPoint(ctxt, x, y);
	CGContextAddLineToPoint(ctxt, x-R, y+R);
	CGContextSetLineCap(ctxt, kCGLineCapSquare);
	CGContextSetLineJoin(ctxt, kCGLineJoinMiter);
	CGContextSetLineWidth(ctxt, 3);
 
	if (self.highlighted)
	{
		[self.highlightedColor setStroke];
	}
	else
	{
		[self.accessoryColor setStroke];
	}
 
	CGContextStrokePath(ctxt);
}
 
- (void)setHighlighted:(BOOL)highlighted
{
	[super setHighlighted:highlighted];
 
	[self setNeedsDisplay];
}
 
- (UIColor *)accessoryColor
{
	if (!_accessoryColor)
	{
		return [UIColor blackColor];
	}
 
	return _accessoryColor;
}
 
- (UIColor *)highlightedColor
{
	if (!_highlightedColor)
	{
		return [UIColor whiteColor];
	}
 
	return _highlightedColor;
}
 
@synthesize accessoryColor = _accessoryColor;
@synthesize highlightedColor = _highlightedColor;
 
@end

To use this in a tableview cell, all you need to do is this:

// in cellForRowAtIndexPath:
DTCustomColoredAccessory *accessory = [DTCustomColoredAccessory accessoryWithColor:cell.textLabel.textColor];
accessory.highlightedColor = [UIColor blackColor];
cell.accessoryView =accessory;

After discovering the thing about UIControl and highlighted, I modified it to also take a highlight color. Having this handy saved me quite some time today when I implemented a white-on-black look for a mock up app I’ve been contracted to produce.


Categories: Recipes

11 Comments »

  1. Hi, came across your post & wanted to say thanks. One thing, I am wondering how one might mimic

    – (void)tableView: (UITableView *)tableView accessoryButtonTappedForRowWithIndexPath: (NSIndexPath *)indexPath

    given that it appears this method is not called when using either an accessory view or your custom accessory.

  2. well, then you have to have touch handling on your own accessory. If it’s a button, then addTarget:selector:

  3. thank you very much

  4. Awesome thanks for this!

  5. nice addition – thanks. I tweaked a little so that it can do checkmarks too. Just add a BOOL property for check:

    + (CustomColouredDisclosure *)accessoryWithColor:(UIColor *)color
    {
    CustomColouredDisclosure *ret = [[[CustomColouredDisclosure alloc] initWithFrame:CGRectMake(0, 0, 15.0, 15.0)] autorelease];
    ret.accessoryColor = color;

    return ret;
    }

    – (void)drawChevron:(CGRect)rect
    {
    // (x,y) is the tip of the arrow
    CGFloat x = CGRectGetMaxX(self.bounds)-5.0;
    CGFloat y = CGRectGetMidY(self.bounds);
    const CGFloat R = 4.5;
    CGContextRef ctxt = UIGraphicsGetCurrentContext();
    CGContextMoveToPoint(ctxt, x-R, y-R);
    CGContextAddLineToPoint(ctxt, x, y);
    CGContextAddLineToPoint(ctxt, x-R, y+R);
    CGContextSetLineCap(ctxt, kCGLineCapSquare);
    CGContextSetLineJoin(ctxt, kCGLineJoinMiter);
    CGContextSetLineWidth(ctxt, 3);

    if (self.highlighted)
    {
    [self.highlightedColor setStroke];
    }
    else
    {
    [self.accessoryColor setStroke];
    }

    CGContextStrokePath(ctxt);
    }

    – (void)drawCheck:(CGRect)rect
    {
    // (x,y) is the tip of the arrow
    CGFloat x = CGRectGetMaxX(self.bounds)-3.0;;
    CGFloat y = 3;
    const CGFloat R = 3.5;
    CGContextRef ctxt = UIGraphicsGetCurrentContext();
    CGContextMoveToPoint(ctxt, x, y);
    CGContextAddLineToPoint(ctxt, x-1.8*R, y+3*R);
    CGContextAddLineToPoint(ctxt, x-2.7*R, y+2*R);
    CGContextSetLineCap(ctxt, kCGLineCapRound);
    CGContextSetLineJoin(ctxt, kCGLineJoinRound);
    CGContextSetLineWidth(ctxt, 3);

    if (self.highlighted)
    {
    [self.highlightedColor setStroke];
    }
    else
    {
    [self.accessoryColor setStroke];
    }

    CGContextStrokePath(ctxt);
    }

    – (void)drawRect:(CGRect)rect
    {
    if (self.check)
    {
    [self drawCheck:rect];
    }
    else
    {
    [self drawChevron:rect];
    }
    }

  6. Thanks a lot for this.

  7. Is your code here covered by a free software license? If not, please consider adding a license to the code in this post. For example: http://opensource.org/licenses/MIT

  8. I updated the post to reflect that DTCustomColoredAccessory had been a member of DTFoundation since February 2013 and there it is covered by a BSD license.