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

时间:2012-10-05 14:08:02

标签: objective-c cocoa 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];
    }


}

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

3 个答案:

答案 0 :(得分: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以确定用户是否完成了手势。如果他们没有,请将内容视图设置回原位;如果他们这样做,将内容视图放回原位,没有动画,并更新它以匹配屏幕截图。
  6. 正如您所看到的,实际上实现处理程序非常复杂,我甚至没有描述所有细节。即使有了所有这些细节,一个技术娴熟的程序员也可能需要花几天时间才能做到这一点。 10.7 SDK中的PictureSwiper示例是一个很好的起点。 (10.8版本的PictureSwiper使用NSPageController。就像你应该做的那样。)

答案 1 :(得分:9)

来自the 10.7 release notes

  

流体滑动跟踪 - 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;
     

当适当的控制器收到带有非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; }

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

答案 2 :(得分:0)

我在NSViewController类中使用了touchesBeganWithEvent:touchesEndedWithEvent:方法。以下代码只是一个粗略的示例。由于某些原因,swipeWithEvent:从未被解雇。所以我放弃了这个(swipeWithEvent)。以下代码在High Sierra中使用Xcode 10(在视图控制器的实现部分内部)进行了测试:

- (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(在初始化或drawRect中的某个位置也需要“ setAcceptsTouchEvents”)。这只是检测左右滑动的一个示例。要上下滑动,您需要使用touch.normalizedPosition.y数据。

如果在NSWindowController子类中使用它,则需要在windowDidLoad中的这一行self.window.contentView.acceptsTouchEvents=YES;。您可能还需要将OSX目标设置为10.10才能使所有这些功能正常工作,因为setAcceptsTouchEvents已经不支持更高版本。