【问题标题】:iOS 8 Auto cell height - Can't scroll to last rowiOS 8自动单元格高度 - 无法滚动到最后一行
【发布时间】:2014-10-30 10:38:27
【问题描述】:

我正在使用 iOS 8 新的自调整单元格。从视觉上看,它工作得很好——每个单元格都有正确的大小。但是,如果我尝试滚动到最后一行,表格视图似乎不知道它的正确大小。这是一个错误还是有解决办法?

以下是重现问题的方法:

使用这个项目 - TableViewCellWithAutoLayoutiOS8(引用自 this SO answer),我得到了预期的自动调整大小的单元格。

但是,如果我调用 scrollToRowAtIndexPath 函数,像这样:

tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: model.dataArray.count - 1, inSection: 0), atScrollPosition: .Bottom, animated: true)

没有到达最后一行 - 它只会让我走到一半。

即使尝试使用这样的低级函数:

tableView.setContentOffset(CGPointMake(0, tableView.contentSize.height - tableView.frame.size.height), animated: true)

结果不如预期,不会走到最后。如果我多次单击它或等待片刻,最终它会到达正确的位置。似乎 tableView.contentSize.height 设置不正确,所以 iOS“不知道”最后一个单元格在哪里。

不胜感激。

谢谢

【问题讨论】:

  • 一些问题:它是否适用于开头的行?是否有停止工作的截止点?是一个部分的问题还是所有部分的问题?您的自动布局是否有任何错误?您估计的细胞高度与细胞的实际高度是否有很大差异?最后,如果您有可重复的错误,请报告给:bugreport.apple.com
  • 我插入的任何行索引都不准确。仅用 1 个部分进行了尝试。 IB/控制台中没有报告错误。估计的高度似乎是平均水平。我认为这似乎是一个错误,但也许这篇文章会有所帮助:) 谢谢。
  • 即使是 ios 8 和 Xcode GM 种子也没有修复这个 :(
  • 是时候记录一个错误了,尽管我很惊讶没有其他人遇到过这个问题。这种模式并不少见。
  • 我遇到了同样的问题。此错误的另一个副作用是,当您滚动到“底部”(或靠近该位置的某个位置)然后向上滚动时,表格视图会在从顶行重新计算正确的单元格大小时跳转。如果你 scroll:animated NO (它没有给表格视图加载顶部单元格的时间),就会发生这种情况。

标签: ios uitableview swift ios8


【解决方案1】:

更新:2015 年 6 月 24 日

Apple 已从 iOS 9.0 SDK 中解决了大部分此类错误。从 iOS 9 beta 2 开始,所有问题都已修复,包括在没有动画的情况下滚动到表格视图的顶部和底部,以及在表格视图中间滚动时调用 reloadData

以下是尚未修复的剩余问题:

  1. 使用较大的估计行高时,使用动画滚动到最后一行会导致表格视图单元格消失。
  2. 使用较小的估计行高时,使用动画滚动到最后一行会导致表格视图过早完成滚动,从而将一些单元格留在可见区域下方(最后一行仍在屏幕外)。

已针对这些与动画滚动相关的问题提交了新的错误报告 (rdar://21539211)。

原答案

这是一个 Apple 的关于 table view 行高估计的错误,自 iOS 7 首次引入此功能以来,它就一直存在。我直接与 Apple UIKit 工程师和开发人员就这个问题进行了合作——他们承认这是一个错误,但没有任何可靠的解决方法(没有禁用行高估计),并且似乎对修复它并不特别感兴趣。

请注意,该错误以其他方式表现出来,例如当您在部分或完全向下滚动时调用 reloadData 时表格视图单元格消失(例如,contentOffset.y 明显大于 0)。

显然,对于 iOS 8 的自调整单元格,行高估计至关重要,因此 Apple 确实需要尽快解决这个问题。

我已于 2013 年 10 月 21 日将此问题提交为 Radar #15283329。 请提交重复的错误报告,以便 Apple 优先修复。

您可以附上这个简单的sample project 来演示这个问题。它直接基于 Apple 自己的示例代码。

【讨论】:

  • 或者您可以使用github.com/slackhq/SlackTextViewController 来解决我们仍在等待 Apple 解决的问题
  • @abinop Slack 的文本视图控制器如何为 UITableView 提供通用案例替代/解决方法?我认为这仅适用于某些特定用例,当然不值得重新实现现有代码。
  • 这取决于一个人需要向下滚动列表有多糟糕,并且知道问题存在这么长时间并且可能很快没有解决方案。我将尝试为自己扩展 Slack看看进展如何。它可能对刚开始开发项目的人有用。
  • @abinop IIRC 他们在内部实现中使用颠倒的表格视图。我不会太兴奋:)
  • 我创建了一个可以清楚地显示问题所在的示例,可以在这里找到github.com/abinop/Auto-height-ios8-UITableView-scroll-problem。我会将其作为错误报告发布。
【解决方案2】:

这是一个非常烦人的错误,但我想我找到了一个永久的解决方案,尽管我无法完全解释原因。

在微小的(未被注意到的)延迟后调用函数:

let delay = 0.1 * Double(NSEC_PER_SEC)
let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))

