Iphone – Highlighting a custom UIButton

cocoa-touchiphoneuibutton

The app I'm building has LOTS of custom UIButtons laying over top of fairly precisely laid out images. Buttonish, controllish images and labels and what have you, but with a clear custom-style UIButton sitting over top of it to handle the tap.

My client yesterday says, "I want that to highlight when you tap it". Never mind that it immediately pushes on a new uinavigationcontroller view… it didn't blink, and so he's confused. Oy.

Here's what I've done to address it. I don't like it, but it's what I've done:

I subclassed UIButton (naming it FlashingUIButton).

For some reason I couldn't just configure it with a background image on control mode highlighted. It never seemed to hit the state "highlighted". Don't know why that is.

So instead I wrote:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self setBackgroundImage:[UIImage imageNamed:@"grey_screen"] forState:UIControlStateNormal];
    [self performSelector:@selector(resetImage) withObject:nil afterDelay:0.2];
    [super touchesBegan:touches withEvent:event];
}

-(void)resetImage
{
    [self setBackgroundImage:nil forState:UIControlStateNormal];
}

This happily lays my grey_screen.png (a 30% opaque black box) over the button when it's tapped and replaces it with happy emptyness .2 of a second later.

This is fine, but it means I have to go through all my many nibs and change all my buttons from UIButtons to FlashingUIButtons. Which isn't the end of the world, but I'd really hoped to address this as a UIButton category, and hit all birds with one stone.

Any suggestions for a better approach than this one?

Best Answer

You can intercept the touches events with a gesture recognizer, and then programmatically add the recognizer to all your uibuttons. For instance:

//
//  HighlighterGestureRecognizer.h
//  Copyright 2011 PathwaySP. All rights reserved.
//

#import <Foundation/Foundation.h>


@interface HighlightGestureRecognizer : UIGestureRecognizer {
    id *beganButton;
}

@property(nonatomic, assign) id *beganButton;

@end

and the implementation:

//
//  HighlightGestureRecognizer.m
//  Copyright 2011 PathwaySP. All rights reserved.
//

#import "HighlightGestureRecognizer.h"


@implementation HighlightGestureRecognizer

@synthesize beganButton;

-(id) init{
    if (self = [super init])
    {
        self.cancelsTouchesInView = NO;
    }
    return self;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    self.beganButton = [[[event allTouches] anyObject] view];
    if ([beganButton isKindOfClass: [UIButton class]]) {
        [beganButton setBackgroundImage:[UIImage imageNamed:@"grey_screen"] forState:UIControlStateNormal];
        [self performSelector:@selector(resetImage) withObject:nil afterDelay:0.2];

    }
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{

}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
}

- (void)reset
{
}

- (void)ignoreTouch:(UITouch *)touch forEvent:(UIEvent *)event
{
}

- (BOOL)canBePreventedByGestureRecognizer:(UIGestureRecognizer *)preventingGestureRecognizer
{
    return NO;
}

- (BOOL)canPreventGestureRecognizer:(UIGestureRecognizer *)preventedGestureRecognizer
{
    return NO;
}

- (void)resetImage
{
    [beganButton setBackgroundImage: nil forState:UIControlStateNormal];
}

@end

Hmm. I didn't test this code, so if it doesn't work, don't sue me, just comment and we'll work it out. I just wrote it real quick for reference. Even if it doesn't work, the logic should still be sound.

edit:

Forgot to add, the way you'd add the gesture recognizer to your button would be like so:

HighlighterGestureRecognizer * tapHighlighter = [[HighlighterGestureRecognizer alloc] init];

[myButton addGestureRecognizer:tapHighlighter];
[tapHighlighter release];

So basically you're declaring it, initializing it, and then adding it. After that, you'll want to release it, since the addGestureRecognizer retains it.