Showing posts with label UIButton. Show all posts
Showing posts with label UIButton. Show all posts

May 12, 2009

Multiple Buttons on a Navigation Bar

Previously, I wrote about how to use standard icons in your navigation bar. This takes advantage of the nav bar's ability to put a button on the right side. But what if you want to put multiple buttons there? If your title is short enough, you can easily fit 2 or 3 buttons in that space. The nav bar allows you to stick content on the right hand side via the rightBarButtonItem property. On the surface, you might think you can add but one button to this property. But it's not so. There is an initializer for UIBarButtonItem that takes a view:
initWithCustomView:(UIView *)customView
With this, you can create multiple UIButton objects, add them to a UIView, and then create a UIBarButtonItem passing in this parent UIView. The following pseudo-code shows what I mean:
// create the container
UIView* container = [[UIView alloc] init];

// create a button and add it to the container
UIButton* button = [[UIButton alloc] init...];
[container addSubview:button];
[button release];

// add another button
button = [[UIButton alloc] init...];
[container addSubview:button];
[button release];

// now create a Bar button item
UIBarButtonItem* item = [[UIBarButtonItem alloc] initWithCustomView:container];

// set the nav bar's right button item
self.navigationItem.rightBarButtonItem = item;
[item release];

But, what if you want multiple *standard* buttons. Access to the standard buttons/icons is through the UIBarButtonItem class, not UIButton. You can't add UIBarButtonItem objects to a UIView. While there is support to create a UIBarButtonItem object from a UIView-derived object, there does not seem to be a way to do the opposite; there is no way to create a UIView object from a UIBarButtonItem. The only way I found how to deal with multiple UIBarButtonItem objects is to stick them in a toolbar. A UIToolBar is designed to hold multiple UIBarButtonItem objects. Think of a toolbar as your container, instead of the UIView object. And since a UIToolBar is UIView based, it can be added to the right side of a nav bar using the above trick. The following code shows how you can add two standard buttons to the right side:

// create a toolbar to have two buttons in the right
UIToolbar* tools = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 133, 44.01)];

// create the array to hold the buttons, which then gets added to the toolbar
NSMutableArray* buttons = [[NSMutableArray alloc] initWithCapacity:3];

// create a standard "add" button
UIBarButtonItem* bi = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:NULL];
bi.style = UIBarButtonItemStyleBordered;
[buttons addObject:bi];
[bi release];

// create a spacer
bi = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace target:nil action:nil];
[buttons addObject:bi];
[bi release];

// create a standard "refresh" button
bi = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self action:@selector(refresh:)];
bi.style = UIBarButtonItemStyleBordered;
[buttons addObject:bi];
[bi release];

// stick the buttons in the toolbar
[tools setItems:buttons animated:NO];

[buttons release];

// and put the toolbar in the nav bar
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:tools];
[tools release];


April 15, 2009

Creating Stretchable Button Images

In a previous article, I described a couple techniques for putting buttons in a table. If you want your buttons to have a background color, and likely some kind of gradient, the trick is to use a custom UIButton. These are interesting because they can have an image that forms the background of the button, and this image can be stretched to fits the frame of the button. In this article I will describe how to create these stretchable button images that look nice in a table. I will demonstrate this using Inkscape, a wonderful vector drawing application that can be freely downloaded from here. What I will show is how to create an image that looks like this:
When this image is applied to a custom UIButton, with some text added, you get something like this:
This shows how the image is stretched in order to fit the button's frame.

Step 1. Starting Inkscape

The first step is to fire up Inkscape. We're only dealing with an image of around 100x40 pixels, so your canvas shouldn't be too big. Bring up the document properties (File>Document Properties) and set the custom size to something like 200 x 60 pixels. But this will make your canvas really small, so zoom in with View>Zoom>Page
Here is what your Inkscape window should look like:

Step 2. Drawing the Rectangle

