如何检测UITableView的加载结束

eme*_*gro 137 iphone uitableview ios

我想在加载完成后更改表的偏移量,并且该偏移量取决于表上加载的单元格数.

是否可以通过SDK知道uitableview加载何时完成?我在委托和数据源协议上都没有看到任何内容.

我不能使用数据源的计数,因为只加载可见单元格.

fol*_*lex 222

改进@RichX答案: lastRow可以是两者[tableView numberOfRowsInSection: 0] - 1((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row.所以代码将是:

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if([indexPath row] == ((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row){
        //end of loading
        //for example [activityIndicator stopAnimating];
    }
}
Run Code Online (Sandbox Code Playgroud)

更新: 嗯,@ htafoya的评论是对的.如果您希望此代码检测从源加载所有数据的结束,则不会,但这不是原始问题.此代码用于检测何时显示所有可见的单元格.willDisplayCell:这里用于更流畅的UI(单个单元格通常在willDisplay:调用后快速显示).你也可以尝试一下tableView:didEndDisplayingCell:.

  • tableView:didEndDisplayingCell:实际上是从视图中删除单元格时调用的,而不是在视图中的渲染完成时调用,因此无效.不是一个好的方法名称.得阅读文档. (26认同)
  • 但是,只要用户滚动查看更多单元格,就会调用此方法. (13认同)

Dan*_*lia 28

Swift 3和4版本:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if let lastVisibleIndexPath = tableView.indexPathsForVisibleRows?.last {
        if indexPath == lastVisibleIndexPath {
            // do here...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Ric*_*chX 27

我总是使用这个非常简单的解决方案

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if([indexPath row] == lastRow){
        //end of loading
        //for example [activityIndicator stopAnimating];
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 最后一行是`[tableView numberOfRowsInSection:0] - 1`.您必须用所需的值替换"0".但那不是问题.问题是UITableView仅加载可见.但是`((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row`解决了这个问题. (6认同)

Kyl*_*egg 10

这是另一种似乎对我有用的选择.在viewForFooter委托方法中检查它是否是最后一节并在那里添加代码.在意识到如果你拥有它们,willDisplayCell不会占用页脚,就会想到这种方法.

- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section 
{
  // Perform some final layout updates
  if (section == ([tableView numberOfSections] - 1)) {
    [self tableViewWillFinishLoading:tableView];
  }

  // Return nil, or whatever view you were going to return for the footer
  return nil;
}

- (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section
{
  // Return 0, or the height for your footer view
  return 0.0;
}

- (void)tableViewWillFinishLoading:(UITableView *)tableView
{
  NSLog(@"finished loading");
}
Run Code Online (Sandbox Code Playgroud)

我发现如果你想找到整个装载的最终载荷UITableView,而不仅仅是可见的单元,这种方法效果最好.根据您的需要,您可能只需要可见的细胞,在这种情况下,folex的答案是一个很好的途径.


fat*_*han 10

Swift 2解决方案:

// willDisplay function
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    let lastRowIndex = tableView.numberOfRowsInSection(0)
    if indexPath.row == lastRowIndex - 1 {
        fetchNewDataFromServer()
    }
}

// data fetcher function
func fetchNewDataFromServer() {
    if(!loading && !allDataFetched) {
        // call beginUpdates before multiple rows insert operation
        tableView.beginUpdates()
        // for loop
        // insertRowsAtIndexPaths
        tableView.endUpdates()
    }
}
Run Code Online (Sandbox Code Playgroud)


mal*_*hal 9

试试这个魔法:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // cancel the perform request if there is another section
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tableViewDidLoadRows:) object:tableView];

    // create a perform request to call the didLoadRows method on the next event loop.
    [self performSelector:@selector(tableViewDidLoadRows:) withObject:tableView afterDelay:0];

    return self.objects.count;
}

// called after the rows in the last section is loaded
-(void)tableViewDidLoadRows:(UITableView*)tableView{
    // make the cell selected after all rows loaded
    if(self.selectedObject){
        NSInteger index = [self.objects indexOfObject:self.selectedObject];
        [tableView selectRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0] animated:NO scrollPosition:UITableViewScrollPositionMiddle];
    }
}
Run Code Online (Sandbox Code Playgroud)

表加载的行为意味着您不能调用选择行,直到表知道行计数并且我希望默认选择一行.我有一个表视图委托,它不是一个视图控制器所以我不能简单地将表格单元格选择放在视图中出现或加载委托方法,而其他任何答案都不是我喜欢的.


Tra*_* M. 8

对于Swift 3中选择的答案版本:

var isLoadingTableView = true

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if tableData.count > 0 && isLoadingTableView {
        if let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows, let lastIndexPath = indexPathsForVisibleRows.last, lastIndexPath.row == indexPath.row {
            isLoadingTableView = false
            //do something after table is done loading
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我需要isLoadingTableView变量,因为我想在确保默认单元格选择之前确保表已完成加载.如果你不包括这个,那么每次滚动表时它都会再次调用你的代码.


小智 6

我所知道的最好的方法是Eric的回答:当UITableView完成数据请求时收到通知?

更新:为了使其工作,我必须把这些电话 -tableView:cellForRowAtIndexPath:

[tableView beginUpdates];
[tableView endUpdates];
Run Code Online (Sandbox Code Playgroud)


Gaé*_*anZ 5

要知道 table view 何时完成加载其内容,我们首先需要对视图如何放置在屏幕上有一个基本的了解。

在应用程序的生命周期中,有 4 个关键时刻:

  1. 应用程序接收事件(触摸、计时器、分派块等)
  2. 应用程序处理事件(它修改约束、启动动画、更改背景等)
  3. 应用程序计算新的视图层次结构
  4. 应用程序呈现视图层次结构并显示它

2次和3次是完全分开的。为什么 ?出于性能原因,我们不想在每次修改完成时都执行所有计算(在 3 处完成)。

所以,我认为你正面临这样的情况:

tableView.reloadData()
tableView.visibleCells.count // wrong count oO
Run Code Online (Sandbox Code Playgroud)

这里有什么问题?

表视图懒惰地重新加载其内容。实际上,如果您reloadData多次调用它不会造成性能问题。表格视图仅根据其委托实现重新计算其内容大小,并等待时刻 3 加载其单元格。这次称为布局传递。

好的,如何参与布局pass?

在布局过程中,应用程序计算视图层次结构的所有帧。涉足,您可以覆盖专用的方法layoutSubviewsupdateLayoutConstraints等在UIView子类和等效方法在视图控制器子类。

这正是表视图所做的。它覆盖layoutSubviews并基于您的委托实现添加或删除单元格。它cellForRow在添加和布置新单元格之前立即调用,之后立即调用willDisplay。如果您调用reloadData或刚刚将表格视图添加到层​​次结构中,表格视图会根据需要添加尽可能多的单元格以在关键时刻填充其框架。

好的,但是现在,如何知道表视图何时完成重新加载其内容?

我们可以重新表述这个问题:如何知道表视图何时完成其子视图的布局?

最简单的方法是进入表格视图的布局:

class MyTableView: UITableView {
    func layoutSubviews() {
        super.layoutSubviews()
        // the displayed cells are loaded
    }
}
Run Code Online (Sandbox Code Playgroud)

注意这个方法在表视图的生命周期中会被多次调用。由于表格视图的滚动和出队行为,单元格经常被修改、删除和添加。但它的工作原理super.layoutSubviews()是在, 单元格加载之后。这个方案相当于等待willDisplay最后一个索引路径的事件。layoutSubviews当添加单元格时,在执行表视图期间调用此事件。

另一种方法是在应用程序完成布局传递时收到通知。

文档中所述,您可以使用以下选项UIView.animate(withDuration:completion)

tableView.reloadData()
UIView.animate(withDuration: 0) {
    // layout done
}
Run Code Online (Sandbox Code Playgroud)

此解决方案有效,但屏幕将在完成布局和执行块之间刷新一次。这等效于DispatchMain.async解决方案,但已指定。

或者,我更愿意强制表格视图的布局

有一种专用方法可以强制任何视图立即计算其子视图帧layoutIfNeeded

tableView.reloadData()
table.layoutIfNeeded()
// layout done
Run Code Online (Sandbox Code Playgroud)

但是要小心,这样做会删除系统使用的延迟加载。重复调用这些方法可能会产生性能问题。确保在计算 table view 的 frame 之前不会调用它们,否则 table view 将再次加载并且您不会收到通知。


我认为没有完美的解决方案。子类化可能会导致问题。布局过程从顶部开始一直到底部,因此在所有布局完成后不容易得到通知。并且layoutIfNeeded()可能会造成性能问题等。