May 27, 2010

iPhone: UISegmentedControl with custom colors

[UPDATE]: We had an app approved with this code. Keep in mind that Apple has very inconsistent policies regarding app approval, but it's looking good for now.

Tasked with building a UISegmentedControl with different colors for selected/unselected buttons, I created a subclass that accomplishes this by digging through the subviews of the segmented control. I can't verify that this will get approved by the mysterious app store process, nor can I say it will function properly if you're using all of the built-in functions of UISegmentedControl (adding/removing segments dynamically will break this code). But for a simple case, it's working well for my purposes. Here we go:

CustomSegmentedControl.h :
#import 


@interface CustomSegmentedControl : UISegmentedControl {
 
 UIColor *offColor;
 UIColor *onColor;
 
 BOOL hasSetSelectedIndexOnce;
}

-(id)initWithItems:(NSArray *)items offColor:(UIColor*)offcolor onColor:(UIColor*)oncolor;
-(void)setInitialMode;
-(void)setToggleHiliteColors;

@end

CustomSegmentedControl.m :
#import "CustomSegmentedControl.h"


@implementation CustomSegmentedControl


-(id)initWithItems:(NSArray *)items offColor:(UIColor*)offcolor onColor:(UIColor*)oncolor {
 if (self = [super initWithItems:items]) {
        // Initialization code
  offColor = [offcolor retain];
  onColor = [oncolor retain];
  hasSetSelectedIndexOnce = NO;
  [self setInitialMode];
  [self setSelectedSegmentIndex:0];  // default to first button, or the coloring gets all whacked out :(
    }
    return self;
}

-(void)setInitialMode
{
 // set essential properties
 [self setBackgroundColor:[UIColor clearColor]];
 [self setSegmentedControlStyle:UISegmentedControlStyleBar];
 
 // loop through children and set initial tint
 for( int i = 0; i < [self.subviews count]; i++ )
 {
  [[self.subviews objectAtIndex:i] setTintColor:nil];
  [[self.subviews objectAtIndex:i] setTintColor:offColor];
 }
 
 // listen for updates
 [self addTarget:self action:@selector(setToggleHiliteColors) forControlEvents:UIControlEventValueChanged];
}

-(void)setToggleHiliteColors
{
 // get current toggle nav index
 int index = self.selectedSegmentIndex;
 int numSegments = [self.subviews count];
 
 for( int i = 0; i < numSegments; i++ )
 {
  // reset color
  [[self.subviews objectAtIndex:i] setTintColor:nil];
  [[self.subviews objectAtIndex:i] setTintColor:offColor];
 }
 
 if( hasSetSelectedIndexOnce )
 {
  // this is super weird - the subviews array is backwards... so deal with it like that
  [[self.subviews objectAtIndex: numSegments - 1 - index] setTintColor:onColor];
 }
 else
 {
  // ...but the very first time, they're the expected order :-/
  [[self.subviews objectAtIndex: index] setTintColor:onColor];
  hasSetSelectedIndexOnce = YES;
 }

}


@end 
And to initialize :
NSArray *toggleItems = [[NSArray alloc] initWithObjects:@"One",@"Two",@"Three",nil];
CustomSegmentedControl *toggleNav = [[CustomSegmentedControl alloc] initWithItems:toggleItems offColor:[UIColor blackColor] onColor:[UIColor redColor] ];
[toggleNav addTarget:self action:@selector(handleToggleNav:) forControlEvents:UIControlEventValueChanged];
[toggleNav setFrame:CGRectMake(52, 8, 211, 25)]; 
[self.view addSubview:toggleNav];
[toggleNav release];

Otherwise, follow the documentation for a UISegmentedControl, and enjoy.

iPhone: Bug in UITextField component - setTextColor not working

I'm working on a form for an iPhone app that does real-time form validation to let the user know if they're trying to create an account with a username that already exists. To let the user know that a username is already taken, I set the UITextField textColor property to red:
#define kColorRedError [UIColor colorWithRed:1 green:0 blue:0 alpha:1.0]
//... UITextField *username = [[UITextField alloc] initWithFrame:CGRectMake(12, 14, 296, 30)];
[username setTextColor:kColorRedError];
This wouldn't update until I typed another letter into the UITextField, so quite often it would display an error when it shouldn't, and vice versa. I tried using setNeedsDisplay and some other bits of code to try to force a display update after setting the text color. Nothing worked, until I tried this:
[password setTextColor:kColorRedError];
username.text = username.text;
...Quite absurd, but forces a redraw on the component. Gotta love those Apple components ;)