【问题标题】:UITableView inside UIScrollView not receiving first tap after scrolllingUIScrollView中的UITableView在滚动后没有收到第一次点击
【发布时间】:2014-03-07 18:54:35
【问题描述】:

简介

我在UIScrollView 中遇到UITableView 的问题。当我滚动外部scrollView 时,table 在第一次触摸时不会收到willSelect/didSelect 事件,但在第二次触摸时会收到。更奇怪的是,即使delegate 没有,单元格本身也会获得触摸和高亮状态。

详细说明

我的视图层次结构:

UIView
  - UIScrollView  (outerscroll)
      - Some other views and buttons
      - UITableView (tableView)

在滚动视图中,我有一些额外的视图可以动态展开/关闭。表格视图需要与视图的其他一些元素一起“固定”在顶部,这就是我创建此布局的原因,它允许我以与 Apple 推荐的类似方式轻松移动元素,当使用转换时滚动发生。

当外卷轴像这样移动时,表格视图被转换成平移效果:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView == self.outerScrollView) {

        CGFloat tableOffset = scrollView.contentOffset.y - self.fixedHeaderFrame.origin.y;
        if (tableOffset > 0) {
            self.tableView.contentOffset = CGPointMake(0, tableOffset);
            self.tableView.transform = CGAffineTransformMakeTranslation(0, tableOffset);
        }
        else {
            self.tableView.contentOffset = CGPointMake(0, 0);
            self.tableView.transform = CGAffineTransformIdentity;
        }

        // Other similar transformations are done here, but not involving the table

}

在我的单元格中,如果我实现这些方法:

- (void)setSelected:(BOOL)selected {
    [super setSelected:selected];
    if (selected) {
        NSLog(@"selected");
    }
}

- (void)setHighlighted:(BOOL)highlighted animated:(BOOL)animated
{
    [super setHighlighted:highlighted animated:animated];
    if (highlighted) {
        NSLog(@"highlighted");
    }
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    NSLog(@"touchesBegan");
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];
    NSLog(@"touchesEnded");
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [super touchesCancelled:touches withEvent:event];
    NSLog(@"touchesCancelled");
}

Y 在失败时可以看到这个输出(第一次点击):

2014-02-10 13:04:40.940 MyOrderApp[5588:70b] highlighted 
2014-02-10 13:04:40.940 MyOrderApp[5588:70b] touchesBegan 
2014-02-10 13:04:40.978 MyOrderApp[5588:70b] touchesEnded

而这个工作时(第二次点击):

2014-02-10 13:05:30.359 MyOrderApp[5588:70b] highlighted 
2014-02-10 13:05:30.360 MyOrderApp[5588:70b] touchesBegan 
2014-02-10 13:05:30.487 MyOrderApp[5588:70b] touchesEnded 
2014-02-10 13:05:30.498 MyOrderApp[5588:70b] expanded

在第一次和第二次点击之间没有进行其他帧更改、动画或任何其他视图交互。此外,只有在大量滚动时才会出现错误,但仅滚动几个像素时,一切都会按预期工作。

我也尝试更改一些属性,但没有成功。我做过的一些事情:

  • 从滚动和表格以外的视图中删除 userInteractionEnabled
  • 在发生scrollViewDidScroll 时,在表格、滚动和主视图上添加对setNeedsLayout 的调用。
  • 从表中删除转换(仍然发生)

我看到了一些关于将UITableViews 嵌入UIScrollViews 的意外行为,但我在Apple 的官方文档中看不到这样的警告,所以我期待它能够工作。

该应用仅适用于 iOS7+。

问题

有没有人遇到过类似的问题?为什么会这样?我该如何解决?我认为我可以拦截单元格上的点击手势并使用自定义委托或类似的方式传递它,但是 我希望表格接收正确的事件,所以我的 UITableViewDelegate 收到符合预期。

更新

  • 我尝试按照评论中的建议禁用单元格重用,但它仍然以相同的方式发生。

