Shiny Red Buttons (4)

Test resultsToday, we package the gloss gradient and rounded rect code we developed on Monday and Friday into a UIButton category, and take a quick look at adding labels to these buttons; the latter discussion is much simpler than I had anticipated.

Code

Without further ado, here are the header and implementation files for the Glossy category of UIButton. In addition to the code we’ve seen previously, I added the following top-level supervisor/convenience function:

- (void)setBackgroundToGlossyRectOfColor:(UIColor*)color withBorder:(BOOL)border forState:(UIControlState)state
{
	static const float MIN_SIZE = 4;
	
	// Get and check size
	CGSize size = self.frame.size;
	if ((size.width < MIN_SIZE) || (size.height < MIN_SIZE)) return;
	
	// Create and get a pointer to context
	UIGraphicsBeginImageContext(size);
	CGContextRef context = UIGraphicsGetCurrentContext();
	
	// Convert co-ordinate system to Cocoa's (origin in UL, not LL)
	CGContextTranslateCTM(context, 0, size.height);
	CGContextConcatCTM(context, CGAffineTransformMakeScale(1, -1));
	
	// Set stroke color
	CGContextSetStrokeColorWithColor(context, [[UIColor colorWithRed:159.0/255 green:159.0/255 blue:159.0/255 alpha:1] CGColor]);
	
	// Draw background image
	if (border)
	{
		// Draw border
		[UIButton setPathToRoundedRect:CGRectMake(0.5, 0.5, size.width-1, size.height-1) forInset:0 inContext:context];
		CGContextStrokePath(context);
		
		// Prepare clipping region
		[UIButton setPathToRoundedRect:CGRectMake(1, 1, size.width-2, size.height-2) forInset:1 inContext:context];
		CGContextClip(context);

		// Draw glossy image
		[UIButton drawGlossyRect:CGRectMake(1, 1, size.width-2, size.height-2) withColor:color inContext:context];
	}
	else
	{
		// Prepare clipping region
		[UIButton setPathToRoundedRect:CGRectMake(0, 0, size.width, size.height) forInset:0 inContext:context];
		CGContextClip(context);
		
		// Draw glossy image
		[UIButton drawGlossyRect:CGRectMake(0, 0, size.width, size.height) withColor:color inContext:context];
	}

	// Create and assign image
	[self setBackgroundImage:UIGraphicsGetImageFromCurrentImageContext() forState:state];
	
	// Release image context
	UIGraphicsEndImageContext();
}

Notes

I suppose a few general remarks are in order:

  • setBackgroundToGlossyRectOfColor:withBorder:forState: creates and sets a background image for a button appropriate for the button’s current size. This implies two things: First, that the button must have a current size (specifically, both its width and height must be greater than or equal to 4) and second, that things probably won’t work right if the button is subsequently resized. I feel awful about that, but that’s what you get when you take free code from the Internet.
  • The border argument will cause a simple, 1-pixel rounded rect to be drawn around the perimeter of the button.
  • Two methods (the setPathToRoundedRect:forInset:inContext: and drawGlossyRect:withColor:inContext: functions) are still exposed as class methods of UIButton; I believe they have sufficient general utility to merit this.
  • In choosing how to present this functionality, I tried to strike a balance between convenience, generality, and symmetry. Since I couldn’t subclass UIButton, I chose not to hide the fact that the glossy effect is accomplished with a simple background image. This approach is only slightly wordier than a hypothetical setTint approach, allows the caller to manipulate the generated image, and avoids any ugliness with some of the more exotic UIButtonTypes.

Test Code

Here is some case code; it’s designed to be added to a View Controller (in my case, a View Controller that was pushed onto a Navigation Controller’s stack, but probably any VC will do).

- (UIButton*)shinyButtonWithWidth:(NSUInteger)width height:(NSUInteger)height color:(UIColor*)color
{
	// Create button (in UL corner of client area)
	UIButton* button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
	button.frame = CGRectMake(0, 0, width, height);

	// Configure background image(s)
	[button setBackgroundToGlossyRectOfColor:color withBorder:YES forState:UIControlStateNormal];
	[button setBackgroundToGlossyRectOfColor:[UIColor grayColor] withBorder:YES forState:UIControlStateHighlighted];

	// Configure title(s)
	[button setTitle:@"Start   Reset" forState:UIControlStateNormal];
	[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
	[button setTitleShadowColor:[UIColor colorWithRed:.25 green:.25 blue:.25 alpha:1] forState:UIControlStateNormal];
	[button setTitleShadowOffset:CGSizeMake(0, -1)];
	[button setFont:[UIFont boldSystemFontOfSize:28]];

	// Add to TL view, and return
	[self.view addSubview:button];
	return button;
}

// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView
{
	// TL view
	self.view = [[UIView alloc] initWithFrame:CGRectZero];
	self.view.backgroundColor = [UIColor whiteColor];

	// Create buttons
	[self shinyButtonWithWidth:310 height:54 color:[UIColor colorWithRed:.65 green:.05 blue:.05 alpha:1]].center = CGPointMake(160, 54);
	[self shinyButtonWithWidth:310 height:54 color:[UIColor colorWithRed:.05 green:.65 blue:.05 alpha:1]].center = CGPointMake(160, 162);
	[self shinyButtonWithWidth:310 height:54 color:[UIColor colorWithRed:.05 green:.05 blue:.65 alpha:1]].center = CGPointMake(160, 270);
}

Note that this code creates UIButtonTypeRoundedRects; it could just as well have created UIButtonTypeCustom, and it would have made no difference.

Text

When I started this project, I had some goofy ideas about rendering text directly onto the glossy images. (My idea was to use the button’s background for the border, and the foreground image for a composited glossy image and label.) This idea is a non-starter because:

  1. Rendering text onto the glossy images would make them completely non-stretchable. (Not that I’m stretching them now, but I’ve got plans.)
  2. It’s far more complex that using UIButton's built-in title and (foreground) image support.

So, this part of the project just went *poof*, which is nice from the standpoint of elegance, but a little awkward from the standpoint of blogging. Ah well.

Share and Enjoy:
  • Twitter
  • Facebook
  • Digg
  • Reddit
  • HackerNews
  • del.icio.us
  • Google Bookmarks
  • Slashdot
This entry was posted in iPhone. Bookmark the permalink.

Comments are closed.