开发者

Is there a way to track a UIPinchGesture when one finger leaves and subsequently returns?

I have an iP开发者_如何转开发ad app that makes extensive use of UIGestureRecognizers of all flavors. Generally, I'm a big fan. I have one nagging and very specific issue with a UIPinchGestureRecognizer:

Consider a scenario where a user makes a pinch gesture. Then, without moving one finger AT ALL, removes the other finger and replaces it in a different location and continues the pinch. (It's not as impossible as it sounds).

My problem seems to lie specifically in the UIGestureRecognizer's general inability to notice when a finger leaves the screen and trigger any sort of action. I've noticed this in other places (touchesBegan and touchesEnded) as well, but I've been able to hack around it. Not this time.

Here's how the action looks to UIGestureRecognizer:

  1. UIGestureRecognizerStateBegan, as the pinch starts.
  2. n UIGestureRecognizerStateChangeds, while the pinch changes.
  3. Awkward silence as the user removes a finger.
  4. Awkward silence as the user replaces the finger, probably far away.
  5. Another UIGestureRecognizerStateChanged, as the pinch resumes. 5.

Now, I have a problem at step 5. Since nothing is happening in the handler at steps 3 and 4, it transitions perfectly seamlessly from UIGestureRecognizerStateChangeds in 2 and 5, but a lot has happened. To the handler, it looks like the user just made an incredibly fast pinch, with the rebellious finger traveling potentially hundreds of screen units in the time between handler messages.

Now, if it were impossible for the user to actually make a pinch that fast, I could just put a threshold on allowable finger position deltas between updates. But it is possible. A fast pinch gesture can indeed have a user's finger traveling such great distances. So the climax of the problem is this; UIPinchGestureRecognizer's inability to distinguish the odd situation above from a fast pinch gesture. I need to handle these in very different ways, and right now I have no way whatsoever to tell the difference. Why can't the OS tell me when a finger leaves the screen? Is this a feature of the capacitive screen hardware or an OS bug? Or... intentional design...?


I don’t think there’s anything you can do to make UIPinchGestureRecognizer tell you about a finger touching down or up, but you can make your own gesture recognizer subclass. Below is a scale gesture recognizer that is similar to pinch, and it distinguishes between a one touch and a two touch gesture (the handleOneTouchGesture selector is empty in this example but you could do something like compute a move distance).

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

#import <Foundation/Foundation.h>

@interface RSScaleGestureRecognizer : UIGestureRecognizer {

}

@property (nonatomic, readonly) float   scale;

@end

And the implementation:

//
//  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));
}
0

上一篇:

下一篇:

精彩评论

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

最新问答

问答排行榜