iPhone Gradient Buttons, with Highlighting


My app- Lyrics– is pretty simple graphically. The only real object we have is a button. So, it’s nice to get the most out of my designer, and he has repeatedly been a fan of gradient buttons with a different visual gradient when pressed (highlighted in iPhone talk). While iPhone is great for a lot of things, just right out of the box, adjusting the default button style tends to be very difficult. We also did this in Android.

Normal

Highlighted

Subclass UIButton to use Highlight method

Subclass UIButton. Do this first so the subclass is available in Interface Builder. I put some layer properties common to all buttons in the initialization of the object. This is just a snippet- here’s a full zip.

-(void)initLayers{
// Initialization code here.
[self setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[[self layer] setCornerRadius:7.0f];
[[self layer] setMasksToBounds:YES];
[[self layer] setBorderWidth:1.0f];

}

The main reason I subclassed though – instead of doing it all in the view class – was to use the “setHighlight” method. This uses the button’s nice default click animation, and you can give it instructions during a highlight. Here, I flip the two layers I’ve made. One is a darker gradient layer than the other.


- (void)setHighlighted:(BOOL)highlight {
if (highlight != self.highlighted){
CAGradientLayer *bL = [[[self layer] sublayers] objectAtIndex:1];
[bL setHidden:![bL isHidden]];
}

[self setNeedsDisplay];
[super setHighlighted:highlight];
}

@end


Create your XIB, select the button object. Set the class to “lyrButton” (or whatever you name your subclass).


Set the button as:

  • Clear/transparent background
  • Custom
  • Highlight reverses
  • Assign tags to them, starting with 1. We will create a “btns” array that stores the kind of buttons we have- for me, “rec”,”play”,”stop”, etc.


Do this for as many buttons as you want.

Setup Buttons in View Class

I chose to setup the buttons in my main view class, because I do this as a batch, really related to the view and not individual buttons. Also, I establish a big colors array and only want to do that once. You could break this up even more and put in the subclass if you want.

  1. Setup a button property. I use this to take XIB settings and leverage them in the code (instead of doing entire button programmatically). We get our button bounds, for example, from this default button.
  2. In IB, link the property to the default button.
  3. Create your color array- I used a dictionary, with keys the represent the type of button (i.e. “rec”), the dark/light type of color it is, and whether it’s a highlight or “normal” layer. This was the hardest part to keep right, and we used a Google doc with the designer to double-check the colors.
  4. Build a “create gradient” layer method, pass into it the button type and which layer (highlight or normal).

    -(CAGradientLayer *)setupGradientButton:(NSString *)btnType gradientType:(NSString *)gType {

    CAGradientLayer *gradientLayer = [CAGradientLayer layer];

    // use that btn property for its bounds, easier and more flexible this way
    gradientLayer.frame = btn.layer.bounds;

    [gradientLayer setLocations:[NSArray arrayWithObjects:
    [NSNumber numberWithFloat:0.0f],
    [NSNumber numberWithFloat:0.7f],
    nil]
    ];

    // our colors dictionary has this composite key- button type, high/low color, and layer (highlight
    // or "normal")
    NSString *low_key = [NSString stringWithFormat:@"%@_low_%@",btnType,gType];
    NSString *high_key = [NSString stringWithFormat:@"%@_high_%@",btnType, gType];

    // use gradient layer's "colors" method to let QuartzCore build the gradient
    [gradientLayer setColors:
    [NSArray arrayWithObjects:
    (id)[[colors objectForKey:high_key] CGColor],
    (id)[[colors objectForKey:low_key] CGColor],
    nil]];

    return gradientLayer;
    }

  5. Create a “setupButtons” method, to loop through the view, grab each subclassed button, and apply the gradient layers. I also grab the appropriate border color (from the colors array) for each button and apply it here. It gives it that extra polished look.

    -(void)setupButtons{

    // keep track of which buttons are which in our loop
    // to build the appropriate gradient color
    NSArray *btns = [[NSArray alloc] initWithObjects:@"",@"rec",@"play",@"stop",@"share",nil];
    for(UIButton *bt in [[self view] subviews ]) {

    // tags are the index in the array of which button is which, best way to
    // integrate with interface builder
    if([bt tag] > 0){

    // build each gradient and plop into button, 0 is bottom/not visible, 1 is visible
    // the subclassed "highlight" method flips this order

    [[bt layer] insertSublayer:[self setupGradientButton:[btns objectAtIndex:[bt tag]] gradientType:@"high"] atIndex:0];
    [[bt layer] insertSublayer:[self setupGradientButton:[btns objectAtIndex:[bt tag]] gradientType:@"normal"] atIndex:1];

    // add a border color specific to this kind of button, get it from colors array
    // using the key, "[type]_border"
    NSString *border_key=[NSString stringWithFormat:@"%@_border",[btns objectAtIndex:[bt tag]]];
    [[bt layer] setBorderColor:[[colors objectForKey:border_key] CGColor]];

    }
    [btns release];
    }

    }

– Update 6/5, thanks @domsware, tips on convenience methods and the !hidden in the layer switcheroo.

Code

Download Zip

rootViewController header file:

//
// RootViewController.h
// test3
//
// Created by Anna Billstrom on 5/25/12.
// Copyright 2012 banane.com. All rights reserved.
//

#import
#import

@interface RootViewController : UIViewController {

IBOutlet UIButton *btn;
NSDictionary *colors;
}

@property (nonatomic,retain) IBOutlet UIButton *btn;
@property (nonatomic,retain) NSDictionary *colors;

-(void)setupColors;
-(void)setupButtons;
-(CAGradientLayer *)setupGradientButton:(NSString *)btnType gradientType:(NSString *)gType;

@end

rootViewController.m implementation file:


//
// RootViewController.m
// test3
//
// Created by Anna Billstrom on 5/25/12.
// Copyright 2012 banane.com. All rights reserved.
//

#import "RootViewController.h"
#import "myButton.h"

@implementation RootViewController

@synthesize colors,btn;

- (void)viewDidLoad
{
// do once on build
[self setupColors];
[self setupButtons];
[super viewDidLoad];

}

-(void)setupButtons{

// keep track of which buttons are which in our loop
// to build the appropriate gradient color
NSArray *btns = [[NSArray alloc] initWithObjects:@"",@"rec",@"play",@"stop",@"share",nil];
for(UIButton *bt in [[self view] subviews ]) {

// tags are the index in the array of which button is which, best way to
// integrate with interface builder
if([bt tag] > 0){

// build each gradient and plop into button, 0 is bottom/not visible, 1 is visible
// the subclassed "highlight" method flips this order

[[bt layer] insertSublayer:[self setupGradientButton:[btns objectAtIndex:[bt tag]] gradientType:@"high"] atIndex:0];
[[bt layer] insertSublayer:[self setupGradientButton:[btns objectAtIndex:[bt tag]] gradientType:@"normal"] atIndex:1];

NSString *border_key=[NSString stringWithFormat:@"%@_border",[btns objectAtIndex:[bt tag]]];
[[bt layer] setBorderColor:[[colors objectForKey:border_key] CGColor]];
}
}
[btns release];

}
-(CAGradientLayer *)setupGradientButton:(NSString *)btnType gradientType:(NSString *)gType {

CAGradientLayer *gradientLayer = [CAGradientLayer layer];

// use that btn property for its bounds, easier and more flexible this way
gradientLayer.frame = btn.layer.bounds;

[gradientLayer setLocations:[NSArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:0.7f],
nil]
];

// our colors dictionary has this composite key- button type, high/low color, and layer (highlight
// or "normal")
NSString *low_key = [NSString stringWithFormat:@"%@_low_%@",btnType,gType];
NSString *high_key = [NSString stringWithFormat:@"%@_high_%@",btnType, gType];

// use gradient layer's "colors" method to let QuartzCore build the gradient
[gradientLayer setColors:
[NSArray arrayWithObjects:
(id)[[colors objectForKey:high_key] CGColor],
(id)[[colors objectForKey:low_key] CGColor],
nil]];

return gradientLayer;
}

