UITableView 刷新不滚动

Posted

技术标签:

【中文标题】UITableView 刷新不滚动【英文标题】:UITableView Refresh without scrolling 【发布时间】:2014-08-31 06:36:02 【问题描述】:

我有一个带有项目的_TableView,我想设置自动刷新,我不希望它在刷新时滚动,假设用户向下滚动了 2 页,并且触发了刷新 -> 所以我想将刷新后的内容放在表格顶部,而不影响用户的滚动

假设用户在第 18 行 现在 _dataSource 已刷新,因此它获取了 4 个项目,所以我希望用户留在他原来的项目上。

实现它的最佳方法是什么??

【问题讨论】:

希望这些答案对您有所帮助:***.com/questions/5872759/… 您的表格视图单元格有静态高度还是动态高度?因为如果您的表格视图具有动态单元格高度,则无法在不滚动的情况下重新加载,表格视图必须在具有动态单元格时从头开始重新创建所有单元格。 【参考方案1】:

对于 Swift 3+

您需要保存UITableView 的当前偏移量,然后重新加载,然后将偏移量设置回UITableView

我为此目的创建了这个函数:

func reload(tableView: UITableView) 

    let contentOffset = tableView.contentOffset
    tableView.reloadData()
    tableView.layoutIfNeeded()
    tableView.setContentOffset(contentOffset, animated: false)


只需调用它:reload(tableView: self.tableView)

【讨论】:

如果tableview不改变偏移量仍然加载新单元格怎么办? @RockBalbao - 我不明白你的问题,兄弟 我正在使用 NSFetchedResultsControllerDelegate。现在我正在使用 UIRefreshControl 在 scrollDown 上加载旧单元格。在 performFetch 加载数据时,我只需要重新加载数据。但我不想显示重新加载的单元格。让它们在上面加载。当我在上面滚动时,只有它们变得可见。因此,为了实现这一点,我尝试使用这种 contentOffset 方式。但是在 reloadData 之前和 reloadData 之后 contentOffset 是相同的,尽管它在上面添加了几行 我觉得您的实现与此没有太大关系。因为在 reloadData 之前和之后让 contentOffset 相同正是关键所在。例如,如果您有一个“喜欢按钮”并且您正在使用实时数据库,这在这里很有用。防止tableView晃动。我建议在堆栈上打开一个线程并尽可能准确地描述您的问题。【参考方案2】:

SWIFT 3

let contentOffset = self.tableView.contentOffset
self.tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(contentOffset, animated: false)

这是 ios8 在使用 UITableViewAutomatic Dimension 时的错误。我们需要存储表格的内容偏移量,重新加载表格,强制布局并设置 contenOffset。

CGPoint contentOffset = self.tableView.contentOffset;
[self.tableView reloadData];
[self.tableView layoutIfNeeded];
[self.tableView setContentOffset:contentOffset];

【讨论】:

关于 iOS 8 的第一句话拯救了我的一天。 感谢分享代码。 拯救了我的一天。谢谢。 如果您有 50 行,每行首先高 30 点,然后是 3 行,每行高 3 点,我不确定这个答案是否有意义。想象一下,您滚动到底部并重新加载数据 - 之后您无法保持相同的滚动偏移量。【参考方案3】:

我正在显示是否只添加了一行。您可以将其扩展到多行。

    // dataArray is your data Source object
    [dataArray insertObject:name atIndex:0];
    CGPoint contentOffset = self.tableView.contentOffset;
    contentOffset.y += [self tableView:self.tableView heightForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]];
    [self.tableView reloadData];
    [self.tableView setContentOffset:contentOffset];

但要使其工作,您需要定义 - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 方法。否则,如果它是恒定的,您可以直接给您的 tableview 行高。

【讨论】:

不是固定的,谢谢你的回答我会试试的, 是的,那么您肯定已经定义了- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath 的方法。如果对您有帮助,请接受我的回答。【参考方案4】:

只需将estimatedRowHeight 设置为最大可能值。

 self.tableView.estimatedRowHeight = 1000
 self.tableView.estimatedSectionFooterHeight = 100.0
 self.tableView.estimatedSectionHeaderHeight = 500.0

就是这样!!

注意:

请不要使用FLT_MAX, DBL_MAX 值。可能会导致您的应用崩溃。

【讨论】:

完美地为我工作 完美地为我工作 这对我也很有效,tx!我将这个答案与关于同一主题的另一个问题联系起来。【参考方案5】:

我是这样做的:

messages.insertContentsOf(incomingMsgs.reverse(), at: 0)
table.reloadData()

// This is for the first load, first 20 messages, scroll to bottom
if (messages.count <= 20) 
      let indexToScroll = NSIndexPath(forRow: saferSelf.messages.count - 1, inSection: 0)
      table.scrollToRowAtIndexPath(indexToScroll, atScrollPosition: .Top , animated: false)

