【问题标题】:How to keep UITableView contentoffset after calling -reloadData调用-reloadData后如何保持UITableView contentoffset
【发布时间】:2012-01-28 06:01:34
【问题描述】:
CGPoint offset = [_table contentOffset];
[_table reloadData];
[_table setContentOffset:offset animated:NO];    //unuseful

//    __block UITableView *tableBlock = _table;
//    [self performBlock:^(id sender) {
//        [tableBlock setContentOffset:offset];
//    } afterDelay:2];

我知道不知道在reloadData 之后调用的任何委托方法。 并且使用afterDelay:2 这有点像hack 可能太短或太长,那么我该如何实现呢?

【问题讨论】:

  • here。这个答案比这里提供的所有答案都好。即使是马特考拉的回答我也不是 100% 的工作:)

标签: ios uitableview reloaddata contentoffset


【解决方案1】:

我遇到了这个问题,因为我在 cellForRowAtIndexPath 方法中弄乱了单元格大小。我注意到在执行 reloadData 后尺寸信息已关闭,所以我意识到我需要在设置内容偏移量之前立即强制它进行布局。

CGPoint offset = tableView.contentOffset;
[tableView.messageTable reloadData];
[tableView layoutIfNeeded]; // Force layout so things are updated before resetting the contentOffset.
[tableView setContentOffset:offset];

【讨论】:

  • 我不明白接受的答案。我看到 reloadData 肯定会改变我的内容偏移量,这有帮助。
  • layoutIfNeeded() 在 reloadData() (Swift 3.0) 之后对我有用
  • [tableView layoutIfNeeded]; 解决了我的问题。谢谢!
  • 对于那些在tableView: heightForRowAtIndexPath: 中使用UITableViewAutomaticDimension 的人,为了让它在iOS 10 下工作,我还必须在tableView: estimatedHeightForRowAtIndexPath: 中返回“UITableViewAutomaticDimension”以使其工作。在这种方法中返回一个常量对我不起作用。
  • 如果使用此解决方案在滚动时重新加载,tableView 会卡在屏幕下方
【解决方案2】:

在 tableView 上调用 reloadData 不会改变内容偏移量。但是,如果您使用的是在 iOS 8 中引入的UITableViewAutomaticDimension,您可能会遇到问题。

在使用UITableViewAutomaticDimension 时,需要编写委托方法tableView: estimatedHeightForRowAtIndexPath: 并返回UITableViewAutomaticDimension 以及tableView: heightForRowAtIndexPath:,它也返回相同的结果。

对我来说,我在使用 iOS 8 时遇到了问题。这是因为 estimatedHeightForRowAtIndexPath: 方法返回的值不准确,即使我使用的是 UITableViewAutomaticDimension。这是 iOS 8 的问题,因为 iOS 9 设备没有问题。

我通过使用字典来存储单元格高度的值并返回它来解决这个问题。这就是我所做的。

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSNumber *key = @(indexPath.row);
    NSNumber *height = @(cell.frame.size.height);

    [self.cellHeightsDictionary setObject:height forKey:key];
}

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSNumber *key = @(indexPath.row);
    NSNumber *height = [self.cellHeightsDictionary objectForKey:key];

    if (height)
    {
        return height.doubleValue;
    }

    return UITableViewAutomaticDimension;
}

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

第一次加载页面时检查是否存在高度。

【讨论】:

  • 你的回答拯救了我的一天 :-) 谢谢!
  • 但是我有一个改进!保存 indexPath 本身,而不仅仅是行。由于我有多个部分,这是一个问题......
  • @blackjacx 是的。如果您有多个部分,您的建议会很有帮助。谢谢你
  • 这个答案有效。但是,如果您的单元格包含可以动态更改高度的项目,例如 UITextView,那么您需要在单元格高度更改时更改行的存储高度。我在调用 textViewDidChange() 委托方法时更新高度。像魅力一样工作。
  • 这是使用自动布局的人的正确答案。在'viewDidLoad'中,你需要为'self.tableView.estimatedRowHeight'设置一个非零值,然后调用'self.tableView.rowHeight = UITableViewAutomaticDimension'。然后,您需要覆盖 'tableView(:heightForRowAt:)' 以返回 'UITableViewAutomaticDimension',并覆盖 tableView(:estimatedHeightForRowAt:) 来执行相同的操作。什么 API,嗯?恒星。