【问题讨论】:

  • 你试过[yourScroll bringSubviewToFront: yourTable]; ?
  • 您提到这仅在您滚动很多时才会发生,这让我认为 tableCells 的重新创建/回收可能会影响这一点?能不能在 cellForRowAtIndexPath: 函数里面放一个 NSlog,看看这个 bug 是不是在被调用的时候触发了?
  • @Ilario 谢谢,但我已经试过了。此外,单元格正在接触(失败时和工作时),并且它们在表格内,因此表格位置似乎不是问题。
  • @JackWu 这实际上是一个非常好的提示,但它也不起作用。我已经删除了单元重用来对其进行测试,并且仍然以相同的方式发生。 :(
  • 请问您为什么在滚动视图中使用表格视图?这就像在滚动视图上有一个滚动视图......

标签: ios objective-c uitableview uiscrollview


【解决方案1】:

我建议寻找选项,例如当您实际滚动外部滚动视图时不让您的单元格处于突出显示状态,这很容易处理并且是推荐的方式。你可以通过使用一个布尔值并在下面的方法中切换它来做到这一点

- (void)scrollViewDidScroll:(UIScrollView *)scrollView 

【讨论】:

  • 我不明白这个答案。我的问题是cell接收到touch事件但是table没有调用didSelectRowAtIndexPath,和高亮状态无关。
  • @AngelGarcíaOlloqui 从 Apple 的角度思考。在滚动最外面的滚动视图时,您如何区分表格视图单元格的意外触摸。要调用表视图的 didSelectRowAtIndexPath 方法可能有一个最小阈值持续时间。我敢肯定,一旦您的外部滚动视图没有滚动或已经减速其滚动运动,它就会被完美地调用。最终用户可以轻松理解该场景,因此您不必担心这个问题。
  • 这是有道理的。但是,在我的情况下,我可以在表格完成滚动后重现此问题,甚至在那之后几秒钟。
【解决方案2】:

您的 UiTableView 似乎无法识别您的点击。你试过用那个吗:

- (BOOL)gestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:(UISwipeGestureRecognizer             *)otherGestureRecognizer
{
    if ([otherGestureRecognizer.view isKindOfClass:[UITableView class]]) {
        return YES;
    }
    return NO;
}

来自苹果的注解:

当对gestureRecognizer 或otherGestureRecognizer 之一的识别被另一个阻止时调用。返回 YES 以允许两者同时识别。默认实现返回 NO(默认情况下不能同时识别两个手势)

注意:返回 YES 保证允许同时识别。不保证返回 NO 会阻止同时识别,因为其他手势的委托可能会返回 YES

希望这会有所帮助。

【讨论】:

    【解决方案3】:

    Apple Documentation,您不应该在UIScrollView 中嵌入UITableView

    重要提示:您不应将 UIWebView 或 UITableView 对象嵌入 UIScrollView 对象。如果这样做,可能会导致意外行为 因为两个对象的触摸事件可能会混淆和错误 处理。

    您的问题确实与您的UIScrollView 所做的有关。

    但如果只是在需要时隐藏 tableview(这是我的情况),您可以在其父视图中移动 UITableView

    我在这里写了一个小例子:https://github.com/rvirin/SoundCloud/

    【讨论】:

    【解决方案4】:

    手势识别器无法对两个嵌入式滚动视图或子类正常工作。

    尝试解决方法:

    1. 使用透明、自定义和覆盖单元格 UIButton 中的所有内容,并使用适当的标记或子类 UIButton 并添加索引路径属性并在每次重用单元格中覆盖。

    2. 将此按钮作为属性添加到您的自定义单元格中。

    3. 为所需的UIControlEvent(一个或多个)添加目标,该目标指向您的 UITableViewDelegate 协议采用类。

    4. 禁用 IB 中的选择,并手动管理代码中的选择。

    此解决方案需要注意单选/多选的情况。

    【讨论】:

    • 我认为没有手势识别器。但是,与解决方法类似的事情是我现在正在做的事情,但是它需要一些肮脏的技巧来模仿表格视图的点击行为。我想删除它:(
    • 手势识别器的意思是某些类使用类似的行为,例如UITableViewCell 通过点击或长按手势被选中。我提出的解决方法适用于两个嵌入式 UITableView 类(我希望有可以通过滑动导航的水平表格行)导致 UI 行为与预期一样(完美管理单元格选择和滑动两个表格视图)。如果你愿意,我可以分享代码。
    • 它肯定有点hacky。但是如果你真的“别无选择”嵌套例如滚动视图中的 tableView,此解决方案是其他同样“hacky”解决方案中最好和最简单的。我最终在 cellForRowAtIndexPath 中为 tableViewCell 提供了一个 buttonPushed 闭包,而我的闭包只调用了 tableView.selectRow ... 然后是 didSelectCell。效果很好。
    【解决方案5】:

    滚动视图试图确定用户的意图是否是滚动,因此它故意延迟初始触摸。您可以通过将delaysContentTouches 设置为NO 来关闭此功能。

    【讨论】:

    • 感谢 Stepan,这实际上是我最初的想法,但我已经尝试将该属性设置为 NO,并且似乎发生了同样的错误。
    【解决方案6】:

    正如其他答案所说,您不应该将表格视图放在滚动视图中。无论如何, UITableView 从 UIScrollView 继承,所以我想这就是事情变得混乱的地方。在这种情况下我总是做的是:

    1) 子类 UITableViewController 并包含一个属性 UIView *headView。

    2) 在父 UIViewController 中创建容器 UIView 中的所有顶级内容

    3) 初始化您的自定义 UITableView 并将 tableView 的视图添加到视图控制器的全尺寸

    [self.view addSubview: self.myTableView.view];
    

    4) 将 headView 设置为您的 UIView gubbins

    self.tableView.headView = myHeadViewGubbins.
    

    5) 在 tableViewController 方法中

    -(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger *)section;
    

    做:

    if ( section == 0 ) {
        return self.headView;
    }
    

    现在你有一个表格视图,顶部有一堆其他的 shizzle。

    享受吧!

    【讨论】:

    • 感谢您的建议 Martin,但这也可以用于内容插入和其他一些方式。但是,正如我上面解释的那样,这不是我的 UI 所需要的。还是谢谢。
    【解决方案7】:

    将内部 UITableView 的 scrollEnabled 属性设置为 YES。这让内部UITableView 知道正确处理UIScrollView 上的滚动相关触摸。

    【讨论】:

    • 感谢这为我节省了一些时间。我正在移动一个 tableView 坐标并故意将 scrollEnabled 设置为 NO 并发现单元格确实选择了仅在第二次尝试时才注意到。重新启用 scrollEnabled 解决了它。谢谢
    • 这就是答案。是的,确实不支持在 scrollView 中嵌入 tableView,但是如果您从来没有遵守规则(像我一样),这就是解决上述问题的方法!我还要补充一点,任何嵌入式 tableViews 都应该将 bounces 设置为 NO 以避免潜在的奇怪滚动行为。
    • 我在另一个“tableview 单元格”中有一个 tableview,我遇到的问题是内部表格单元格确实选择仅在第二次尝试时被识别。将内部 UITableView 的 scrollEnabled 属性更改为 @987654328 @ 和 bouncesNO 解决了这个问题。
    【解决方案8】:

    我对嵌套的 UITableView 也有同样的问题,并找到了解决方法:

    innerTableView.scrollEnabled = YES;
    innerTableView.alwaysBounceVertical = NO;
    

    您需要将内部表格视图的高度设置为与其单元格的总高度相匹配,这样当用户滚动外部视图时它就不会滚动。

    希望这会有所帮助。

    【讨论】:

    • 但是这种方法的问题是您将一次加载所有单元格,从而产生性能问题(我的表有数百个单元格)。我需要内部滚动才能从单元重用和延迟加载中获得所有好处。
    • 此外,如果您在用户滚动到末尾时自动加载更多单元格,那么这将继续加载更多单元格。
    【解决方案9】:

    我的错误是实现了委托方法:

    - (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
    

    而不是我打算实现的那个:

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    

    因此只在被点击的第二个单元格上被调用,因为那是第一个单元格被de选中的时候。在自动完成的帮助下犯了愚蠢的错误。对于那些可能在这里徘徊但没有意识到自己也犯了同样错误的人来说,这只是一个想法。

    【讨论】:

    • 我曾经犯过这种史诗般的错误,需要半天时间才能意识到这种错误。呵呵!
    【解决方案10】:

    我在一些遗留代码的 UIScrollView 中遇到了一个 UITableView,其 scrollEnabled 为 NO。我无法轻松更改现有层次结构,也无法启用滚动,但针对第一次点击问题提出了以下解决方法:

    @interface YourOwnTableView : UITableView
    @end
    
    @implementation YourOwnTableView
    
    - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    
        [super touchesCancelled:touches withEvent:event];
    
        // Note that this is a hack and it can stop working at some point.
        // Looks like a table view with scrollEnabled being NO does not handle cancellation cleanly,
        // so let's repeat begin/end touch sequence here hoping it'll reset its own internal state properly
        // but won't trigger cell selection (the touch passed is in its cancelled phase, perhaps there is a part
        // of code inside which actually checks it)
        [super touchesBegan:touches withEvent:event];
        [super touchesEnded:touches withEvent:event];
    }
    
    @end
    

    同样,这只是在我的特定情况下工作的一种解决方法。在滚动视图中包含表格视图仍然是错误的。

    【讨论】:

    • 迄今为止,解决方案对我仍然有效。在我看来似乎是最好的解决方案
    【解决方案11】:

    我遇到了同样的问题并找到了解决方案!

    您需要在滚动视图上将 delaysTouchesBegan 设置为 true,以便滚动视图将其失败的滚动手势(即点击)发送给其子级。

    var delaysTouchesBegan: Bool - 一个布尔值,用于确定接收器是否延迟在开始阶段向其视图发送触摸。

    当属性值为YES时,窗口暂停传递 在 UITouchPhaseBegan 阶段触摸对象到视图。如果 手势识别器随后识别其手势,这些触摸 对象被丢弃。但是,如果手势识别器不 识别它的手势,窗口将这些对象传递给视图 在 touchesBegan:withEvent: 消息中(可能还有后续 touchesMoved:withEvent: 通知它当前触摸的消息 地点)。

    https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIGestureRecognizer_Class/index.html#//apple_ref/occ/instp/UIGestureRecognizer/delaysTouchesBegan

    但是有一个问题……如果你直接在滚动视图上这样做是行不通的!

    // Does NOT work
    self.myScrollview.delaysTouchesBegan = true
    

    显然这是一个 iOS 错误,设置此属性不起作用(感谢苹果)。但是有一个简单的解决方法:直接在滚动视图的平移手势上设置属性。果然,这对我很有效:

    // This works!!
    self.myScrollview.panGestureRecognizer.delaysTouchesBegan = true
    

    【讨论】:

    • 谢谢!这对我有用。这可能不是解决整个问题的方法,但它让我可以解决眼前的问题,而无需重构所有内容以从滚动视图中删除表格视图。
    【解决方案12】:

    它,如果触摸表格视图它将正常工作。在同一个视图控制器中也有滚动视图。

    tableview.scrollEnabled = true;
    

    【讨论】:

      【解决方案13】:

      我也有同样的问题,那就参考lxx所说的“Nesting Scroll Views”吧。

      https://developer.apple.com/library/ios/documentation/WindowsViews/Conceptual/UIScrollView_pg/NestedScrollViews/NestedScrollViews.html

      可以在股票应用程序中找到横向滚动的示例。顶视图是表格视图,而底视图是使用分页模式配置的水平滚动视图。虽然它的三个子视图中有两个是自定义视图,但第三个视图(包含新闻文章)是一个 UITableView(UIScrollView 的子类),它是水平滚动视图的子视图。水平滚动到新闻视图后,您可以垂直滚动其内容。

      这是工作

      【讨论】:

        【解决方案14】:
        1. 在您的 UITableViewCell 上放置一个 UIButton 并将出口创建为“btnRowSelect”。
        2. 在您的视图控制器中将此代码放入 cellForRowAtIndexPath

          cell.btnRowSelect.tag = indexPath.row
          cell.btnRowSelect.addTarget(self, action: Selector("rowSelect:"), forControlEvents: .TouchUpInside)
          
        3. 将此函数也添加到您的 viewController-

          func rowSelect (sender:UIButton) {
          // "sendet.tag" give you the selected row 
          // do whatever you want to do in didSelectRowAtIndexPath
          }
          

        此函数“rowSelect”将作为 didSelectRowAtIndexPath 工作,其中 你得到的行“indexPath.row”为“sender.tag”

        【讨论】:

        • 最佳和最简单的解决方案!谢谢!
        猜你喜欢
        • 1970-01-01
        • 2015-01-20
        • 1970-01-01
        • 1970-01-01
        • 2011-01-07
        • 1970-01-01
        • 1970-01-01
        • 2016-07-30
        相关资源
        最近更新 更多