There is a special type of UIButton that allows several degrees of customization, like adding an image to the left side of the label and using custom artwork as the background image. This type of button is UIButtonTypeCustom; it is the default type for a UIButton instance created with alloc + init. There isn’t any predefined styling property for these buttons, the developer is able to set the appearance of the button as he wants.
As a general principal of good design, the highlighted state of the button should have different styling from the normal state, for the users to get a visual feedback when they press the button. For custom buttons, this usually means that we need different artwork for each button state. But if we only have artwork for the default / normal state, it ’s still possible to dynamically generate an image to use for the highlighted state.
Generate the highlighted image
Core Image is a powerful image processing framework available in Cocoa and Cocoa Touch, allowing for advanced image editing on pixel by pixel basis. It comes with an impressive set of configurable image filters, that can be used individually or combined, to alter bitmap images.
To dynamically generate the highlighted image for a button, we will extract the image from the button representing all its content (title, icon and background) and invert the colors of the image using a special Core Image filter.
The backing CALayer of any UIView is the root of the layer hierarchy of that view; its own content and the content of its sublayers compose the image displayed by the UIView. The CALayer class exposes the -renderInContext: method, which saves the content of the layer and its hierarchy into the specified context, from which it can extracted as an UIImage object.
Core Image filters apply to CIImage objects, so we have to convert the UIImage into a CIImage instance. After applying the filter, we convert the output CIImage back into an UIImage object we can use for the highlighted state of the button.
In the sample project available here, I’ve created an UIView category which implements the -highlightedImage method returning the color inverted image for the current UIView instance:
- (UIImage *)highlightedImage
// Get the bitmap representation of the view
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
CGContextClipToRect (UIGraphicsGetCurrentContext(), self.bounds);
UIImage *imageToHighlight = UIGraphicsGetImageFromCurrentImageContext();
// Invert colors to create highlighted image
CIImage *image = [CIImage imageWithCGImage:imageToHighlight.CGImage];
CIFilter *highlightFilter = [CIFilter filterWithName:@"CIColorInvert" keysAndValues:kCIInputImageKey, image, nil];
return [UIImage imageWithCIImage:highlightFilter.outputImage];
Customize the highlighted state
To demonstrate how the dynamic highlight image is applied, I’ve used two custom UIButtons with dark and light appearances.
Their initial appearance, corresponding to the normal state, is set in the -viewDidLoad method of the view controller. The highlight image is created in the -viewDidLayoutSubviews method, because it has to be generated from the final version of the button, after it is laid out on screen. Setting this image for the highlight state is as simple as calling the UIButton -setImage:forState method:
UIImage *darkButtonIconImage = [UIImage imageNamed:@"icn_dark_button"];
[self.darkButton setImage:darkButtonIconImage forState:UIControlStateNormal];
[self.darkButton setTitle:@"Dark button" forState:UIControlStateNormal];
[self.darkButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
self.darkButton.backgroundColor = [UIColor darkGrayColor];
UIImage *lightButtonIconImage = [UIImage imageNamed:@"icn_light_button"];
[self.lightButton setImage:lightButtonIconImage forState:UIControlStateNormal];
[self.lightButton setTitle:@"Light button" forState:UIControlStateNormal];
[self.lightButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];
self.lightButton.backgroundColor = [UIColor lightGrayColor];
self.darkButton.titleEdgeInsets = UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 0.0f);
self.lightButton.titleEdgeInsets = UIEdgeInsetsMake(0.0f, 10.0f, 0.0f, 0.0f);
UIImage *highlightedButtonImage = [self.darkButton highlightedImage];
[self.darkButton setImage:highlightedButtonImage forState:UIControlStateHighlighted];
highlightedButtonImage = [self.lightButton highlightedImage];
[self.lightButton setImage:highlightedButtonImage forState:UIControlStateHighlighted];
There are other types of Core Image filters that can be used to obtain a more subtle highlight image than the CIColorInvert filter, which probably contrasts too much with the default state image. But I let you explore the documentation and experiment with the plethora of image filters available in the framework.