What's up with the size of UIButton's Touc

2020-06-15 10:23发布

问题:

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

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.

回答1:

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.



回答2:

A Swift version:

  private let _boundsExtension: CGFloat = 0 // Adjust this as needed

  override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent?) -> Bool {
    let outerBounds: CGRect = CGRectInset(bounds, -1 * _boundsExtension, -1 * _boundsExtension)
    let currentLocation: CGPoint = touch.locationInView(self)
    let previousLocation: CGPoint = touch.previousLocationInView(self)

    let touchOutside: Bool = !CGRectContainsPoint(outerBounds, currentLocation)
    if touchOutside {
      let previousTouchInside: Bool = CGRectContainsPoint(outerBounds, previousLocation)
      if previousTouchInside {
        sendActionsForControlEvents(.TouchDragExit)
      } else {
        sendActionsForControlEvents(.TouchDragOutside)
      }
    } else {
      let previousTouchOutside: Bool = !CGRectContainsPoint(outerBounds, previousLocation)
      if previousTouchOutside {
        sendActionsForControlEvents(.TouchDragEnter)
      } else {
        sendActionsForControlEvents(.TouchDragInside)
      }
    }

    return true
  }

  override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch: UITouch = touches.first!
    let outerBounds: CGRect = CGRectInset(bounds, -1 * _boundsExtension, -1 * _boundsExtension)
    let currentLocation: CGPoint = touch.locationInView(self)

    let touchInside: Bool = CGRectContainsPoint(outerBounds, currentLocation)
    if touchInside {
      return sendActionsForControlEvents(.TouchUpInside)
    } else {
      return sendActionsForControlEvents(.TouchUpOutside)
    }
  }