10

イベント swipeWithEvent を使用して、webView で前後に移動する機能を実装したいと考えています。ただし、これをどのように使用するかはわかりません。

メインの webView が 1 つと、前後に移動する 2 つのメソッドがあります。ここでの大きな問題は、この質問の書き方が正確にわからないことです。webView でスワイプ ジェスチャを認識し、2 つのメソッドを呼び出す方法を知る必要があるだけです。Safari、Google Chrome、Mozilla Firefox と同様です。ありがとう。

編集 前後にスワイプできると思われるこれらのメソッドを実装しました。

- (void)swipeWithEvent:(NSEvent *)event {
    NSLog(@"Swipe With Event");
    CGFloat x = [event deltaX];
    //CGFloat y = [event deltaY];

    if (x != 0) {
        (x > 0) ? [self goBack:self] : [self goForward:self];
    }
}


-(BOOL)recognizeTwoFingerGestures
{
    NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
    return [defaults boolForKey:@"AppleEnableSwipeNavigateWithScrolls"];
}

- (void)beginGestureWithEvent:(NSEvent *)event
{
    if (![self recognizeTwoFingerGestures])
        return;

    NSSet *touches = [event touchesMatchingPhase:NSTouchPhaseAny inView:nil];

    self.twoFingersTouches = [[NSMutableDictionary alloc] init];

    for (NSTouch *touch in touches) {
        [twoFingersTouches setObject:touch forKey:touch.identity];
    }
}

- (void)endGestureWithEvent:(NSEvent *)event
{
    if (!twoFingersTouches) return;

    NSSet *touches = [event touchesMatchingPhase:NSTouchPhaseAny inView:nil];

    // release twoFingersTouches early
    NSMutableDictionary *beginTouches = [twoFingersTouches copy];
    self.twoFingersTouches = nil;

    NSMutableArray *magnitudes = [[NSMutableArray alloc] init];

    for (NSTouch *touch in touches)
    {
        NSTouch *beginTouch = [beginTouches objectForKey:touch.identity];

        if (!beginTouch) continue;

        float magnitude = touch.normalizedPosition.x - beginTouch.normalizedPosition.x;
        [magnitudes addObject:[NSNumber numberWithFloat:magnitude]];
    }

    // Need at least two points
    if ([magnitudes count] < 2) return;

    float sum = 0;

    for (NSNumber *magnitude in magnitudes)
        sum += [magnitude floatValue];

    // Handle natural direction in Lion
    BOOL naturalDirectionEnabled = [[[NSUserDefaults standardUserDefaults] valueForKey:@"com.apple.swipescrolldirection"] boolValue];

    if (naturalDirectionEnabled)
        sum *= -1;

    // See if absolute sum is long enough to be considered a complete gesture
    float absoluteSum = fabsf(sum);

    if (absoluteSum < kSwipeMinimumLength) return;

    // Handle the actual swipe
    if (sum > 0)
    {
        [self goForward:self];
    } else
    {
        [self goBack:self];
    }


}

ただし、このコードは何もしていません。まったく呼び出されていないようです。

4

3 に答える 3

21

最新の OS (Lion 以降) の場合、これは「Y で X を実行するにはどうすればよいですか?」の 1 つです。答えが「Y を使用しない」である質問。

-swipeWithEvent:10.6 スタイルのトラックパッド スワイプを実装するために使用され、画面上のコンテンツはスワイプで移動しません。ほとんどの Mac トラックパッドは、この種のスワイプを許可するように構成されていません。トラックパッド設定ペインの「ページ間でスワイプ」設定を使用するには、「3 本の指でスワイプ」に設定する必要があります。これは、デフォルト設定でも、ユーザーが変更する一般的な設定でもありません。

代わりに、ライオン スタイルの「滑らかなスワイプ」がスクロール イベントとして発生します。Xcode SDK の PictureSwiper サンプル プロジェクトは、開始するのに適した場所ですが、概要として、次のことを行います。

10.8+ のみをサポートする必要がある場合

NSPageController履歴スタック モードで使用します。

10.7 をサポートする必要がある場合

  1. 少なくともこの機能については、10.8+ のみをサポートすることを真剣に検討してください。手動で実装するのは非常に面倒です。私があなたに警告しなかったとは言わないでください。

  2. スワイプ可能にしたいビューのスーパービューであるカスタム ビューを作成します。

  3. オーバーライドして適切な軸-wantsScrollEventsForSwipeTrackingOnAxis:に戻ります。YESSafari スタイルの前後のナビゲーションを行っている場合、それはNSEventGestureAxisHorizontal.

  4. 深呼吸する; これはすごいです。

  5. -scrollWheel:カスタム ビューでオーバーライドします。オーバーライドは を呼び出す必要があります-trackSwipeEventWithOptions:dampenAmountThresholdMin:max:usingHandler:。大まかに言えば、減衰量のしきい値の最小値は、ユーザーが左にスワイプできるデータの表示回数であり、最大値は右にスワイプできる回数です。ハンドラーは、ユーザーがスワイプすると繰り返し呼び出されるブロックです。そうすべき:

    1. NSImageView戻る/進むページのスクリーンショットをコンテンツ ビューの後ろに配置します。
    2. ユーザーの動きに合わせてコンテンツ ビューを移動します。このgestureAmountパラメーターは、減衰量のしきい値と同様に、(分数で、場合によっては負の) アイテム数であることに注意してください。コンテンツ ビューを正しく配置するには、ビューの幅を掛ける必要があります。
    3. ジェスチャ フェーズが の場合NSEventPhaseEnded、 を評価しgestureAmountて、ユーザーがジェスチャを完了したかどうかを判断します。そうでない場合は、コンテンツ ビューをアニメーション化して元の位置に戻します。その場合は、コンテンツ ビューをアニメーションなしで元の位置に戻し、スクリーンショットと一致するように更新します。

