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.
- 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.
- In IB, link the property to the default button.
- 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.
- 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;
} - 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
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