我想通过使用Event swipeWithEvent来实现在webView中来回导航的功能。然而,我不知道该如何使用它。
我有一个主要的webView,以及两个向后导航和第四导航的方法。这里的一个大问题是,我不太确定如何写这个问题。我只需要知道如何识别webView上的滑动手势,并调用我的两个方法。与Safari、Google Chrome和Mozilla Firefox类似。谢谢。
EDIT我已经实现了这些方法,这些方法可以让我前后滑动。
- (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];
}
}
但是,这段代码没有任何作用。它似乎根本没有接到电话。
对于现代操作系统(Lion和更新版本),这是"我如何用Y做X?"问题之一,答案是"不要使用Y。">
-swipeWithEvent:
用于实现10.6风格的触控板滑动,其中屏幕上的内容不会随着滑动而移动。大多数Mac触控板不再被配置为允许这种滑动;轨迹板首选项窗格中的"在页面之间滑动"首选项必须设置为"用三个手指滑动"才能使用,这既不是默认设置,也不是用户需要更改的常见设置。
狮子风格的"流畅滑动"改为滚动事件。Xcode SDK中的PictureSwiper示例项目是一个很好的起点,但作为概述,以下是您要做的:
如果您只需要支持10.8+
在历史堆栈模式下使用NSPageController
。
如果您需要支持10.7
-
认真考虑只支持10.8+,至少在这个功能上是这样。手动实现它是一个巨大的混乱。不要说我没有警告你。
-
创建一个自定义视图,该视图是您想要滑动的任何视图的超级视图。
-
覆盖
-wantsScrollEventsForSwipeTrackingOnAxis:
以返回相应轴的YES
。如果你正在进行Safari风格的前后导航,那就是NSEventGestureAxisHorizontal
。 -
深呼吸;这是一个愚蠢的。
-
在自定义视图中覆盖
-scrollWheel:
。您的覆盖应该调用-trackSwipeEventWithOptions:dampenAmountThresholdMin:max:usingHandler:
。粗略地说,阻尼量阈值的最小值是用户可以向左滑动多少数据,最大值是他们可以向右滑动多少数据。处理程序是一个在用户滑动时重复调用的块;它应该:- 在内容视图后面放置一个
NSImageView
,其中包含要返回/转发的页面的屏幕截图 - 移动内容视图以匹配用户的移动。注意,
gestureAmount
参数与阻尼量阈值一样,是(分数的并且可能是负的)项目数量;您必须将它乘以视图宽度才能正确定位内容视图 - 如果手势阶段是
NSEventPhaseEnded
,则评估gestureAmount
以确定用户是否完成了手势。如果没有,请将内容视图设置为动画;如果他们这样做了,将内容视图放回原位,没有动画,并更新它以匹配屏幕截图
- 在内容视图后面放置一个
正如您所看到的,实际实现处理程序非常复杂,我甚至还没有描述所有的细节。即使有了所有这些细节,一个技术高超的程序员也可能需要花几天时间才能把它做好。10.7 SDK中的PictureSwiper示例是一个很好的起点。(10.8版本的PictureSwiper使用NSPageController
。就像你应该做的那样。)
来自10.7发行说明:
流体Swipe跟踪-API
以下API允许将手势滚轮事件作为流体滑动手势进行跟踪。与iOS类似,NSCrollView将在需要时反弹一次,然后可选地传递手势滚轮事件,以便控制器可以使用此API将滚动手势作为流体滑动手势进行跟踪。
ScrollWheel NSEvents现在响应-phase消息。有三种类型的卷轴:
1) 手势滚动-这些滚动以NSEventPhaseBegan开始,有许多NSEventPhaceChanged事件,当用户用NSEventPhase Ended抬起手指时终止。
2) 动量滚动-它们的相位为NSEventPhase none,但它们的动量相位为NSEeventPhaseBegan/NSEventPhaseChanged/NSEventPhaseEnded。
3) 传统滚动-这些滚轮事件的相位为NSEventPhaseNone,momentumPhase为NSEventPhaseNone。无法确定用户何时开始或停止执行传统滚动。
NSScrollView处理所有手势滚轮事件,不会将它们传递到响应程序链上。通常,跟踪滑动在响应程序链中的较高位置完成,例如在WindowController级别。为了实现iOS风格的"不在边缘时反弹,否则滑动"行为,您需要通知NSScrollView,它应该在适当的时候转发滚轮消息。您的NSResponder可以实现以下方法并返回YES,而不是手动在NSCrollView上设置属性。
- (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; }
其他解决方案是直接处理触摸事件以识别手势。有关更多信息,请考虑查看文档。
我在NSViewController类中使用了touchesBeganWithEvent:
和touchesEndedWithEvent:
方法。以下代码只是一个粗略的示例。出于某种原因,swipeWithEvent:
从未被解雇。所以我放弃了这个(swipeWithEvent)。以下代码在High Sierra中使用Xcode 10(在视图控制器的实现部分内)进行了测试:
-(无效)视图DidPear{[super-viewDidPeare];
[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在更高版本中已经被弃用。