开发者

Using UIPinchGestureRecognizer to scale uiviews in single direction

开发者 https://www.devze.com 2023-03-21 20:30 出处:网络
I would like to know how we can use UIPinchGestureRecognizer to scale UIView in single (x or y) directions alone. Say, if the user moves his two fingers in a pinch gesture only in a single direction (

I would like to know how we can use UIPinchGestureRecognizer to scale UIView in single (x or y) directions alone. Say, if the user moves his two fingers in a pinch gesture only in a single direction (horizontal), only the width of the uiview should increase/decrease and if the fingers are moved only vertically, the height should change. If the fingers move diagonally, then both height and width of uiview should increase/decrease. I have seen the MoveMe sample code from Apple.

UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(scalePiece:)];
[pinchGesture setDelegate:self];
[piece addGestureRecognizer:pinchGesture];
[pinchGesture release];

Scale piece:

- (void)scalePiece:(UIPinchGestureRecognizer *)gestureRecognizer
{
    UIView *piece = (UIView *) [gestureRecognizer view];
    NSLog(@"scalePiece enter");
    if ([gestureRecognizer state] == UIGestureRecognizerStateBegan){
          NSLog(@"inside if");
          lastTouchPosition = [gestureRecognizer locationInView:piece];
    } 
    else if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged){
          NSLog(@"inside else");
          CGPoint currentTouchLocation = [gestureRecognizer locationInView:piece];
          NSLog(@"currentTouchLocation = %@ and lastTouchPosition= %@",NSStringFromCGPoint(currentTouchLocation), NSStringFromCGPoint(lastTouchPosition));
          CGPoint deltaMove = [self calculatePointDistancewithPoint1:currentTouchLocation andPoint2:lastTouchPosition];
          NSLog(@"deltaMove = %@",NSStringFromCGPoint(deltaMove));
          float distance = sqrt(deltaMove.x*deltaMove.x + deltaMove.y*deltaMove.y);
          NSLog(@"distance = %f",distance);
          float hScale = 1 - deltaMove.x/distance * (1-gestureRecognizer.scale);
          float vScale = 1 - deltaMove.y/distance * (1-gestureRecognizer.scale);
          if (distance == 0) {
                     hScale = 1;
                     vScale = 1;
          }
          NSLog(@"[gestureRecognizer scale] = %f",[gestureRecognizer scale]);
          NSLog(@"hScale = %f and vScale = %f",hScale, vScale);
          piece.transform = CGAffineTransformScale([piece transform], hScale, vScale);
          [gestureRecognizer setScale:1];
          lastTouchPosition = currentTouchLocation;
    }
    NSLog(@"scalePiece exit");
}

Calculate distance:

- (CGPoint) calculatePointDistancewithPoint1:(CGPoint)point1 andPoint2:(CGPoint) point2 {
    return CGPointMake(point2.x - point1.x, point2.y - point1.y);
}

This is the log output when I try to pinch out (zoom into) of the view only moving my fingers in a vertical direction. The element's height does not increase.

