1

すべてのフレーバーの UIGestureRecognizers を広範囲に使用する iPad アプリがあります。一般的に、私は大ファンです。UIPinchGestureRecognizer には、非常に具体的な問題が 1 つあります。

ユーザーがピンチ ジェスチャを行うシナリオを考えてみましょう。次に、1 本の指をまったく動かさずに、もう 1 本の指を取り外して別の場所に置き、ピンチを続けます。(思ったほど不可能ではありません)。

私の問題は、UIGestureRecognizer が一般的に、指が画面から離れたときに認識できず、何らかのアクションをトリガーできないことに特にあるようです。他の場所 (touchesBegan と touchesEnded) でもこれに気付きましたが、ハックすることができました。今回ではない。

アクションが UIGestureRecognizer にどのように見えるかを次に示します。

  1. ピンチが始まると、UIGestureRecognizerStateBegan。
  2. n UIGestureRecognizerStateChangeds、ピンチが変化します。
  3. ユーザーが指を 離すと気まずい沈黙。
  4. ユーザーが指を入れ替えると、おそらく遠く離れた場所で 気まずい沈黙。
  5. ピンチが再開すると、別の UIGestureRecognizerStateChanged。5.

ここで、ステップ 5 で問題が発生しました。ステップ 3 と 4 ではハンドラーで何も起きていないため、ハンドラーは 2 と 5 の UIGestureRecognizerStateChangeds から完全にシームレスに移行しますが、多くのことが発生しています。ハンドラーには、ハンドラー メッセージの間に反抗的な指が何百もの画面単位を移動する可能性があり、ユーザーが信じられないほど速くピンチしたように見えます。

ここで、ユーザーが実際にそれほど速くピンチすることが不可能な場合は、更新間の許容指位置デルタにしきい値を設定するだけで済みます。しかし、それは可能です。素早いピンチ ジェスチャでは、ユーザーの指が非常に長い距離を移動する可能性があります。問題のクライマックスはこれです。UIPinchGestureRecognizer が上記の奇妙な状況と素早いピンチ ジェスチャを区別できない。私はこれらを非常に異なる方法で処理する必要があり、今のところ違いを見分ける方法がまったくありません. 指が画面から離れたときに OS が教えてくれないのはなぜですか? これは静電容量式スクリーン ハードウェアの機能ですか、それとも OS のバグですか? それとも…意図的なデザイン…?

4

1 に答える 1

0

UIPinchGestureRecognizer に指のタッチダウンまたはタッチを通知させるためにできることは何もないと思いますが、独自のジェスチャ認識サブクラスを作成することはできます。以下は、ピンチに似たスケール ジェスチャ レコグナイザーで、1 回のタッチと 2 回のタッチのジェスチャを区別します (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 に答える