// This is to reload older messages on top of tableview
else 
      let indexToScroll = NSIndexPath(forRow: incomingMsgs.count, inSection: 0)
      table.scrollToRowAtIndexPath(indexToScroll, atScrollPosition: .Top , animated: false)
      // Remove the refreshControl.height + tableHeader.height from the offset so the content remain where it was before reload
      let theRightOffset = CGPointMake(0, table.contentOffset.y - refreshControl.frame.height - table.headeView.frame.height)
      table.setContentOffset(theRightOffset, animated: false)

...另外,由于我使用动态单元格高度,为了避免一些奇怪,估计被缓存:

var heightAtIndexPath = [NSIndexPath: CGFloat]()
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat 
    return heightAtIndexPath[indexPath] ?? UITableViewAutomaticDimension

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) 
    heightAtIndexPath[indexPath] = cell.frame.height

【讨论】:

在我的情况下,单元缓存是唯一有效的方法,谢谢! :) 感谢关于缓存高度估计的好建议! @马可【参考方案6】:

使用扩展

创建UITableViewExtensions.swift 并添加以下内容:

extension UITableView 

    func reloadDataWithoutScroll() 
        let offset = contentOffset
        reloadData()
        layoutIfNeeded()
        setContentOffset(offset, animated: false)
    

【讨论】:

【参考方案7】:

iOS 12.x 中,使用Xcode 10.2.1 是一个更简单的选择。

UIView.performWithoutAnimation  
    let loc = tableView.contentOffset
    tableView.reloadRows(at: [indexPath], with: .none)
    tableView.contentOffset = loc

这比跟随更好;当行不完全可见时,它有时会晃动。

let contentOffset = self.tableView.contentOffset
self.tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(contentOffset, animated: false)

【讨论】:

【参考方案8】:

此代码将防止不必要的动画并保持滚动视图的内容偏移,对我来说效果很好。

let lastScrollOffset = tableView.contentOffset
tableView.beginUpdates()
tableView.reloadData()
tableView.endUpdates()
tableView.layer.removeAllAnimations()
tableView.setContentOffset(lastScrollOffset, animated: false)

【讨论】:

【参考方案9】:

当你想重新加载时,你必须

self.tableView.reloadData()

self.tableView.layoutIfNeeded()

也可以使用这个UITableViewDelegate

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat 
        return 'your maximum cell's height'

并且您的 tableView 将保持在先前的滚动位置而不滚动

【讨论】:

【参考方案10】:

Swift 4.2:简单的解决方案

override func viewDidLoad() 
 super.viewDidLoad()

 self.tableView.estimatedRowHeight = 0
 self.tableView.estimatedSectionHeaderHeight = 0
 self.tableView.estimatedSectionFooterHeight = 0


//And then simply update(insert, reloadSections, delete etc) your tableView or reload

tableView.reloadData()

//or

UIView.performWithoutAnimation 

  tableView.beginUpdates()
  .....
  tableView.endUpdates()

【讨论】:

【参考方案11】:

尝试替换

reloadData

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

但是你应该关心no cells,如果no cells,这个方法应该会导致崩溃。

【讨论】:

【参考方案12】:

我写了一些适合我的东西:

extension UIScrollView 
    func reloadDataAndKeepContentOffsetInPlace(reloadData:(() -> Void)) 
    let currentContentHeight = contentSize.height
    if currentContentHeight == .zero 
       reloadData()
       return
    
    reloadData()
    layoutIfNeeded()
    let newContentHeight = self.contentSize.height
    DispatchQueue.main.async 
        var contentOffset = self.contentOffset
        contentOffset.y += newContentHeight - currentContentHeight
        self.setContentOffset(contentOffset, animated: false)
    
  

这样使用:

   self.reloadSomeData()
   collectionView.reloadDataAndKeepContentOffsetInPlace  [weak self] in
                                guard let self = self else  return 
                                self.collectionView.reloadData()
                            

【讨论】:

【参考方案13】:

试试下面的。

tableView.estimatedRowHeight = 0
tableView.estimatedSectionHeaderHeight = 0
tableView.estimatedSectionFooterHeight = 0

来源:https://developer.apple.com/forums/thread/86703

【讨论】:

以上是关于UITableView 刷新不滚动的主要内容,如果未能解决你的问题,请参考以下文章

在主线程上调用 tableView.reloadData 后 UITableView 不刷新

UITableView 刷新不滚动

UITableView 选定的单元格在滚动时不会保持选中状态

UITableView 布局在滚动之前不会刷新

UITableView 滚动刷新使单元格跳跃

UITableview刷新单元格在部分标题下滚动