2011-07-21 13:06:56.245 New[8169:707] scalePiece enter
2011-07-21 13:06:56.248 New[8169:707] inside if
2011-07-21 13:06:56.251 New[8169:707] scalePiece exit
2011-07-21 13:06:56.259 New[8169:707] scalePiece enter
2011-07-21 13:06:56.262 New[8169:707] inside else
2011-07-21 13:06:56.264 New[8169:707] currentTouchLocation = {88, 87} and lastTouchPosition= {87, 86}
2011-07-21 13:06:56.265 New[8169:707] deltaMove = {-1, -1}
2011-07-21 13:06:56.267 New[8169:707] distance = 1.414214
2011-07-21 13:06:56.268 New[8169:707] [gestureRecognizer scale] = 1.102590
2011-07-21 13:06:56.271 New[8169:707] hScale = 0.927458 and vScale = 0.927458
2011-07-21 13:06:56.272 New[8169:707] scalePiece exit
2011-07-21 13:06:56.281 New[8169:707] scalePiece enter
2011-07-21 13:06:56.283 New[8169:707] inside else
2011-07-21 13:06:56.284 New[8169:707] currentTouchLocation = {87, 89} and lastTouchPosition= {88, 87}
2011-07-21 13:06:56.286 New[8169:707] deltaMove = {1, -2}
2011-07-21 13:06:56.287 New[8169:707] distance = 2.236068
2011-07-21 13:06:56.296 New[8169:707] [gestureRecognizer scale] = 1.096172
2011-07-21 13:06:56.298 New[8169:707] hScale = 1.043009 and vScale = 0.913981
2011-07-21 13:06:56.299 New[8169:707] scalePiece exit
2011-07-21 13:06:56.302 New[8169:707] scalePiece enter
2011-07-21 13:06:56.303 New[8169:707] inside else
2011-07-21 13:06:56.305 New[8169:707] currentTouchLocation = {88, 89} and lastTouchPosition= {87, 89}
2011-07-21 13:06:56.309 New[8169:707] deltaMove = {-1, 0}
2011-07-21 13:06:56.311 New[8169:707] distance = 1.000000
2011-07-21 13:06:56.313 New[8169:707] [gestureRecognizer scale] = 1.066320
2011-07-21 13:06:56.314 New[8169:707] hScale = 0.933680 and vScale = 1.000000
2011-07-21 13:06:56.316 New[8169:707] scalePiece exit
2011-07-21 13:06:56.318 New[8169:707] scalePiece enter
2011-07-21 13:06:56.320 New[8169:707] inside else
2011-07-21 13:06:56.329 New[8169:707] currentTouchLocation = {88, 90} and lastTouchPosition= {88, 89}
2011-07-21 13:06:56.331 New[8169:707] deltaMove = {0, -1}
2011-07-21 13:06:56.333 New[8169:707] distance = 1.000000
2011-07-21 13:06:56.334 New[8169:707] [gestureRecognizer scale] = 1.061696
2011-07-21 13:06:56.335 New[8169:707] hScale = 1.000000 and vScale = 0.938304
2011-07-21 13:06:56.338 New[8169:707] scalePiece exit
2011-07-21 13:06:56.343 New[8169:707] scalePiece enter
2011-07-21 13:06:56.346 New[8169:707] inside else
2011-07-21 13:06:56.347 New[8169:707] currentTouchLocation = {88, 92} and lastTouchPosition= {88, 90}
2011-07-21 13:06:56.349 New[8169:707] deltaMove = {0, -2}
2011-07-21 13:06:56.350 New[8169:707] distance = 2.000000
2011-07-21 13:06:56.351 New[8169:707] [gestureRecognizer scale] = 1.096869
2011-07-21 13:06:56.353 New[8169:707] hScale = 1.000000 and vScale = 0.903131
2011-07-21 13:06:56.362 New[8169:707] scalePiece exit
2011-07-21 13:06:56.366 New[8169:707] scalePiece enter
2011-07-21 13:06:56.370 New[8169:707] inside else
2011-07-21 13:06:56.373 New[8169:707] currentTouchLocation = {88, 92} and lastTouchPosition= {88, 92}
2011-07-21 13:06:56.376 New[8169:707] deltaMove = {0, 0}
2011-07-21 13:06:56.380 New[8169:707] distance = 0.000000
2011-07-21 13:06:56.383 New[8169:707] [gestureRecognizer scale] = 1.035330
2011-07-21 13:06:56.387 New[8169:707] hScale = 1.000000 and vScale = 1.000000
2011-07-21 13:06:56.389 New[8169:707] scalePiece exit
2011-07-21 13:06:56.393 New[8169:707] scalePiece enter
2011-07-21 13:06:56.397 New[8169:707] inside else
2011-07-21 13:06:56.399 New[8169:707] currentTouchLocation = {88, 93} and lastTouchPosition= {88, 92}
2011-07-21 13:06:56.403 New[8169:707] deltaMove = {0, -1}
2011-07-21 13:06:56.406 New[8169:707] distance = 1.000000
2011-07-21 13:06:56.409 New[8169:707] [gestureRecognizer scale] = 1.042659
2011-07-21 13:06:56.412 New[8169:707] hScale = 1.000000 and vScale = 0.957341
2011-07-21 13:06:56.414 New[8169:707] scalePiece exit
2011-07-21 13:06:56.419 New[8169:707] scalePiece enter
2011-07-21 13:06:56.422 New[8169:707] inside else
2011-07-21 13:06:56.425 New[8169:707] currentTouchLocation = {88, 92} and lastTouchPosition= {88, 93}
2011-07-21 13:06:56.427 New[8169:707] deltaMove = {0, 1}
2011-07-21 13:06:56.430 New[8169:707] distance = 1.000000
2011-07-21 13:06:56.432 New[8169:707] [gestureRecognizer scale] = 1.024549
2011-07-21 13:06:56.436 New[8169:707] hScale = 1.000000 and vScale = 1.024549
2011-07-21 13:06:56.439 New[8169:707] scalePiece exit
2011-07-21 13:06:56.442 New[8169:707] scalePiece enter
2011-07-21 13:06:56.447 New[8169:707] inside else
2011-07-21 13:06:56.450 New[8169:707] currentTouchLocation = {88, 92} and lastTouchPosition= {88, 92}
2011-07-21 13:06:56.453 New[8169:707] deltaMove = {0, 0}
2011-07-21 13:06:56.455 New[8169:707] distance = 0.000000
2011-07-21 13:06:56.458 New[8169:707] [gestureRecognizer scale] = 1.007702
2011-07-21 13:06:56.460 New[8169:707] hScale = 1.000000 and vScale = 1.000000
2011-07-21 13:06:56.464 New[8169:707] scalePiece exit
2011-07-21 13:06:56.501 New[8169:707] scalePiece enter
2011-07-21 13:06:56.504 New[8169:707] inside else
2011-07-21 13:06:56.507 New[8169:707] currentTouchLocation = {89, 92} and lastTouchPosition= {88, 92}
2011-07-21 13:06:56.509 New[8169:707] deltaMove = {-1, 0}
2011-07-21 13:06:56.510 New[8169:707] distance = 1.000000
2011-07-21 13:06:56.511 New[8169:707] [gestureRecognizer scale] = 1.000283
2011-07-21 13:06:56.513 New[8169:707] hScale = 0.999717 and vScale = 1.000000
2011-07-21 13:06:56.517 New[8169:707] scalePiece exit
2011-07-21 13:06:56.566 New[8169:707] scalePiece enter
2011-07-21 13:06:56.570 New[8169:707] inside else
2011-07-21 13:06:56.572 New[8169:707]开发者_C百科 currentTouchLocation = {89, 91} and lastTouchPosition= {89, 92}
2011-07-21 13:06:56.573 New[8169:707] deltaMove = {0, 1}
2011-07-21 13:06:56.575 New[8169:707] distance = 1.000000
2011-07-21 13:06:56.576 New[8169:707] [gestureRecognizer scale] = 1.008267
2011-07-21 13:06:56.579 New[8169:707] hScale = 1.000000 and vScale = 1.008267
2011-07-21 13:06:56.582 New[8169:707] scalePiece exit
2011-07-21 13:06:56.585 New[8169:707] scalePiece enter
2011-07-21 13:06:56.586 New[8169:707] inside else
2011-07-21 13:06:56.588 New[8169:707] currentTouchLocation = {89, 91} and lastTouchPosition= {89, 91}
2011-07-21 13:06:56.589 New[8169:707] deltaMove = {0, 0}
2011-07-21 13:06:56.591 New[8169:707] distance = 0.000000
2011-07-21 13:06:56.597 New[8169:707] [gestureRecognizer scale] = 1.000000
2011-07-21 13:06:56.599 New[8169:707] hScale = 1.000000 and vScale = 1.000000
2011-07-21 13:06:56.600 New[8169:707] scalePiece exit
2011-07-21 13:06:56.603 New[8169:707] scalePiece enter
2011-07-21 13:06:56.604 New[8169:707] inside else
2011-07-21 13:06:56.606 New[8169:707] currentTouchLocation = {89, 182} and lastTouchPosition= {89, 91}
2011-07-21 13:06:56.607 New[8169:707] deltaMove = {0, -91}
2011-07-21 13:06:56.617 New[8169:707] distance = 91.000000
2011-07-21 13:06:56.620 New[8169:707] [gestureRecognizer scale] = 1.000000
2011-07-21 13:06:56.623 New[8169:707] hScale = 1.000000 and vScale = 1.000000
2011-07-21 13:06:56.626 New[8169:707] scalePiece exit
2011-07-21 13:06:56.630 New[8169:707] scalePiece enter
2011-07-21 13:06:56.632 New[8169:707] scalePiece exit


