如何正确使用swipeWithEvent来导航webView,Obj-C

Jos*_*iah 9 cocoa objective-c event-handling

我想通过使用Event swipeWithEvent实现在webView中导航回第四个的能力.但是,我不知道我是如何使用它的.

我有一个主要的webView,以及两个导航回来和第四个的方法.这里的一个大问题是我不确定如何写这个问题.我只需要知道如何识别我的webView上的滑动手势并调用我的两种方法.与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];
    }


}
Run Code Online (Sandbox Code Playgroud)

但是,此代码没有做任何事情.它似乎根本没有被调用.

Bre*_*don 19

对于现代操作系统(Lion和更新版本),这是"我如何用Y做X?"之一.问题的答案是"不要使用Y."

-swipeWithEvent:用于实现10.6样式的触控板滑动,其中屏幕上的内容不随着滑动一起移动.大多数Mac触控板都没有配置为允许这种滑动; Trackpad pref窗格中的"在页面之间滑动"首选项必须设置为"用三根手指轻扫"才能使其可用,这既不是默认设置,也不是用户更改的常用设置.

狮子式的"流畅的滑动"代替滚动事件.Xcode SDK中的PictureSwiper示例项目是一个很好的起点,但作为概述,这是您的工作:

如果你只需要支持10.8+

使用NSPageController历史堆栈模式.

如果你需要支持10.7

  1. 认真考虑仅支持10.8+,至少对于此功能.手动实现它是一个巨大的混乱.不要说我没有警告你.

  2. 创建一个自定义视图,该视图是您想要可刷卡的任何视图的超级视图.

  3. 覆盖-wantsScrollEventsForSwipeTrackingOnAxis:以返回YES适当的轴.如果您正在进行Safari风格的后退/前进导航,那就是NSEventGestureAxisHorizontal.

  4. 深吸一口气; 这个是一个很好的.

  5. -scrollWheel:在自定义视图中覆盖.您的覆盖应该调用-trackSwipeEventWithOptions:dampenAmountThresholdMin:max:usingHandler:.粗略地说,阻尼量阈值最小值是用户可以向左滑动的数据量,并且最大值是他们可以向右滑动的数量.处理程序是一个在用户滑动时反复调用的块; 这应该:

    1. NSImageView在您的内容视图后面放置一个屏幕截图,其中包含您要返回/转发的页面.
    2. 移动内容视图以匹配用户的移动.注意,该gestureAmount参数与阻尼量阈值一样,是(分数且可能是负数)项目数; 您必须将其乘以视图宽度才能正确定位内容视图.
    3. 如果手势阶段是NSEventPhaseEnded,则评估gestureAmount以确定用户是否完成了手势.如果他们没有,请将内容视图设置回原位; 如果他们这样做,将内容视图放回到没有动画的位置并更新它以匹配屏幕截图.

正如您所看到的,实际上实现处理程序非常复杂,我甚至没有描述所有细节.即使有了所有这些细节,一个技术娴熟的程序员也可能需要花几天时间才能做到这一点.10.7 SDK中的PictureSwiper示例是一个很好的起点.(PictureSwiper的10.8版本使用NSPageController.就像你应该做的那样.)


Ken*_*tzo 9

10.7发行说明:

流体滑动跟踪 - API

以下API允许将手势滚轮事件跟踪为流畅的滑动手势.与iOS类似,NSScrollView将在需要时弹跳一次,然后可选地传递手势滚轮事件,以便您的控制器可以使用此API跟踪滚动手势作为流畅的滑动手势.

ScrollWheel NSEvents现在响应-phase消息.有3种类型的卷轴:

1)手势滚动 - 这些以NSEventPhaseBegan开头,有许多NSEventPhaseChanged事件,并在用户用NSEventPhaseEnded抬起手指时终止.

2)动量卷轴 - 这些都没有NSEventPhase阶段,但是它们具有NSEventPhaseBegan/NSEventPhaseChanged/NSEventPhaseEnded的势头.

3)传统卷轴 - 这些滚轮事件具有NSEventPhaseNone阶段和NSEventPhaseNone的势头阶段.无法确定用户何时启动,也无法停止执行传统滚动.

NSScrollView处理所有手势滚轮事件,并且不会将它们传递给响应者链.通常,跟踪滑动在响应者链中更高,例如在WindowController级别.要实现iOS风格"不在边缘时弹跳,否则轻扫"行为,您需要通知NSScrollView它应该在适当时转发滚轮信息.您的NSResponder可以实现以下方法并返回YES,而不是在NSScrollView上手动设置属性.

- (BOOL)wantsScrollEventsForSwipeTrackingOnAxis:(NSEventGestureAxis)axis;
Run Code Online (Sandbox Code Playgroud)

当适当的控制器收到带有非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;
...
Run Code Online (Sandbox Code Playgroud)

下面是滑动像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; }
Run Code Online (Sandbox Code Playgroud)

其他解决方案是直接直接处理触摸事件以识别手势.有关更多信息,请考虑查看文档.