ご覧のとおり、ハンドラーを実際に実装するのは非常に複雑で、すべての詳細については説明していません。これらすべての詳細があっても、高度なスキルを持つプログラマーは、これを正しく行うためにおそらく数日を費やさなければならないでしょう. 10.7 SDK の PictureSwiper サンプルは、開始するのに適した場所です。(PictureSwiper の 10.8 バージョンは を使用しNSPageControllerます。あなたがすべきことと同じです。)

于 2012-12-05T05:39:32.393 に答える
10

10.7 リリース ノートから:

Fluid スワイプ トラッキング - API

次の API を使用すると、滑らかなスワイプ ジェスチャとしてジェスチャ スクロール ホイール イベントを追跡できます。iOS と同様に、NSScrollView は必要に応じて 1 回バウンスし、必要に応じてジェスチャ スクロール ホイール イベントを渡して、コントローラがこの API を使用してスクロール ジェスチャを滑らかなスワイプ ジェスチャとして追跡できるようにします。

ScrollWheel NSEvents が -phase メッセージに応答するようになりました。スクロールには次の 3 種類があります。

1) ジェスチャー スクロール - これらは NSEventPhaseBegan で始まり、多数の NSEventPhaseChanged イベントを持ち、ユーザーが NSEventPhaseEnded で指を離すと終了します。

2) Momentum スクロール - これらには NSEventPhase none のフェーズがありますが、NSEventPhaseBegan/NSEventPhaseChanged/NSEventPhaseEnded の MomentumPhase があります。

3) 従来のスクロール - これらのスクロール ホイール イベントには、NSEventPhaseNone のフェーズと NSEventPhaseNone の MomentumPhase があります。ユーザーが従来のスクロールの実行をいつ開始または停止するかを判断する方法はありません。

NSScrollView は、すべてのジェスチャ スクロール ホイール イベントを処理し、レスポンダー チェーンに渡しません。多くの場合、スワイプの追跡は、たとえば WindowController レベルなど、レスポンダー チェーンの上位で行われます。iOS スタイルの「端にいないときはバウンス、それ以外の場合はスワイプ」動作を実現するには、必要に応じてスクロール ホイール メッセージを転送する必要があることを NSScrollView に通知する必要があります。NSScrollView のプロパティを手動で設定する代わりに、NSResponder は次のメソッドを実装して YES を返すことができます。

- (BOOL)wantsScrollEventsForSwipeTrackingOnAxis:(NSEventGestureAxis)axis;

適切なコントローラーが非 NSEventNone フェーズの -scrollWheel: メッセージを受信すると、そのイベント インスタンスで次のメッセージを呼び出して、ユーザーによるイベントの完了とアニメーションの完了の両方にスワイプまたはスクロールを追跡できます。

enum {
    NSEventSwipeTrackingLockDirection = 0x1 << 0,
    NSEventSwipeTrackingClampGestureAmount = 0x1 << 1
};

typedef NSUInteger NSEventSwipeTrackingOptions;

@interface NSEvent ...
- (void)trackSwipeEventWithOptions:(NSEventSwipeTrackingOptions)options
          dampenAmountThresholdMin:(CGFloat)minDampenThreshold
                               max:(CGFloat)maxDampenThreshold
                      usingHandler:(void (^)(CGFloat gestureAmount, NSEventPhase phase, > BOOL isComplete, BOOL *stop))handler;
...

以下は、iOS の写真アプリのように画像のコレクションをスワイプする擬似コードの例です。

- (BOOL)wantsScrollEventsForSwipeTrackingOnAxis:(NSEventGestureAxis)axis {
    return (axis == NSEventGestureAxisHorizontal) ? YES : NO; }