If I understand correctly your question, you are aiming at having non-proportional scaling along the horizontal and vertical axis. In this case, the thing comes down to providing your affine transform:

  piece.transform = CGAffineTransformScale([piece transform], hScale, vScale);

with different scaling factors.

One way to calculate them is the following:

  1. define an ivar in your class to store the lastTouchPosition;

  2. in your gesture handler you will do something like this:

    if ([gestureRecognizer state] == UIGestureRecognizerStateBegan){
    
       lastTouchPosition = [gestureRecognize locationInView:yourViewHere];
       hScale = 1;
       vScale = 1;
    
    } else if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged){
    
       CGPoint currentTouchLocation = [gestureRecognize locationInView:yourViewHere];
       CGPoint deltaMove = CGPointDistance(currentTouchLocation, lastTouchPosition);
       float distance = sqrt(deltaMove.x*deltaMove.x + deltaMove.y*deltaMove.y);
       hScale -= abs(deltaMove.x)/distance * (1-gestureRecognizer.scale);
       vScale -= abs(deltaMove.y)/distance * (1-gestureRecognizer.scale);
       piece.transform = CGAffineTransformScale([piece transform], hScale, vScale);
       [gestureRecognizer setScale:1];
    
       lastTouchPosition = currentTouchLocation;
    }
    