Now we will use the rectangle tool to draw our image. I don't think the size of the image is terribly important, since Cocoa Touch will stretch it to fit. For this exercise, I will create an image roughly 90x40 pixels. Click on the rectangle and squares tool (F4). Now draw a rectangle on your canvas about 90x40 pixels in size. The size will be displayed in the status bar at the bottom and will adjust as you drag your mouse. Don't worry about fill or stroke color at this point; we'll adjust those later. Now you might have something like this (fill color uses whatever color you used last):

Step 3. Rounding the Corners

Time to make the rounded corners. If you don't see the little circle in the upper right corner of your rectangle, select the Edit Paths by Node tool (F2) and click on your rectangle. That should bring up the little circle. Now drag that circle down, which creates the rounded corners. Make them as round as you want, but I pulled the circle down about a ¼ of the way down:

Step 4. Filling with Color

Now we have to change the fill color. Bring up the Object>Fill and Stroke window. You may have to make the window a little larger to fit it on. Make sure the Fill tab is selected and click on the Flat Color icon at the top (the one to the right of the "X"). Now you can enter RGB values, or use the wheel, or any of the 5 methods to set the color just the way you want. I prefer to use the wheel to get to the general color, then switch over to RGB to fine tune it. For this example, I want a color with RGB values 0, 189, 0. Now my image looks like this:

Step 5. Coloring the Border

To set the stroke, you can make this whatever you want, but I prefer a stroke weight of 0.5 pixels. This allows the button to look more like a table row. This is set on the Stroke Style tab of the Fill and Stroke window. The stroke color is set on the Stroke Paint tab. I want a sort of gray color to match the table row borders. I think I am pretty close with these RGB values: 63, 118, 83:

Step 6. Adding the Glare

We're in the home stretch now, but this is the trickiest part: applying the gradient to give it that shining light affect. For this we will use the Create and edit gradients tool (above the eye-dropper tool on the left, or Ctrl-F1). Click in the middle of your image and drag the mouse up. This will create a gradient that gradually gets lighter as you go up. You can click on the endpoints and move them around to get your desired gradient affect:
You might want to add another point on the gradient line to give a more enhanced affect. To do this double click on the bottom handle. You'll see a dialog like this:
Click on the "Add stop" button, and this will add another handle on the gradient line in the middle. You can also drag this around. While the gradient editor is up, you can adjust the colors of each handle.
One problem is that the default settings for the gradient tool is to go towards a translucent color. This might look fine within Inkscape, but when you create a button with translucence, it will actually show the background through the button. This may or may not be a desired affect. If it isn't, you can change the color of each end of the gradient. Simply click on the handles and make sure the opacity is set to 100% and the color will now be solid. Unfortunately, this has the affect of hiding your gradient. Don't worry; just select each handle and adjust the colors the way you want them look. After fiddling with colors of each of the handles, I got an end product that looks like this:

Step 7. Saving your Work

Now you need to save it. This is a very confusing part of the Inkscape interface. When exporting a bitmap, Inkscape saves it as a PNG file, the perfect format for the iPhone. Select File>Export Bitmap. Make sure the Selection tab is selected and that the entire image is selected. Click on the Browse button. The browse button brings up another dialog where you select the directory and enter a filename. There is a save button on this dialog, but don't be fooled. This does not save the file. It really behaves like an Ok button, so go ahead and click it. Now you can actually save the file by hitting the Export button. Copy the resulting .png file into your XCode project, and use it as described in the article mentioned above. I hope this was clear and helpful.

April 9, 2009

Putting a Button in a Table