【解决方案3】:

@Skywalker 答案的 Swift 5 变体:

private var heightDictionary: [IndexPath: CGFloat] = [:]

public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    heightDictionary[indexPath] = cell.frame.size.height
}

public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    let height = heightDictionary[indexPath]
    return height ?? UITableView.automaticDimension
}

另一种解决方案(取自 MessageKit):

这个方法应该被调用而不是reloadData。这可以适用于特定情况。

extension UITableView {
    public func reloadDataAndKeepOffset() {
        // stop scrolling
        setContentOffset(contentOffset, animated: false)
            
        // calculate the offset and reloadData
        let beforeContentSize = contentSize
        reloadData()
        layoutIfNeeded()
        let afterContentSize = contentSize
            
        // reset the contentOffset after data is updated
        let newOffset = CGPoint(
            x: contentOffset.x + (afterContentSize.width - beforeContentSize.width),
            y: contentOffset.y + (afterContentSize.height - beforeContentSize.height))
        setContentOffset(newOffset, animated: false)
    }
}

【讨论】:

  • 谢谢,设置估计高度对我有用。我只实现了 heightForRowAt indexpath。
  • 非常感谢。这为我解决了这个问题。我正在 iOS 11 中进行测试
  • 第一个选项是解决我的问题。谢谢(在 iOS 11 和 swift 4.1 中测试)
  • 第一种解决方案的问题是,如果在顶部插入不同高度的新单元格/项目,系统将使用缓存的单元格高度。我还没有尝试过,但我认为这可以通过在重用单元格时使缓存的单元格高度无效来解决(查看prepareForReuse)。
  • 你拯救了我的一天!
【解决方案4】:

在我的情况下,取消选中行高自动并估计自动解决问题

【讨论】:

  • 非常感谢!我在这个问题上浪费了太多时间...
  • 很好的答案。但是怎么做呢?
  • 哥们,给我你的地址...我会给你寄一瓶威士忌!
  • 你是最棒的!
  • 它有效!谢谢!!
【解决方案5】:

默认情况下,reloadData 保留 contentOffset。但是,如果您确实有不准确的估计行高度值,它可能会被更新。

【讨论】:

  • 如果每个单元格的高度都是动态的,如何正确设置estimatedRowHeight?
  • @NikKov,顾名思义,它是一个估计值,不需要是您的单元格的实际大小。如果您知道您的单元格高度通常在 40 到 60 磅之间,则将估计行高度设置为 50。
  • @Cyril 你说的是it is an estimated value that doesn't need to be the exact actual size of your cell,但也说的是if you do have inaccurate estimatedRowHeight values。那么我们怎么知道哪个是准确的呢?例如。我的单元格通常在 40 和 60,我设置为 50。但是,有时会弹出异常数据导致单元格的高度增长到 120,可能是 240 或 300。那么准确的估计行高度应该是多少?
  • 嗨@Eddie,您可以在estimatedRowHeight 中添加几个if else 语句,因为它不需要太多计算时间。因此,我建议您为异常数据返回不同的估计行高。 120 到 300 的范围很大,如果您没有有效的方法来知道大概的大小,那么您应该返回 UITableViewAutomaticDimension
【解决方案6】:

如果你实现estimatedHeightForRowAtIndexPath 方法并且你的估计不正确,你可能会陷入这种情况。

要解决这个问题,您可以返回一个比 tableView 中每个单元格高度都大的高度,如下所示:

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { 
    return 800.f; // if 800 is bigger than every possible value of your cell height.
}