An alternative way of doing is:

    if ([gestureRecognizer state] == UIGestureRecognizerStateBegan){

       lastTouchPosition = [gestureRecognize locationInView:yourViewHere];

    } else if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged){

       CGPoint currentTouchLocation = [gestureRecognize locationInView:yourViewHere];
       CGPoint deltaMove = CGPointDistance(currentTouchLocation, lastTouchPosition);
       float distance = sqrt(deltaMove.x*deltaMove.x + deltaMove.y*deltaMove.y);
       float hScale = 1 - abs(deltaMove.x)/distance * (1-gestureRecognizer.scale);
       float vScale = 1 - abs(deltaMove.y)/distance * (1-gestureRecognizer.scale);
       piece.transform = CGAffineTransformScale([piece transform], hScale, vScale);

       lastTouchPosition = currentTouchLocation;
    }

This will not store at each iteration the current hFloat and vFloat and instead rely on the fact that the gestureRecognizer will accumulate the overall scale change. It does an "absolute" calculate, while the first implementation does a "relative" one.

Note that you need also define CGPointDistance to calculate the distance between the two touches and choose which view are your going to use to calculate the distance (yourViewHere).

EDIT:

CGPoint CGPointDistance(CGPoint point1, CGPoint point2)
{
    return = CGPointMake(point2.x - point1.x, point2.y - point1.y);
};