Sometime you may need to add a button to the contents of a UITableViewController. While you have the ability to put buttons in the navigation bar or in a toolbar at the bottom, it is sometimes best to simply put the button "inline" near your data. Sure, you could simply put a UIButton control right in a UITableCell object, but I have found the results of that not very attractive, especially in grouped tables. So what's the best way? I like to use the Apple applications as examples of good design. There are a couple Apple applications that put UIButtons in UITableViewControllers. Take a look at these examples. This one is from the mail settings, where there is a "Delete Account" button at the bottom of the table:
And this one is when you are editing a contact, where there is another "Delete" button at the bottom:
Although they are the same size of a single cell section, you can tell these are not normal rows. The giveaways are the rounded corners that don't quite match those of the neighboring rows. My guess is these are custom buttons, which stretch a background image to fit a frame that's the same size of a cell. But using UIButton controls is not the only way to get button behavior. You can simulate a button with just a basic grouped table cell; no UIButton at all. This is what I call cell-based buttons, for lack of a better term. I found a couple Apple applications that appear to use this technique. For example, here is the the settings from Safari:
That section of three rows in the middle contain cells where each operates as a button. The phone settings also uses this, with the "Change Voicemail Password" cell in the middle. There are probably other ways to get button functionality into your tables, but these two seem to be the most common. This article will explain the advantages and disadvantages of each approach, and I'll provide some code snippets that shows how to do them.

Cell-Based Buttons

Simulating a button in a cell has one big advantage: it's super easy to do. If you don't care about it looking fancy, or want to bunch up several "buttons" in one section, as the one above for editing a contact, then a cell-based button is a good option. For simple black on white text, all you have to do is set the cell.text property to your button text. Since the visual aspects of a table cell can be customized quite extensively, you really have lots of flexibility here. However, once you go beyond the standard UITableCell object, the complexity of your code increases. I believe that once you have gone beyond the basic black on white text, you might as well go with a real button.

Implementing

How a cell-based button works is that your code simply traps the tableView:didSelectRowAtIndexPath: message. You associate the section and row with which "button" was hit and perform whatever action that button is supposed to do. For example, from the image above, you can associate that block of buttons with section #1, and row #0 is the "Clear History" button, row #1 is "Clear Cookies", etc. One thing to remember is that the action of touching a cell automatically highlights the cell. Before you do anything else, you should turn off that highlight. Here is some example code of how we might implement the above section of three buttons. There are two aspects here: setting up the buttons for display, and handling the actions when the buttons are selected. Setting up for display. All of this is fairly boilerplate code for the tableView:cellForRow: message. The big difference here is that we are not fetching the row contents from a database, but hard-coding the button title. This can easily be internationalized as well.
- (UITableViewCell *)
tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

   static NSString *CellIdentifier = @"Cell";

   UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
   if (cell == nil) {
       cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
   }

   // are we setting up our button section?
   if(indexPath.section == 1) {
       switch(indexPath.row) {
           case 0: cell.text = @"Clear History"; break;
           case 1: cell.text =@"Clear Cookies"; break;
           case 2: cell.text = @"Clear Cache"; break;
       }
   }
   else {
       // do whatever for the other sections
   }

   return cell;
}
For cell-based buttons, you want to avoid using the accessory view, like putting in a detailed disclosure button or the checkmark. Having these items on a "button" will be confusing. Handling actions. All you do here is figure out which "button" was hit and do whatever it is you need to do. Also notice the code to turn the highlight off.
- (void)
tableView:(UITableView *) aTableView didSelectRowAtIndexPath:(NSIndexPath *) indexPath
{
   // remove the row highlight
   [aTableView deselectRowAtIndexPath:indexPath animated:YES];

   // Is this section our "button" section?
   if(indexPath.section == 1) {
       switch(indexPath.row) {
           case 0: [self doClearHistory]; break;
           case 1: [self doClearCookies]; break;
           case 2: [self doClearCache]; break;
       }

       return;
   }
   else {
       // do other stuff...
   }
}

Real Buttons

Real buttons offer all the advantages you get with a real button. For one thing, the size of the button can be fully customizable. This allows you to have two buttons side by side, as in this example from showing detailed info of a contact:
You can't have two table cells side by side like that, so these have to be buttons. You'll note that these rounded corners match the rounded corners of the table cells. I think these buttons use the normal RoundedRect button, as opposed to custom buttons. Custom buttons are mostly needed when you want a background color other than white. But if all you need are plain white backgrounds, you can use the RoundedRect buttons. Which leads to the other advantage of using real buttons: better control over background colors. Setting a background color in a grouped table cell is difficult. Just simply setting the background property to a color is not enough, as the rounded corners do not get the color. The only solution I've seen to this problem is a complicated bit of code to color in the curved corners (as demonstrated here). You also don't get the cool gradients as you see in the two red buttons on the top two examples.
There are probably other advantages to using real buttons, but to me these are enough to force me to use buttons sometimes.