- (void)scrollWheel:(NSEvent *)event {
    // NSScrollView is instructed to only forward horizontal scroll gesture events (see code above). However, depending
    // on where your controller is in the responder chain, it may receive other scrollWheel events that we don't want
    // to track as a fluid swipe because the event wasn't routed though an NSScrollView first.
    if ([event phase] == NSEventPhaseNone) return; // Not a gesture scroll event.
    if (fabsf([event scrollingDeltaX]) <= fabsf([event scrollingDeltaY])) return; // Not horizontal
    // If the user has disabled tracking scrolls as fluid swipes in system preferences, we should respect that.
    // NSScrollView will do this check for us, however, depending on where your controller is in the responder chain,
    // it may scrollWheel events that are not filtered by an NSScrollView.
    if (![NSEvent isSwipeTrackingFromScrollEventsEnabled]) return;
    if (_swipeAnimationCanceled && *_swipeAnimationCanceled == NO) {
        // A swipe animation is still in gestureAmount. Just kill it.
        *_swipeAnimationCanceled = YES;
        _swipeAnimationCanceled = NULL;
    }
    CGFloat numPhotosToLeft = // calc num of photos we can move to the left and negate
    CGFloat numPhotosToRight = // calc num of photos we can move to the right
    __block BOOL animationCancelled = NO;
    [event trackSwipeEventWithOptions:0 dampenAmountThresholdMin:numPhotosToLeft max:numPhotosToRight
                         usingHandler:^(CGFloat gestureAmount, NSEventPhase phase, BOOL isComplete, BOOL *stop) {
        if (animationCancelled) {
            *stop = YES;
            // Tear down animation overlay
            return;
        }
        if (phase == NSEventPhaseBegan) {
            // Setup animation overlay layers
        }
        // Update animation overlay to match gestureAmount
        if (phase == NSEventPhaseEnded) {
            // The user has completed the gesture successfully.
            // This handler will continue to get called with updated gestureAmount
            // to animate to completion, but just in case we need
            // to cancel the animation (due to user swiping again) setup the
            // controller / view to point to the new content / index / whatever
        } else if (phase == NSEventPhaseCancelled) {
            // The user has completed the gesture un-successfully.
            // This handler will continue to get called with updated gestureAmount
            // But we don't need to change the underlying controller / view settings.
        }
        if (isComplete) {
            // Animation has completed and gestureAmount is either -1, 0, or 1.
            // This handler block will be released upon return from this iteration.
            // Note: we already updated our view to use the new (or same) content
            // above. So no need to do that here. Just...
            // Tear down animation overlay here
            self->_swipeAnimationCanceled = NULL;
        }
    }];
    // We keep a pointer to a BOOL __block variable so that we can cancel our
    // block handler at any time. Note: You must assign the BOOL pointer to your
    // ivar after block creation and copy!
    self->_swipeAnimationCanceled = &animationCancelled; }

他の解決策は、ジェスチャーを認識するためにタッチイベントを直接処理することです。詳細については、ドキュメントを参照してください。

于 2012-12-05T06:10:51.977 に答える
0

NSViewController クラスでメソッドを使用touchesBeganWithEvent:しました。touchesEndedWithEvent:以下のコードは単なる大雑把な例です。どういうわけか、swipeWithEvent:決して解雇されません。だから私はこれをあきらめました(swipeWithEvent)。次のコードは、High Sierra で Xcode 10 を使用してテストされました (View Controller の実装セクション内)。

- (void)viewDidAppear {
       [super viewDidAppear];

[self.view setAcceptsTouchEvents:YES]; } float beginX, endX; - (void)touchesBeganWithEvent:(NSEvent *)event { if(event.type == NSEventTypeGesture){ NSSet *touches = [event touchesMatchingPhase:NSTouchPhaseAny inView:self.view]; if(touches.count == 2){ for (NSTouch *touch in touches) { beginX = touch.normalizedPosition.x; } // since there are two touches, beginX will always end up with the data from the second touch } } } - (void)touchesEndedWithEvent:(NSEvent *)event { if(event.type == NSEventTypeGesture){ NSSet *touches = [event touchesMatchingPhase:NSTouchPhaseAny inView:self.view]; if(touches.count == 2){ for (NSTouch *touch in touches) { endX = touch.normalizedPosition.x; } // since there are two touches, endX will always end up with the data from the second touch if (endX > beginX) { NSLog(@"swipe right!"); } else if (endX < beginX) { NSLog(@"swipe left!"); } else { NSLog(@"no swipe!"); } } } }

[self.view setAcceptsTouchEvents:YES];声明は非常に重要です。すべてのビューが表示された後にタッチ情報の受け入れを開始するように、viewDidAppear メソッドに入れました。これをサブクラスで使用している場合は、 に変更self.viewするだけselfで済みます (初期化または のどこかに "setAcceptsTouchEvents" も必要ですdrawRect)。これは、左右のスワイプを検出する単なる例です。上下にスワイプするには、touch.normalizedPosition.yデータを使用する必要があります。

NSWindowController サブクラス内で使用している場合は、windowDidLoad にこの行が必要ですself.window.contentView.acceptsTouchEvents=YES;。setAcceptsTouchEvents は上位バージョンでは既に廃止されているため、これらすべてが機能するために OSX ターゲットを 10.10 に設定する必要がある場合もあります。

于 2020-04-14T06:29:47.263 に答える