EDIT2: about the formula to calculate the scaling

The idea is calculating the variation in the scale factor and apply it to the two directions (x and y) according to their relative variations.

  1. the scale factor delta is: 1-gestureRecognizer.scale;

  2. the delta is the multiplied by a factor so that it is somehow proportional to the displacement along the horizontal or vertical axis; when the displacement is zero, the delta scale is also zero; when the displacement is equal along the two axis, the scale delta is also equal along the two axis; I decided to divide by the distance, but there are other possibilities (like dividing by the sum of the two deltaMoves or by the max of the deltaMoves, etc).

  3. the adjusted delta is finally subtracted from the current scale of the element.


Today I am facing the same problem and I found a simple and short way to do this

- (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer
{
  recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale,1);
  recognizer.scale = 1;
}


I created a custom version of a UIPinchGestureRecognizer to do exactly what you're looking for. It uses the slope of line between the two fingers to determine the direction of the scale. It does 3 types: Vertical; Horizontal; and Combined(diagonal). Please see my notes at the bottom.

-(void) scaleTheView:(UIPinchGestureRecognizer *)pinchRecognizer
    {
    if ([pinchRecognizer state] == UIGestureRecognizerStateBegan || [pinchRecognizer state] == UIGestureRecognizerStateChanged) {

    if ([pinchRecognizer numberOfTouches] > 1) {

        UIView *theView = [pinchRecognizer view];

        CGPoint locationOne = [pinchRecognizer locationOfTouch:0 inView:theView];
        CGPoint locationTwo = [pinchRecognizer locationOfTouch:1 inView:theView];
            NSLog(@"touch ONE  = %f, %f", locationOne.x, locationOne.y);
            NSLog(@"touch TWO  = %f, %f", locationTwo.x, locationTwo.y);
        [scalableView setBackgroundColor:[UIColor redColor]];

        if (locationOne.x == locationTwo.x) {
                // perfect vertical line
                // not likely, but to avoid dividing by 0 in the slope equation
            theSlope = 1000.0;
        }else if (locationOne.y == locationTwo.y) {
                // perfect horz line
                // not likely, but to avoid any problems in the slope equation
            theSlope = 0.0;
        }else {
            theSlope = (locationTwo.y - locationOne.y)/(locationTwo.x - locationOne.x);
        }

        double abSlope = ABS(theSlope);

        if (abSlope < 0.5) {
                    //  Horizontal pinch - scale in the X
            [arrows setImage:[UIImage imageNamed:@"HorzArrows.png"]];
            arrows.hidden = FALSE;
                    // tranform.a  = X-axis
                NSLog(@"transform.A = %f", scalableView.transform.a);
                    // tranform.d  = Y-axis
                NSLog(@"transform.D = %f", scalableView.transform.d);

                    //  if hit scale limit along X-axis then stop scale and show Blocked image
            if (((pinchRecognizer.scale > 1.0) && (scalableView.transform.a >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.a <= 0.1))) {
                blocked.hidden = FALSE;
                arrows.hidden = TRUE;
            } else {
                        // scale along X-axis
                scalableView.transform = CGAffineTransformScale(scalableView.transform, pinchRecognizer.scale, 1.0);
                pinchRecognizer.scale = 1.0;
                blocked.hidden = TRUE;
                arrows.hidden = FALSE;
            }
        }else if (abSlope > 1.7) {
                    // Vertical pinch - scale in the Y
            [arrows setImage:[UIImage imageNamed:@"VerticalArrows.png"]];
            arrows.hidden = FALSE;
                NSLog(@"transform.A = %f", scalableView.transform.a);
                NSLog(@"transform.D = %f", scalableView.transform.d);

                    //  if hit scale limit along Y-axis then don't scale and show Blocked image
            if (((pinchRecognizer.scale > 1.0) && (scalableView.transform.d >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.d <= 0.1))) {
                blocked.hidden = FALSE;
                arrows.hidden = TRUE;
            } else {
                        // scale along Y-axis
                scalableView.transform = CGAffineTransformScale(scalableView.transform, 1.0, pinchRecognizer.scale);
                pinchRecognizer.scale = 1.0;
                blocked.hidden = TRUE;
                arrows.hidden = FALSE;
            }
        } else {
                    // Diagonal pinch - scale in both directions
            [arrows setImage:[UIImage imageNamed:@"CrossArrows.png"]];
            blocked.hidden = TRUE;
            arrows.hidden = FALSE;

                NSLog(@"transform.A = %f", scalableView.transform.a);
                NSLog(@"transform.D = %f", scalableView.transform.d);

                    // if we have hit any limit don't allow scaling
            if ((((pinchRecognizer.scale > 1.0) && (scalableView.transform.a >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.a <= 0.1))) || (((pinchRecognizer.scale > 1.0) && (scalableView.transform.d >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.d <= 0.1)))) {
                blocked.hidden = FALSE;
                arrows.hidden = TRUE;
            } else {
                        // scale in both directions
                scalableView.transform = CGAffineTransformScale(scalableView.transform, pinchRecognizer.scale, pinchRecognizer.scale);
                pinchRecognizer.scale = 1.0;
                blocked.hidden = TRUE;
                arrows.hidden = FALSE;
            }
        }  // else for diagonal pinch
    }  // if numberOfTouches
}  // StateBegan if

if ([pinchRecognizer state] == UIGestureRecognizerStateEnded || [pinchRecognizer state] == UIGestureRecognizerStateCancelled) {
    NSLog(@"StateEnded StateCancelled");
    [scalableView setBackgroundColor:[UIColor whiteColor]];
    arrows.hidden = TRUE;
    blocked.hidden = TRUE;
    }
}