dispatch_after(time, dispatch_get_main_queue(), {
  tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: model.dataArray.count - 1, inSection: 0), atScrollPosition: .Bottom, animated: true)
})

请告诉我这是否也适合你。

【讨论】:

  • 这对我有用。它允许我在尝试重置滚动位置之前完成对reloadData 的调用。我正在使用带有自动高度单元格的 iOS 8.1。
  • 这似乎对我不起作用,即使延迟时间更长。 =(
  • 这对我有用,但前提是我做了两次。一旦马上去错误的位置,然后延迟。我不确定它有多可靠。 stackoverflow.com/questions/28613995/…
  • 是的,这行得通,但是我尝试减少脏代码: dispatch_after(0, dispatch_get_main_queue(), ^{ [_table scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:_myGroup.shouts.count-1 inSection:0] atScrollPosition:UITableViewScrollPositionTop 动画:NO]; });
  • 这适用于 8.2。如果没有这种解决方法,滚动错误仍然存​​在。
【解决方案3】:

这绝对是 Apple 的错误。我也有这个问题。我通过两次调用“scrollToRowAtIndexPath”方法解决了这个问题示例代码是:

        if array.count > 0 {
        let indexPath: NSIndexPath = NSIndexPath(forRow: array.count - 1, inSection: 0)
        self.tblView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
        let delay = 0.1 * Double(NSEC_PER_SEC)
        let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay))

        dispatch_after(time, dispatch_get_main_queue(), {
            self.tblView.scrollToRowAtIndexPath(indexPath, atScrollPosition: .Bottom, animated: true)
        })
    }

【讨论】:

  • 这对我来说适用于 iOS9,谢谢!我稍微修改了一下,改用dispatch_async(dispatch_get_main_queue()...,这样就没有可确定的延迟,只是在主队列空闲的时候。
  • 我可以确认这是唯一的走动方式(iOS9~iOS11 测试),谢谢你,救救我!
  • 你拯救了我的一天!谢谢。
【解决方案4】:

在 Apple 决定修复困扰我们的许多错误之前,我发现了一个临时解决方法可能会有所帮助。

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSString *text = [self findTextForIndexPath:indexPath];
    UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:13];
    CGRect estimatedHeight = [text boundingRectWithSize:CGSizeMake(215, MAXFLOAT)
                                                options:NSStringDrawingUsesLineFragmentOrigin
                                             attributes:@{NSFontAttributeName: font}
                                                context:nil];
    return TOP_PADDING + CGRectGetHeight(estimatedHeight) + BOTTOM_PADDING;
}

这并不完美,但它为我完成了工作。现在我可以打电话了:

- (void)scrollToLastestSeenMessageAnimated:(BOOL)animated
{
    NSInteger count = [self tableView:self.tableView numberOfRowsInSection:0];
    if (count > 0) {
        NSInteger lastPos = MAX(0, count-1);
        [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForItem:lastPos inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:animated];
    }
}

viewDidLayoutSubviews 上,它会在底部找到正确的位置(或非常接近的估计位置)。

希望对你有帮助。

【讨论】:

  • 我一直在寻找解决方案,但这确实是最好的解决方法!谢谢!
【解决方案5】:

就我而言,我找到了一个临时解决方法,即不向程序建议估计的单元格高度。我通过在我的代码中注释掉以下方法来做到这一点:

- (CGFloat) tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath

但是,请注意,如果您的单元格彼此之间变化很大,这样做可能会影响用户滚动时的用户体验。就我而言,到目前为止没有明显的区别。

希望对你有帮助!

