在UITableView中检测水平滑动

32
我正在使用UIPanGestureRecognizer来识别UITableView中的水平滑动(确切地说是在单元格上,尽管它被添加到表格本身)。然而,这个手势识别器显然会从表格中夺取触摸。我已经让pangesturerecognizer识别水平滑动并随之弹回;但是,如果用户开始垂直滑动,它应该将该触摸的所有事件传递给tableview。
我尝试过的一件事是禁用识别器,但是那样它就无法滚动直到下一个触摸事件。所以我需要它立即传递事件。
我尝试过的另一件事是自己滚动,但是这样你会错过停止触摸后的持续速度。
以下是一些代码:
//In the viewdidload method
UIPanGestureRecognizer *slideRecognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(sliding:)];
[myTable addGestureRecognizer:slideRecognizer];



-(void)sliding:(UIPanGestureRecognizer *)recognizer
{
    if (recognizer.state == UIGestureRecognizerStateBegan)
    {
    CGPoint translation = [recognizer translationInView:favoritesTable];
    if (sqrt(translation.x*translation.x)/sqrt(translation.y*translation.y)>1) {
        horizontalScrolling = YES; //BOOL declared in the header file
        NSLog(@"horizontal");
        //And some code to determine what cell is being scrolled:
        CGPoint slideLocation = [recognizer locationInView:myTable];
        slidingCell = [myTable indexPathForRowAtPoint:slideLocation];
        if (slidingCell.row == 0) {
            slidingCell = nil;
        }

    }
    else
    {
        NSLog(@"cancel");
    }

    if (recognizer.state == UIGestureRecognizerStateEnded || recognizer.state == UIGestureRecognizerStateCancelled)
    {
    horizontalScrolling = NO;
    }


    if (horizontalScrolling)
    {
        //Perform some code
    }
    else
    {
    //Maybe pass the touch from here; It's panning vertically
    }

}

那么,有关于如何传递触摸的建议吗?

补充:我也考虑过子类化tableview的手势识别方法,首先检查它是否是水平的;然而,那么我需要原始代码,我想...不知道苹果公司会不会有问题。 另外:我没有子类化UITableView(控制器),只是单元格。这段代码在持有表格的视图控制器中 ;)


你为什么不在单个单元格上执行此操作? - drewag
代码是集中的,而且被调用的方法都在tableviewcontroller中。但即使它们在单元格本身上,识别器也会从tableview中窃取触摸。 - Erik S
1
你可以尝试手动使用触摸事件而不是手势识别器。除了最终识别滑动手势时,始终将事件传递回表视图。 - drewag
嗯,我想我打错了。实际上它不在tableview本身中,而是在持有tableviewcontroller的viewcontroller中:X没有tableviewcontroller子类,只有单元格的子类。@drewag:我可以尝试一下,不过需要找出如何使用...从未使用过除gesturerecognizers之外的任何东西。 - Erik S
你是否也知道如何将触摸事件传递给视图控制器?我可以自己编写代码来检查触摸事件,只需要知道在哪里传递即可。谢谢! - Erik S
显示剩余3条评论
5个回答

58

我曾经遇到过同样的问题,并想出了一个适用于UIPanGestureRecognizer的解决方案。

与Erik不同的是,我直接将UIPanGestureRecognizer添加到单元格中,因为我只需要一次支持pan的特定单元格。但我猜这对Erik的情况也应该适用。

以下是代码。

- (BOOL)gestureRecognizerShouldBegin:(UIPanGestureRecognizer *)gestureRecognizer
{
    UIView *cell = [gestureRecognizer view];
    CGPoint translation = [gestureRecognizer translationInView:[cell superview]];

    // Check for horizontal gesture
    if (fabsf(translation.x) > fabsf(translation.y))
    {
        return YES;
    }

    return NO;
}

水平手势的计算是从Erik的代码中复制的 - 我已经在iOS 4.3上进行了测试。

编辑: 我发现这种实现方式会阻止“滑动删除”手势。为了恢复该行为,我在上面的if语句中添加了手势速度的检查。

if ([gestureRecognizer velocityInView:cell].x < 600 && sqrt(translate...

在我的设备上玩了一会儿后,我得出了一个速度为500到600的结论,我认为这是在平移和滑动删除手势之间提供最佳用户体验的速度。


这么做的作用是,如果它不是水平手势,就将手势传递给其父视图(在本例中为tableview)? - Erik S
2
实际上并没有将手势传递给父视图。它将UIGestureRecognizer的状态设置为UIGestureRecognizerStateFailed。因此,父视图能够处理手势。这就是我认为它是如何工作的,但我不知道这是否在技术上是正确的。 - Florian Mielke
5
你或许可以简化 sqrt(translation.x * translation.x) ;) - Olie
这真是省时的。谢谢! - user376845
1
注意:如果不是立即明显的话,gestureRecognizerShouldBegin 是一个 UIGestureRecognizerDelegate 的代理方法。从文档中可以看到 - 返回值: 返回YES(默认),以告诉手势识别器继续解释触摸操作,返回NO则防止它尝试识别手势。 - Gavin Hope
显示剩余2条评论

7

我的答案与Florian Mielke的相同,但我进行了一些简化和修正。

如何使用:

只需为您的UIPanGestureRecognizer指定一个委托(UIGestureRecognizerDelegate)。例如:

UIPanGestureRecognizer *panner = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panDetected:)];
panner.delegate = self;
[self addGestureRecognizer:panner];

然后让该委托实现以下方法:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
    CGPoint translation = [(UIPanGestureRecognizer *)gestureRecognizer translationInView:gestureRecognizer.view.superview];
    return fabsf(translation.x) > fabsf(translation.y);
}

1
也许你可以使用 UISwipeGestureRecognizer 代替?你可以通过 direction 属性告诉它忽略上下滑动。

这样做不行,因为用户必须能够慢慢地滑动单元格。我确实考虑过使用滑动识别器。不幸的是,pangesturerecognizer没有方向方法 :/ - Erik S

1

您可以尝试手动使用触摸事件而不是手势识别器。除了最后识别滑动手势时,始终将事件传递回表视图。

每个继承自UIResponder的类都会有四个触摸函数(began、ended、canceled和moved)。因此,“转发”调用的最简单方法是在您的类中处理它,然后显式地调用下一个要处理它的对象(但您应该确保首先使用respondsToSelector:检查对象是否响应消息,因为它是一个可选函数)。这样,您可以检测任何想要的事件,并允许其他需要它的元素进行正常的触摸交互。


0
感谢这些技巧!我最终选择了一个UITableView子类,在其中检查移动是否为水平方向(在这种情况下,我使用自定义行为),否则调用[super touchesMoved: withEvent:]。
然而,我仍然不太明白为什么这样可以。我检查了一下,super是一个UITableView。看来我仍然没有完全理解这个层次结构。有人能试着解释一下吗?

1
UITableView有逻辑来检测触摸以实现滚动。作为子类,您正在覆盖此函数,因此如果您从未调用过函数的超级版本,则无法滚动。您基本上正在执行自己的算法,然后还允许默认算法运行。这与您在初始化程序中调用[super init]非常相似。您调用它以便不必重新实现超类所做的每件事情,只需介绍新子类中引入的内容即可。 - drewag
那部分对我来说听起来很合理;然而,它实际上在调用什么?子类的super到底是什么? - Erik S

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接