-(void)setupColors{

// this is a more complicated interface and design, but very pleasing
// different palate colors for each state, high and low
// high is a lighter shade than low

colors = [[NSDictionary alloc] initWithObjectsAndKeys:
[UIColor colorWithRed:0.79 green:0.45 blue:0.57 alpha:1.0], @"rec_low_normal"
,[UIColor colorWithRed:0.95 green:0.53 blue:0.69 alpha:1.0], @"rec_high_normal"
,[UIColor colorWithRed:0.47 green:0.27 blue:0.35 alpha:1.0], @"rec_low_high"
,[UIColor colorWithRed:0.57 green:0.33 blue:0.42 alpha:1.0], @"rec_high_high"
,[UIColor colorWithRed:0.39 green:0.51 blue:0.30 alpha:1.0], @"play_low_normal"
,[UIColor colorWithRed:0.49 green:0.65 blue:0.38 alpha:1.0], @"play_high_normal"
,[UIColor colorWithRed:0.33 green:0.44 blue:0.36 alpha:1.0], @"play_low_high"
,[UIColor colorWithRed:0.39 green:0.51 blue:0.30 alpha:1.0], @"play_high_high"
,[UIColor colorWithRed:0.49 green:0.50 blue:0.50 alpha:1.0], @"stop_low_normal"
,[UIColor colorWithRed:0.57 green:0.59 blue:0.59 alpha:1.0], @"stop_high_normal"
,[UIColor colorWithRed:0.39 green:0.39 blue:0.39 alpha:1.0], @"stop_low_high"
,[UIColor colorWithRed:0.50 green:0.51 blue:0.51 alpha:1.0], @"stop_high_high"
,[UIColor colorWithRed:0.44 green:0.63 blue:0.65 alpha:1.0], @"share_low_normal"
,[UIColor colorWithRed:0.62 green:0.80 blue:0.82 alpha:1.0], @"share_high_normal"
,[UIColor colorWithRed:0.24 green:0.32 blue:0.33 alpha:1.0], @"share_low_high"
,[UIColor colorWithRed:0.40 green:0.54 blue:0.56 alpha:1.0], @"share_high_high"
, nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}

- (void)viewDidUnload
{
btn =nil;
colors=nil;
[super viewDidUnload];

}

- (void)dealloc
{
[btn release];
[colors release];

[super dealloc];
}

@end