Generally, the iPhone SDK is not that bad, however they left out an important feature that app developers really need - the ability to change the color of navigation buttons. Navigation buttons live on the very top of the application and are usually there to navigation backwards and forwards through views. Sometimes, though, they're used as confirmation or to save data that has been entered. This is when they need a different color. This tutorial is going to demonstrate how to create and place a blue button in the navigation controller.
A good example of a blue navigation button occurs when adding a new contact. When you start editing contact information, the Save button on the top-right corner of the app turns blue to let you know you're supposed to click that when you're finished.
Whereas the iPhone SDK lets you change the color of the entire navigation controller to anything you want, you can't change the color of an individual button. And unfortunately, getting the job done ourselves is a very tedious task.
There is really nothing built in to help us out here, so we need to start by creating an object to encapsulate the blue button. I called my object, BlueButton.
BOOL landscape;
}
-(id)init;
@property BOOL landscape;
@end
As you can see, there's really not much going on here. BlueButton extends UIButton, so whoever is using this control can set the text and attach to events just as they would any ordinary button. Buttons in the navigation bar are different heights depending on the layout of the screen - landscape or portrait, so I added a property that an owning UIViewController would set whenever the orientation changes.
Now let's jump into the implementation of init. This is where the bulk of the magic happens.
if(self = [super init]) {
// The default size for the save button is 49x30 pixels
self.frame = CGRectMake(0, 0, 49.0, 30.0);
// Center the text vertically and horizontally
self.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
self.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
UIImage *image = [UIImage imageNamed:@"blueButton.png"];
// Make a stretchable image from the original image
UIImage *stretchImage =
[image stretchableImageWithLeftCapWidth:15.0 topCapHeight:0.0];
// Set the background to the stretchable image
[self setBackgroundImage:stretchImage forState:UIControlStateNormal];
// Make the background color clear
self.backgroundColor = [UIColor clearColor];
// Set the font properties
[self setTitleShadowColor:[UIColor blackColor] forState:UIControlStateNormal];
self.titleShadowOffset = CGSizeMake(0, -1);
self.font = [UIFont boldSystemFontOfSize:13];
}
return self;
}
We're setting up a very basic button here. First I give it a frame that I default to size of the iPhone's 'save' button. Someone using the control can easily override this frame if needed. Next I make sure the text will be centered both vertically and horizontally.
The next stuff is probably the most confusing. I'm using a stretchable image so I can use a single background image and make the button any width I want. The stretchableImageWithLeftCapWidth property tells the button it can stretch the image all it wants, but leave 15 pixels unaffected, which will be where the rounded corners on the button are. If we didn't set this property, it would stretch the image uniformly, which will warp the rounded corners.
Now you're probably wondering where to get the background image. Well, unfortunately you're going to have to make it. I used the simulator to get a screenshot to start with and used Photoshop for the rest. As I mentioned earlier, there are two sizes of buttons required - one 30 pixels tall and one 24 pixels tall.
The navigation controller in my app is black, so these buttons are designed to work on that. They'll probably look ok on other colors, but you'll have to check and see. If not, you can always tweak them in Photoshop. Apple released a button similar to these in the UICatalog, however it was designed for the larger buttons located on views instead of in the navigation bar.
Ok, back to some code. If you remember, there is also a landscape property that changes the button to work in landscape mode. Here's the guts of the setter for that property.
UIImage *image;
CGRect frame = self.frame;
if(value) {
image = [UIImage imageNamed:@"blueButtonSmall.png"];
frame.size.height = 24;
}
else {
image = [UIImage imageNamed:@"blueButton.png"];
frame.size.height = 30;
frame.origin.y -= 3;
}
UIImage *stretchImage =
[image stretchableImageWithLeftCapWidth:15.0 topCapHeight:0.0];
self.frame = frame;
[self setBackgroundImage:stretchImage forState:UIControlStateNormal];
}
The first thing I do is set the correct background based on orientation. There's an interested quirk that I needed in my app to make sure the button was always centered vertically when switching between landscape and portrait. When the orientation is changed from landscape back to portrait, the navigation already re-centers the button based on the 24 pixel tall button. Then, when I switch it out for the 30 pixel button, it now sits 3 pixels too low, so I need to move it up where it belongs.
All right now let's look at how to actually use this thing. You can run the following code whenever you want to add a blue button, but I put mine in the UIViewController's viewDidLoad function since I wanted it there when the view was loaded.
[blueSaveButton setTitle:@"Add" forState:UIControlStateNormal];
[blueSaveButton addTarget:self action:@selector(addSite)
forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *button = [[UIBarButtonItem alloc]
initWithCustomView:blueSaveButton];
self.navigationItem.rightBarButtonItem = button;
[button release];
[blueSaveButton release];
The first three lines are basic button initialization code. Using initWithCustomView gives you the ability to create a navigation button from any view you want - in this case our blue button. You then simply set self.navigationItem.rightBarButtonItem to the new button and you're done. Here's what this code gives you:
Well there you go. They didn't make it easy, but it is doable. You can download the blue button images I created below and use them in you're own app if you want. If you've got questions, feel free to leave them below.
08/23/2009 - 11:16
Thanks for the tutorial. My question is a little off-topic, but I can't find the answer anywhere, and this tutorial almost touches on the issue.
My app works in portrait, with a navigation bar. I want to have a horizontal navigation bar (readable with the device in landscape mode) when the iPhone is turned to landscape, and the navigation bar thinner, according to the HIG.
When I detect an orientation change to landscape, I run a CGAffine transform on the navigationBar so that it will be *readable* in landscape mode. Fine. I change its frame to place it along the long axis of the device, and make it thinner. Fine. But the title text and back button won't scale to fit the new navigationBar after the transform. They are too big, and sitting partially off of the bar.
The transform seems to mess things up. How do I rotate a navigation bar to landscape mode while making it thinner and scaling the items in the bar to the new thinner size? Even a link to useful info would be greatly appreciated.
09/02/2009 - 05:17
Can you not just use UIBarButtonItemStyleDone instead of UIBarButtonItemStylePlain? This makes your button blue.
09/02/2009 - 09:32
I definitely remember setting that in Interface Builder and it having no affect. There might be some way to do it in code. I'll have to investigate further.
09/02/2009 - 11:14
bbItem = [[UIBarButtonItem alloc] initWithTitle:aString style:UIBarButtonItemStyleDone target:nil action:nil];
09/02/2009 - 11:40
there is also a UIButtonTypeContactAdd type of UIButton, but I haven't used it
09/02/2009 - 16:34
Cool! Great!
10/21/2009 - 11:03
Thank You. It's been very helpful.
11/30/2009 - 07:19
Have you seen ButtonFactory on the iPhone Appstore, cool app for creating buttons
12/01/2009 - 14:03
Hi,
First of all, thanks for share your code.
I developed similar solution, but I have a problem, when i use this solution in a button added to any UIView, it runs perfectly. But, if I add it to a bar button (f.s. right bar button)
UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithCustomView:blueSaveButton];
the button doesn't resize automatically, it maintains the initialization frame (CGRect) size.
I have the same problem, even in when adding to UIView, if I create the blueSaveButton with buttonWithType:UIButtonTypeCustom
Any idea to solve the problem ¿?
Thanks a lot!
Ivan
01/19/2010 - 09:06
Thanks, good explanation.
01/25/2010 - 22:31
Thanks for the tutorial! Just a minor tweak, I get a closer match to the native buttons using a slightly smaller gray shadow. Also, as of OS 3.0, the shadow and font properties are deprecated. Here's the up-to-date code if anyone is interested:
[self setTitleShadowColor:[UIColor grayColor] forState:UIControlStateNormal];
self.titleLabel.shadowOffset = CGSizeMake(0, -0.75);
self.titleLabel.font = [UIFont boldSystemFontOfSize:13];
Add Comment
[language] [/language]
Examples:
[javascript] [/javascript]
[actionscript] [/actionscript]
[csharp] [/csharp]
See here for supported languages.
Javascript must be enabled to submit anonymous comments - or you can login.