Kai Kai - 4 months ago 25
iOS Question

What's up with the size of UIButton's Touch Drag/Exit hit area?

Well, I guess it is best to show what I mean:

Animation of a UIButton reacting to dragging motions

You can clearly see that once we've touched the button and moved out of it, a consequent move-in event triggers the button state change from far away.

While this behavior is natural for all UIButtons, I couldn't google a solution to alter it.

Is there a way to reduce the hit area for this type of UIButton sensitivity? I want it reduced, because I feel that the button is large enough as it is, and it will provide a better user experience along with up/down sound effects.

UPD: The following override code for UIButton was posted in another thread:

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
CGFloat boundsExtension = 25.0f;
CGRect outerBounds = CGRectInset(self.bounds, -1 * boundsExtension, -1 * boundsExtension);

BOOL touchOutside = !CGRectContainsPoint(outerBounds, [touch locationInView:self]);
if(touchOutside)
{
BOOL previousTouchInside = CGRectContainsPoint(outerBounds, [touch previousLocationInView:self]);
if(previousTouchInside)
{
NSLog(@"Sending UIControlEventTouchDragExit");
[self sendActionsForControlEvents:UIControlEventTouchDragExit];
}
else
{
NSLog(@"Sending UIControlEventTouchDragOutside");
[self sendActionsForControlEvents:UIControlEventTouchDragOutside];
}
}
return [super continueTrackingWithTouch:touch withEvent:event];
}


It alters the hit area extension used by Drag In/Drag Out events, yet button Up/Down states switch exactly the same way as they did before.

Answer

I don't know if you are still having the same issue, but I was able to fix it by using similar code in the touchesEnded:withEvent: method.

I also changed that method to add touchEnter and dragInside because with the current code, those to events still used the same bounds. In addition I made each of the cases return YES so that the super is not called (it would cause the touch drag inside to be called prematurely).

Here is the final code that I ended up with, in two methods:

- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
    CGFloat boundsExtension = 25.0f;
    CGRect outerBounds = CGRectInset(self.bounds, -1 * boundsExtension, -1 * boundsExtension);

    BOOL touchOutside = !CGRectContainsPoint(outerBounds, [touch locationInView:self]);
    if(touchOutside) {
        BOOL previousTouchInside = CGRectContainsPoint(outerBounds, [touch previousLocationInView:self]);
        if(previousTouchInside) {
            [self sendActionsForControlEvents:UIControlEventTouchDragExit];
            return YES;
        }
        else
        {
            [self sendActionsForControlEvents:UIControlEventTouchDragOutside];
            return YES;
        }
    }
    else {
        BOOL previousTouchOutside = !CGRectContainsPoint(outerBounds, [touch previousLocationInView:self]);
        if (previousTouchOutside) {
            [self sendActionsForControlEvents:UIControlEventTouchDragEnter];
            return YES;
        }
        else {
            [self sendActionsForControlEvents:UIControlEventTouchDragInside];
            return YES;
        }
    }
    return [super continueTrackingWithTouch:touch withEvent:event];
}


- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGFloat boundsExtension = 25.0f;
    CGRect outerBounds = CGRectInset(self.bounds, -1 * boundsExtension, -1 * boundsExtension);

    BOOL touchInside = CGRectContainsPoint(outerBounds, [touch locationInView:self]);
    if (touchInside) {
        return [self sendActionsForControlEvents:UIControlEventTouchUpInside];
    }
    else {
        return [self sendActionsForControlEvents:UIControlEventTouchUpOutside];
    }
    return [super endTrackingWithTouch:touch withEvent:event];
}

NOTE: Returning the super of the method at the end is not necessary, but I left it in there for completeness.

Comments