Implementing

So how are UIButtons integrated into a UITableView? Again, there may be several ways, but I find the easiest is to sneak the buttons into a section header. Each section can have a header view. In this view is where you put your UIButton objects. The section itself has no data rows, only a header. So what I do is accommodate for one extra section in my table, but return 0 for the number of rows.
- (NSInteger)
numberOfSectionsInTableView:(UITableView *)aTableView
{
   // return the number of sections the data is organized in, plus 1 for the button
   return 2 + 1;
}
Assuming you want your button at the end of the table, you would use code like this to return the number of rows:
- (NSInteger)
tableView:(UITableView*) aTableView numberOfRowsInSection:(NSInteger) sectionNum
{
   if(sectionNum == 3) {
       // this is our button section
       return 0;
   }
   else {
       // do whatever, according to the data
   }
}
Now we have to create our buttons. This is done in the tableView:viewForHeaderInSection:
- (UIView *)
tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)sectionNum
{
   if(sectionNum == 2) {
       // create the parent view that will hold 1 or more buttons
       UIView* v = [[UIView alloc] initWithFrame:CGRectMake(10.0, 0.0, 300.0, 44.0)];
 
       // create the button object
       UIButton* b = [UIButton buttonWithType:UIButtonTypeCustom];
       [b setBackgroundImage:[[UIImage imageNamed:@"redbutton.png"] stretchableImageWithLeftCapWidth:12.0 topCapHeight:0.0] forState:UIControlStateNormal];
       [b setBackgroundImage:[[UIImage imageNamed:@"bluebutton.png"] stretchableImageWithLeftCapWidth:12.0 topCapHeight:0.0] forState:UIControlStateHighlighted];

       b.frame = CGRectMake(10.0, 0.0, 300.0, 44.0);
       [b setTitle:@"Button Title" forState:UIControlStateNormal];

       // give it a tag in case you need it later
       b.tag = 1;

       // this sets up the callback for when the user hits the button
       [b addTarget:self action:@selector(action:) forControlEvents:UIControlEventTouchUpInside];
     
       // add the button to the parent view
       [v addSubview:b];

       return [v autorelease];
   }
   else {
       // stuff for other sections
   }
}

The values for the frames are specific to portrait mode; landscape mode will need some modifications. To add more buttons in this view, just adjust the y parameter in the frame for the button. I typically add 46.0 from the last one. This code makes use of two button images:

I got these from user "hakimny" from this thread. Creating these aren't hard. (I have posted an article on how to create simple background images like this in Inkscape.)
If you want to create a button with a white background, then you don't need to create a custom button. You can create a RoundedRect button that will look more like the surrounding cells, like you saw above. In this case, your button creation is simply:
b = [UIButton buttonWithType:UIButtonTypeRoundedRect]; 
You don't need to set the background images. If you want to create two side by side buttons, all you have to do is monkey around with the frames of the buttons so that they are side by side instead of on top of each other. There is one gotcha I have discovered using this mechanism, and I think it is an SDK bug. If your table takes up more than one screen and you have to scroll to see your button, some of the button may remain hidden under the tab bar or tool bar. You won't be able to scroll the entire header view into view. This is especially noticeable if you have 2 or more buttons stacked on top of each other. I think Cocoa Touch is confused by there being no data rows, or maybe it can't quite figure out how high the header view is. So the way around it is to tell how high your view is. Once you do this, the view is no longer partially hidden:
- (CGFloat)
tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)sectionNum
{
   return 46.0;
}

Of course, if you have more buttons in this section, you would return the appropriate height. There you have a quick tutorial on putting buttons in a table. How you choose which mechanism to use is up to you. My own guidelines are as follows:
  1. If I want a simple black on white button: use a cell-based button
  2. If I need a colored button: use a real button
  3. If I want multiple buttons per row: use real buttons