【讨论】:

    【解决方案6】:

    我在 Swift 5 iOS 13 中遇到了这个问题,这解决了我的问题

     DispatchQueue.main.async { [weak self] in
        self?.tableView.reloadData()
        self?.tableView.scrollToRow(at: indexPath, at: .middle, animated: false)
     }
    

    【讨论】:

      【解决方案7】:

      我的解决方案是使用故事板的大小作为估计值。

      所以不要这样:

      - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {   
      return UITableViewAutomaticDimension;
      

      }

      我做了这样的事情:

      - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { 
      
      MyMessageType messageType = [self messageTypeForRowAtIndexPath:indexPath];
      
      switch (messageType) {
      
          case MyMessageTypeText:
              return 45;
              break;
      
          case MyMessageTypeMaybeWithSomeMediaOrSomethingBiggerThanJustText:
              return 96;
              break;
      
          default:
              break;
       }
      }
      

      我正在编写一个聊天表视图,因此我的许多单元格,特别是文本类型可能会比 IB 中的要大,尤其是在聊天消息很长的情况下。这似乎是一个相当不错的......嗯......估计和滚动到底部非常接近。随着滚动时间的延长,情况似乎会稍差一些,但我想这是可以预料的

      【讨论】:

        【解决方案8】:

        viewDidAppear 后调用tableview reloadData 即可解决问题

        -(void)viewDidAppear:(BOOL)animated
        {
            [super viewDidAppear:animated];
            [self.tableView reloadData];
        }
        

        【讨论】:

          【解决方案9】:

          虽然 smileyborg's answer 是 iOS 8.x 中的错误,但它应该在您支持的所有平台上修复...

          要在 iOS9 之前的版本中解决问题,下面的代码可以在没有任何 dispatch_async 或 dispatch_after 的情况下解决问题。 在 iOS 8.4 模拟器上测试。

          更新:当 UIPageViewController 滚动显示视图控制器时,调用(仅)layoutIfNeeded 不起作用。所以请改用 layoutSubviews(或者 setNeedsLayout + layoutIfNeeded)。

          // For iOS 8 bug workaround.
          // See https://stackoverflow.com/a/33515872/1474113
          - (void)scrollToBottomForPreiOS9
          {
              CGFloat originalY, scrolledY;
              do {
                  // Lay out visible cells immediately for current contentOffset.
                  // NOTE: layoutIfNeeded does not work when hosting UIPageViewController is dragged.
                  [self.tableView layoutSubviews];
                  originalY = self.tableView.contentOffset.y;
                  [self scrollToBottom];  // Call -scrollToRowAtIndexPath as usual.
                  scrolledY = self.tableView.contentOffset.y;
              } while (scrolledY > originalY);
          }
          

          【讨论】:

            【解决方案10】:

            在创建具有不同单元格高度的聊天 tableView 时,我遇到了同样的问题。我在 viewDidAppear() 生命周期方法中调用下面的代码:

            // First figure out how many sections there are
            let lastSectionIndex = self.tableView.numberOfSections - 1
            
            // Then grab the number of rows in the last section
            let lastRowIndex = self.tableView.numberOfRowsInSection(lastSectionIndex) - 1
            
            // Now just construct the index path
            let pathToLastRow = NSIndexPath(forRow: lastRowIndex, inSection: lastSectionIndex)
            
            // Make the last row visible
            self.tableView.scrollToRowAtIndexPath(pathToLastRow, atScrollPosition: UITableViewScrollPosition.None, animated: true)
            

            如果这对你也有用,请告诉我。

            【讨论】:

            • 不,它没有用。对我来说,每次 tableview 都会在最后一个单元格上方停止 2 个单元格。
            【解决方案11】:

            在情节提要窗口中单击空白区域以取消选择所有视图,然后单击其中包含表格视图的视图,然后单击Resolve Auto Layout Issue 图标并选择Reset to Suggested Constraints

            【讨论】:

              【解决方案12】:

              使用这个简单的代码滚动底部

               var rows:NSInteger=self.tableName.numberOfRowsInSection(0)
                      if(rows > 0)
                      {
                          let indexPath = NSIndexPath(forRow: rows-1, inSection: 0)
                          tableName.scrollToRowAtIndexPath(indexPath , atScrollPosition:  UITableViewScrollPosition.Bottom, animated: true)
                      }
                          }
              

              【讨论】:

              • 问题被标记为“Swift”,而不是“Objective-C”。请始终以标记语言提供答案。谢谢。
              • 问题已经说过这种方法不适用于具有动态高度的单元格
              猜你喜欢
              • 2012-05-24
              • 1970-01-01
              • 2015-03-03
              • 1970-01-01
              • 1970-01-01
              • 2014-11-16
              • 1970-01-01
              • 1970-01-01
              • 2015-07-18
              相关资源
              最近更新 更多