开发者

Custom UIButton drawing - stays depressed after touchesEnded

I've made a custom zero-image, Core Graphics drawn UIButton based on Ray Wenderlich's Tutorial (the only modifications are to CoolButton's drawRect: method, altered drawing code), and it works great most of the time. However, sometimes when I click it for a short period of time, it stays in a depressed state and doesn't return to normal.

From here, the only way to get it back to a normal state is via a long press. Simply clicking means it stays depressed.

Another thing to note is that I've hooked Touch Up Inside up to a chain of a few long methods - I don't think it would take more than 0.1 seconds to complete. I've even used dispatch_async in the @selector that is hooked up to Touch Up Inside, so there shouldn't be a delay in the UI updating, I think.


I've put an NSLog in the drawRect: which fires 3 times 开发者_高级运维per button press usually, and it varies what UIControlState the button is in for each press:

  • For some short presses, it goes Highlighted, Highlighted, Normal
  • for longer presses, it's Highlighted, Normal, Normal

However, for very short presses, it only fires twice, Highlighted -> Highlighted.

When it's a long press to get it back to Normal, it goes H, N, N.


This has been puzzling me for a while, and I haven't been able to work out why short presses only fire drawRect: twice, or why touchesEnded: doesn't seem to call drawRect:. Perhaps touchesEnded: isn't firing?

I really hope someone can help.


If you really want to generate the button images at runtime, generate them when the button is loaded. Then add them for different states using

[button setImage:btnImage forState:UIControlStateNormal];

You can always turn a view into an image using the following: http://pastie.org/244916

Really though, I'd recommend just making images beforehand. If you don't want to get photoshop, there's plenty of alternatives. The upcoming pixelmator update looks pretty suave, ands it's ridiculously cheap!


Well, well, that was easy. 0.15 second delay works, 0.1 doesn't.


As other said, you can simply generate an image and use that as background (if the image is static). No need photoshop, you may simply generate your image once and then take a snapshot, then cut the image out (with Anteprima) and save it as a png file :)

However, since you said the button is connected to some long methods, this may be why the button stay pressed: if you are not calling those methods in background, then the button will stay pressed since all the task are ended. Try (for a test) to connect the button with a single simple method (say NSLog something) and check if it stay pressed. If not, I suggest to detach your methods in background.


I too ran into a similar problem with a UIButton appearing to stick one state when I customize the drawRect method. My hunch is that the state of the button is changing more than once before drawRect is called. So I just added a custom property that would only be set by methods called during touchDown and touchUpInside events. I had to dispatch threads to do this because of other operations that were holding up the main thread, causing a delay in the redrawHighlighted method.

It may be a little messy, but here's what worked for me:

// CustomButton.h
@interface CustomButton : UIButton
@property (atomic) BOOL drawHighlighted;    

// CustomButton.m
@implementation CustomButton
@synthesize drawHighlighted = _drawHighlighted;

- (id)initWithFrame:(CGRect)frame
{
    if ((self = [super initWithFrame:frame]))
    {
        _drawHighlighted = NO;

        [self addTarget:self action:@selector(redrawHighlighted) forControlEvents:UIControlEventTouchDown];
        [self addTarget:self action:@selector(redrawNormal) forControlEvents:UIControlEventTouchUpInside];
        [self addTarget:self action:@selector(redrawNormal) forControlEvents:UIControlEventTouchDragExit];  
    }
    return self;
}

- (void)drawRect:(CGRect)frame
{
    // draw button here
}

- (void)redrawNormal
{
    [NSThread detachNewThreadSelector:@selector(redrawRectForNormal) toTarget:self withObject:nil];
}

- (void)redrawHighlighted
{
    [NSThread detachNewThreadSelector:@selector(redrawRectForHighlighted) toTarget:self withObject:nil];
}

- (void)redrawRectForNormal
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    _drawHighlighted = NO;  
    [self setNeedsDisplay];

    [pool release];
}

- (void)redrawRectForHighlighted
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    _drawHighlighted = YES; 
    [self setNeedsDisplay];

    [pool release];
}


Another cheap but effective alternative I've found is to create a drawRect: that doesn't do any highlighting, and simply alter the button subclass's alpha property when the user interacts with the button.

For example:

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    self.alpha = 0.3f;
    return [super beginTrackingWithTouch:touch withEvent:event];
}

- (void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    self.alpha = 1.0f;
    [super endTrackingWithTouch:touch withEvent:event];
}

- (void)cancelTrackingWithEvent:(UIEvent *)event
{
    self.alpha = 0.3f;
    [super cancelTrackingWithEvent:event];
}

- (void)drawRect:(CGRect)rect
{
    // Your drawing code here sans highlighting
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