有没有办法在一根手指离开并随后返回时跟踪 UIPinchGesture?

发布于 2024-10-12 23:00:22 字数 1014 浏览 8 评论 0原文

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

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

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

的操作:

  1. 以下是UIGestureRecognizer 开始操作时
  2. UIGestureRecognizerStateBegan。 n UIGestureRecognizerStateChangeds,同时捏合发生变化。
  3. 当用户移开手指时出现尴尬的沉默
  4. 当用户放回手指时,出现尴尬的沉默,可能距离很远。
  5. 当捏合恢复时,另一个 UIGestureRecognizerStateChanged。 5.

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

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

I have an iPad 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...?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

简单气质女生网名 2024-10-19 23:00:22

我认为您无法做任何事情来让 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));
}

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));
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文