Search code examples
iosobjective-cuibuttondraggesture

UIButton exceeds the limitation of boundaries when dragging very fast


I'm trying to implement drag controls with boundary limitation on UIButton. And I wrote following code for that.

- (void)onTouchDragInside:(UIButton*)btn withEvent:(UIEvent*)event{
  UITouch *touch = [[event touchesForView:btn] anyObject];
  CGPoint prevPos = [touch previousLocationInView:btn];
  CGPoint pos = [touch locationInView:btn];
  float dX = pos.x-prevPos.x;

  if (btn.frame.origin.x >= buttonOffPosition && btn.frame.origin.x <= buttonOnPosition) {
    btn.center=CGPointMake(btn.center.x+dX, btn.center.y);
    NSLog(@"buttonOffPos: %f", buttonOffPosition);
    NSLog(@"btn.center.x+dX: %f", btn.center.x+dX);
    NSLog(@"buttonOnPos: %f", buttonOnPosition);
  }
}

This works almost properly. But only when the button is dragged very fast, it exceeds the limitation buttonOffPosition and buttonOnPosition.

This is the problem I want to resolve. Is there a good way to solve this problem?

Your thoughts and help will be hugely appreciated.


Solution

  • If you want the code to limit your button's location to between your two values you need to check what the final resting point of your button will be after this touch event is processed, not check where it currently is. If it's currently in bounds to be moved, then you move it by say 1000, it will no longer be in bounds at the end but you allow it to go there because you didn't check the end point.

    You can do this several ways. The simplest one that comes to my mind is:

    - (void)onTouchDragInside:(UIButton*)btn withEvent:(UIEvent*)event{
      UITouch *touch = [[event touchesForView:btn] anyObject];
      CGPoint prevPos = [touch previousLocationInView:btn];
      CGPoint pos = [touch locationInView:btn];
      float dX = pos.x-prevPos.x;
    
      //Get the new origin after this motion
      float newXOrigin = btn.frame.origin.x + dX;
      //Make sure it's within your two bounds
      newXOrigin = MIN(newXOrigin,buttonOnPosition);
      newXOrigin = MAX(newXOrigin,buttonOffPosition);
      //Now get the new dX value staying in bounds
      dX = newXOrigin - btn.frame.origin.x;
    
      btn.center=CGPointMake(btn.center.x+dX, btn.center.y);
    }
    

    This method brings up a problem with your finger no longer being inside the button as you are dragging it from one direction to the other, but I will leave that problem to your next question.

    EDIT:

    Here is how I would make it more readable. This is only my opinion and has no bearing on the way the code works. Outside of just modifying your code, I would create a cached starting point for the button object and do all motion events from that. That way if your button stops moving even though your finger continues, as your finger comes back the button stays with your finger. This current solution, as soon as your finger changes direction the button will start moving again even though your finger is far off the button now. But to do that would be a large code change for you.

    - (void)onTouchDragInside:(UIButton*)btn withEvent:(UIEvent*)event{
      //This code can go awry if there is more than one finger on the screen, careful
      UITouch *touch = [[event touchesForView:btn] anyObject];
      CGPoint prevPos = [touch previousLocationInView:btn];
      CGPoint pos = [touch locationInView:btn];
      float dX = pos.x-prevPos.x;
    
      //Get the original position of the button
      CGRect buttonFrame = btn.frame;
    
      buttonFrame.origin.x += dX;
      //Make sure it's within your two bounds
      buttonFrame.origin.x = MIN(buttonFrame.origin.x,buttonOnPosition);
      buttonFrame.origin.x = MAX(buttonFrame.origin.x,buttonOffPosition);
    
      //Set the button's new frame if we need to
      if(buttonFrame.origin.x != btn.frame.origin.x)
          btn.frame = buttonFrame
    }