1

我有一个 iPad 应用程序,它广泛使用各种风格的 UIGestureRecognizers。一般来说,我是一个大粉丝。我对 UIPinchGestureRecognizer 有一个烦人且非常具体的问题:

考虑用户做出捏合手势的场景。然后,在不移动一根手指的情况下,移开另一根手指并将其替换到不同的位置并继续捏合。(这并不像听起来那么不可能)。

我的问题似乎特别在于 UIGestureRecognizer 通常无法注意到手指何时离开屏幕并触发任何类型的动作。我在其他地方(touchesBegan 和 touchesEnded)也注意到了这一点,但我已经能够绕过它。这次不行。

以下是 UIGestureRecognizer 中该操作的外观:

  1. UIGestureRecognizerStateBegan,随着紧要关头的开始。
  2. n UIGestureRecognizerStateChangeds,同时捏变。
  3. 用户移开手指时的 尴尬沉默。
  4. 当用户更换手指时,尴尬的沉默,可能很远。
  5. 另一个 UIGestureRecognizerStateChanged,随着捏的恢复。5.

现在,我在第 5 步遇到了问题。由于在第 3 步和第 4 步处理程序中没有发生任何事情,因此它从第 2 步和第 5 步中的 UIGestureRecognizerStateChangeds 完美无缝过渡,但发生了很多事情。对于处理程序来说,看起来用户刚刚进行了令人难以置信的快速捏合,在处理程序消息之间的时间内,叛逆的手指可能会移动数百个屏幕单位。

现在,如果用户不可能真正快速地进行捏合,我可以在更新之间允许的手指位置增量上设置一个阈值。但这是可能的。快速捏合手势确实可以让用户的手指移动这么远的距离。所以问题的高潮是这样的;UIPinchGestureRecognizer 无法将上述奇怪情况与快速捏合手势区分开来。我需要以非常不同的方式处理这些问题,而现在我无法区分它们。为什么当手指离开屏幕时操作系统不能告诉我?这是电容屏硬件的功能还是操作系统错误?还是……故意设计……?

4

1 回答 1

0

我认为您无法做任何事情来让 UIPinchGestureRecognizer 告诉您手指向下或向上触摸,但您可以创建自己的手势识别器子类。下面是一个类似于捏合的比例手势识别器,它区分单点触摸和两点触摸手势(handleOneTouchGesture在此示例中选择器为空,但您可以执行计算移动距离之类的操作)。

//
//  RSScaleGestureRecognizer.h
//  Created by Jeff Argast
//

#import <Foundation/Foundation.h>

@interface RSScaleGestureRecognizer : UIGestureRecognizer {

}

@property (nonatomic, readonly) float   scale;

@end

和实施:

//
//  RSScaleGestureRecognizer.m
//  Created by Jeff Argast
//

#import "RSScaleGestureRecognizer.h"
#import <UIKit/UIGestureRecognizerSubclass.h>

//
// Distance function
//

static CGFloat RSGetPointDistance (CGPoint p0, CGPoint p1);

//
// RSScaleGestureRecognizer private selectors
//

@interface RSScaleGestureRecognizer ()

- (void) handleTouchDown: (NSSet*) touches withEvent: (UIEvent*) event;
- (void) handleTouchMoved: (NSSet*) touches withEvent: (UIEvent*) event;
- (void) handleTouchUp: (NSSet*) touches withEvent: (UIEvent*) event;
- (void) handleOneTouchGesture: (NSSet*) allTouches;
- (void) handleTwoTouchGesture: (NSSet*) allTouches touchesMoved: (NSSet*) movedTouches;

@end

//
// UIView helper category
//

@interface UIView (RSScaleGestureRecognizer)

- (CGFloat) computeScaleFrom: (UITouch*) t0 to: (UITouch*) t1;

@end

//
// RSScaleGestureRecognizer Implementation
//

@implementation RSScaleGestureRecognizer

@synthesize scale;

- (id) initWithTarget:(id)target action:(SEL)action
{
    self = [super initWithTarget: target action: action];

    if ( self )
    {
        scale = 1.0f;
    }

    return self;
}

- (void)reset
{
    [super reset];

    scale = 1.0f;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{   
    [super touchesBegan: touches withEvent: event];

    [self handleTouchDown: touches withEvent: event];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved: touches withEvent: event];

    [self handleTouchMoved: touches withEvent: event];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{   
    [super touchesEnded: touches withEvent: event]; 

    [self handleTouchUp: touches withEvent: event];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesCancelled: touches withEvent: event];

    [self handleTouchUp: touches withEvent: event];
}

