使用UIPinchGestureRecognizer以缩放方向缩放视图

我需要一个缩放识别器,可以在x,y或两个方向上缩放,具体取决于捏的方向。 我查看了其他许多问题,他们只有部分答案。 这是我使用自定义UIPinchGestureRecognizer的完整解决scheme。

我创build了一个UIPinchGestureRecognizer的自定义版本。 它使用两根手指之间的线的斜率来确定比例的方向。 它有3种types:垂直; 水平; 和组合(对angular线)。 请在底部看我的笔记。

-(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; } } 

请记住将协议添加到视图控制器头文件中:

 @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 

并在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]]; } 

这是设置为使用主视图捕捉捏; 并操纵第二个视图。 这样,您仍然可以缩放视图变小。 您可以将其更改为直接对可缩放视图作出反应。

限制:我任意select我的视图的开始大小,所以2.0的限制等于全屏。 我的下限是0.1。

用户交互:我篡改了很多用户交互的东西,比如改变视图的背景颜色和在视图上添加/改变箭头以显示方向。 在缩放过程中给予他们反馈是非常重要的,特别是当这些代码允许的情况下改变方向时。

BUG:苹果的UIPinchGestureRecognizer有一个错误。 它会按照您所期望的那样通过两根手指的触摸来注册UIGestureRecognizerStateBegan。 但是,一旦处于StateBegan或StateChanged状态,您可以提起一个手指,并保持状态。 它不会移动到StateEnded或StateCancelled直到两个手指被解除。 这在我的代码中造成了一个错误,并且让我头疼! 如果numberOfTouches> 1修复它。

未来:您可以将斜率设置更改为仅在一个方向上缩放,或者仅添加2.如果添加箭头图像,则可以在旋转手指时看到它们的变化。

Swift中有一个解决scheme:

 extension UIPinchGestureRecognizer { func scale(view: UIView) -> (x: CGFloat, y: CGFloat)? { if numberOfTouches() > 1 { let touch1 = self.locationOfTouch(0, inView: view) let touch2 = self.locationOfTouch(1, inView: view) let deltaX = abs(touch1.x - touch2.x) let deltaY = abs(touch1.y - touch2.y) let sum = deltaX + deltaY if sum > 0 { let scale = self.scale return (1.0 + (scale - 1.0) * (deltaX / sum), 1.0 + (scale - 1.0) * (deltaY / sum)) } } return nil } } 

这种替代解决scheme根据方位angular而不是斜率来确定缩放的方向。 我发现使用angular度测量来调整不同区域更容易一些。

 @objc func viewPinched(sender: UIPinchGestureRecognizer) { // Scale the view either vertically, horizontally, or diagonally based on the axis of the initial pinch let locationOne = sender.location(ofTouch: 0, in: sender.view) let locationTwo = sender.location(ofTouch: 1, in: sender.view) let diffX = locationOne.x - locationTwo.x let diffY = locationOne.y - locationTwo.y // Break the plane into 3 equal segments // Inverse tangent will return between π/2 and -π/2. Absolute value can be used to only consider 0 to π/2 - don't forget to handle divide by 0 case // Breaking π/2 into three equal pieces, we get regions of 0 to π/6, π/6 to 2π/6, and 2π/6 to π/2 (note 2π/6 = π/3) // Radian reminder - π/2 is 90 degreees :) let bearingAngle = diffY == 0 ? CGFloat.pi / 2.0 : abs(atan(diffX/diffY)) if sender.state == .began { // Determine type of pan based on bearing angle formed by the two touch points. // Only do this when the pan begins - don't change type as the user rotates their fingers. Require a new gesture to change pan type if bearingAngle < CGFloat.pi / 6.0 { panType = .vertical } else if bearingAngle < CGFloat.pi / 3.0 { panType = .diagonal } else if bearingAngle <= CGFloat.pi / 2.0 { panType = .horizontal } } // Scale the view based on the pan type switch panType { case .diagonal: transform = CGAffineTransform(scaleX: sender.scale, y: sender.scale) case .horizontal: transform = CGAffineTransform(scaleX: sender.scale, y: 1.0) case .vertical: transform = CGAffineTransform(scaleX: 1.0, y: sender.scale) } }