2

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

4

3 回答 3

10

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

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

这是设置为使用主视图来捕捉夹点;并操纵第二个视图。这样,您仍然可以在视图变小时对其进行缩放。您可以将其更改为直接对可扩展视图做出反应。

限制:我任意选择了视图的起始大小,因此 2.0 的比例限制将等于全屏。我的下限设置为 0.1。

用户交互:我搞砸了很多用户交互,比如更改视图的背景颜色和在视图上添加/更改箭头以显示方向。在缩放过程中向他们提供反馈很重要,尤其是在像此代码允许的那样更改方向时。

BUG:Apple 的 UIPinchGestureRecognizer 中有一个错误。如您所料,它通过两根手指的触摸注册 UIGestureRecognizerStateBegan。但是一旦它处于 StateBegan 或 StateChanged 中,您只需抬起一根手指,状态就会保持不变。在两个手指都抬起之前,它不会移动到 StateEnded 或 StateCancelled。这在我的代码中造成了一个错误,并且让我很头疼!if numberOfTouches > 1 修复它。

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

于 2013-08-04T23:18:34.300 回答
4

这是 Swift 中的一个解决方案:

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
    }
}
于 2016-05-30T14:00:48.917 回答
2

此替代解决方案根据方位角而不是坡度确定缩放方向。我发现使用角度测量来调整不同的区域会更容易一些。

@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)
    }
}
于 2017-10-13T16:25:39.927 回答