【讨论】:

  • 请注意,我遇到了这个问题,由于内容偏移的变化,我能够看到一个空白区域。这里的解决方案可能效果很好。但是,对我来说,虽然重新加载 0,0 的 cellForRowAtIndexPath 没有被调用。我必须通过将 CGFloat.leastNonzeroMagnitude 返回到estimatedRowHeight 来解决这个问题。
【解决方案7】:

我最近正在使用reloadData -- reloadData 不会更改contentOffset 或滚动表格视图。如果偏移量小于新的数据量,它实际上保持不变。

【讨论】:

  • reloadData 不会改变 contentOffset,但偏移量是相对于可见单元格改变的。
  • 这对我不起作用,我必须存储 contentOffset 并在重新加载后立即重新设置。
  • 为什么这是公认的答案。我在这里找不到解决方案。
  • UITableViewAutomaticDimension 可能会导致偏移量出现问题。这个答案也不是问题的答案。
  • 我不能同意这个答案。在我的呼叫跟踪中,我看到 scrollViewDidScroll 调用是由 tableView.reloadData(): 引起的,并且偏移量实际上发生了变化。
【解决方案8】:

如果在 dataSource 数组的开头插入数据,则需要像这样更改 contentOffsetSwift 3+

func prepareReloadData() {
    let previousContentHeight = tableView.contentSize.height
    let previousContentOffset = tableView.contentOffset.y
    tableView.reloadData()
    let currentContentOffset = tableView.contentSize.height - previousContentHeight + previousContentOffset
    tableView.contentOffset = CGPoint(x: 0, y: currentContentOffset)
}

【讨论】:

  • 您需要将tableView.layoutIfNeeded() 放在tableView.reloadData() 之后。这将为您提供更新后的currentContentHeight
【解决方案9】:

我遇到了同样的问题,但是这里建议的答案都没有。这就是我解决它的方法。子类UITableView 并覆盖layoutSubviews 方法,如下所示:

override func layoutSubviews() {
    let offset = contentOffset
    super.layoutSubviews()
    contentOffset = offset
}

【讨论】:

  • 但是:不幸的是,如果您在两个布局之间制作动画,这似乎不起作用。
【解决方案10】:

@Skywalker 的回答显示了估计细胞高度问题的最佳解决方法。但有时问题碱液在不同的地方。
有时问题出在表格视图的 contentInsets 中。如果您在 tableView 在屏幕上不可见时重新加载数据,您可能会在 table view 出现在屏幕上后面临错误的偏移量。
发生这种情况是因为 UIViewController 可以控制插入,如果当滚动视图出现时他的滚动视图允许滚动视图位于透明导航栏和状态栏下方。
我在 iOS 9.1 中遇到过这种行为

【讨论】:

    【解决方案11】:

    上面马特的回答让我意识到这一定是estimatedRowHeight 的问题。

    所以正如一些人指出的reloadData 不应该修改contentOffset 所以当你设置你的rowHeight = UITableView.automaticDimension 只是为了确保设置一个正确的estimatedRowHeight

    如果estimatedRowHeightUITableView 估计的要短,它将尝试修复高度并且会出现滚动行为,但前提是存在高度问题的单元格可见。换句话说,请确保您的estimatedRowHeight 设置正确。

    我希望这可以帮助其他人。

    【讨论】:

      【解决方案12】:

      这是 100% 的工作

      change the tableView.reloadData() 
      

      进入

      tableView.reloadRows(at: tableView!.indexPathsForVisibleRows!, with: .none)
      

      【讨论】:

        【解决方案13】:

        因为它工作正常

        [tView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]
                     atScrollPosition:UITableViewScrollPositionTop
                             animated:NO];
        

        【讨论】:

        • 嗨,Deceze,你能告诉我为什么它被标记为向下病房
        • 可能是因为它不是解决问题的方法。 Id 是将第一个单元格移动到顶部位置的唯一解决方案。
        • 请在回答前阅读问题。这不是解决问题的方法。
        猜你喜欢
        • 1970-01-01
        • 2017-07-30
        • 2013-08-04
        • 1970-01-01
        • 2012-04-05
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-12-17
        相关资源
        最近更新 更多