- (void) handleTouchDown: (NSSet*) touches withEvent: (UIEvent*) event
{
    switch ( self. state )
    {
        case UIGestureRecognizerStateBegan:
        case UIGestureRecognizerStateChanged:
            break;

        case UIGestureRecognizerStatePossible:
            {
                NSSet* allTouches = [event touchesForGestureRecognizer: self];

                if ( allTouches.count > 2 )
                {
                    self.state = UIGestureRecognizerStateFailed;
                    return;
                }
            }
            break;

        default:
            self.state = UIGestureRecognizerStateFailed;
    }
}

- (void) handleTouchMoved: (NSSet*) movedTouches withEvent: (UIEvent*) event
{
    NSSet* allTouches = [event touchesForGestureRecognizer: self];

    switch ( allTouches.count )
    {
        case 1:
        {
            [self handleOneTouchGesture: allTouches];
        }
        break;

        case 2:
        {
            [self handleTwoTouchGesture: allTouches touchesMoved: movedTouches];
        }
        break;
    }
}

- (void) handleTouchUp: (NSSet*) touches withEvent: (UIEvent*) event
{   
    NSSet* allTouches = [event touchesForGestureRecognizer: self];

    int touchesRemaining = allTouches.count - touches.count;

    if ( touchesRemaining > 0 )
        return;

    switch ( self.state )
    {
        case UIGestureRecognizerStateBegan:
        case UIGestureRecognizerStateChanged:
            self.state = UIGestureRecognizerStateEnded;
            break;

        default:
            self.state = UIGestureRecognizerStateFailed;
    }

}

- (void) handleOneTouchGesture: (NSSet*) allTouches
{
    // Do something special here if desired when only one finger is touching
    return;
}

- (void) handleTwoTouchGesture: (NSSet*) allTouches touchesMoved: (NSSet*) movedTouches
{
    UIGestureRecognizerState currentState = self.state;

    switch ( currentState )
    {
        case UIGestureRecognizerStatePossible:  
        case UIGestureRecognizerStateBegan:
        case UIGestureRecognizerStateChanged:
            {
                UIView* selfView        = self.view;
                NSEnumerator* touchEnum = [allTouches objectEnumerator];
                UITouch* firstTouch     = [touchEnum nextObject];
                UITouch* secondTouch    = [touchEnum nextObject];

                scale = scale * [selfView computeScaleFrom: firstTouch to: secondTouch];        

                if ( currentState == UIGestureRecognizerStatePossible )
                {
                    self.state = UIGestureRecognizerStateBegan;
                }
                else 
                {
                    self.state = UIGestureRecognizerStateChanged;
                }
            }
            break;


        default:
            self.state = UIGestureRecognizerStateFailed;
    }
}

@end

//
// UIVIew category implementation
//

@implementation UIView (RSScaleGestureRecognizer)

- (CGFloat) computeScaleFrom: (UITouch*) t0 to: (UITouch*) t1
{
    UITouchPhase t0Phase = t0.phase;

    if ( (t0Phase == UITouchPhaseEnded) || (t0Phase == UITouchPhaseCancelled) || (t0Phase == UITouchPhaseBegan) )
        return 1.0;

    UITouchPhase t1Phase = t1.phase;

    if ( (t1Phase == UITouchPhaseEnded) || (t1Phase == UITouchPhaseCancelled) || (t1Phase == UITouchPhaseBegan) )
        return 1.0;

    CGPoint oldFirstPoint = [t0 previousLocationInView:self];
    CGPoint oldSecondPoint = [t1 previousLocationInView:self];
    CGFloat oldLength = RSGetPointDistance (oldFirstPoint, oldSecondPoint);

    CGPoint currentFirstPoint = [t0 locationInView:self];
    CGPoint currentSecondPoint = [t1 locationInView:self ];
    CGFloat currentLength = RSGetPointDistance (currentFirstPoint, currentSecondPoint);

    // Avoid divide by zero
    if ( oldLength < 0.01f )
        return 1.0f;

    return currentLength / oldLength;
}

@end

//
// Distance function implementation
//

CGFloat RSGetPointDistance (CGPoint p0, CGPoint p1)
{
    CGFloat xDiff = p0.x - p1.x;
    CGFloat yDiff = p0.y - p1.y;

    return sqrtf ((xDiff * xDiff) + (yDiff * yDiff));
}
于 2011-01-21T14:18:52.900 回答