Remember to add the protocol to the view controller header file:

@interface WhiteViewController : UIViewController <UIGestureRecognizerDelegate>
{
IBOutlet UIView *scalableView;
IBOutlet UIView *mainView;
IBOutlet UIImageView *arrows;
IBOutlet UIImageView *blocked;
}
@property (strong, nonatomic) IBOutlet UIView *scalableView;
@property (strong, nonatomic) IBOutlet UIView *mainView;
@property (strong, nonatomic)IBOutlet UIImageView *arrows;
@property (strong, nonatomic)IBOutlet UIImageView *blocked;

-(void) scaleTheView:(UIPinchGestureRecognizer *)pinchRecognizer;
@end

And add the recognizer in the viewDidLoad:

- (void)viewDidLoad
{ UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(scaleTheView:)];
[pinchGesture setDelegate:self];
[mainView addGestureRecognizer:pinchGesture];

arrows.hidden = TRUE;
blocked.hidden = TRUE;
[scalableView setBackgroundColor:[UIColor whiteColor]];}

This is set up to use the main view to capture the pinch; and manipulate a second view. This way you can still scale it as the view gets small. You can change it to react directly to the scalable view.

LIMITS: I arbitrarily chose the starting size of my view so a scale limit of 2.0 would equal full screen. My lower scale is set at 0.1.

USER INTERACTION: I mess around with a lot of user interaction things like changing the view's background color and adding/changing arrows over the view to show direction. It's important to give them feedback during the scaling process, especially when changing directions like this codes allows.

BUG: There is a bug in Apple's UIPinchGestureRecognizer. It registers UIGestureRecognizerStateBegan with the touch of 2 fingers as you would expect. But once it is in StateBegan or StateChanged you can lift one finger and the state remains. It doesn't move to StateEnded or StateCancelled until BOTH fingers are lifted. This created a bug in my code and many headaches! The if numberOfTouches > 1 fixes it.

FUTURE: You can change the slope settings to scale in just one direction, or just 2. If you add the arrows images, you can see them change as you rotate your fingers.

0

精彩评论

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