如何检测UITableView的加载结束

时间:2010-11-12 10:14:14

标签: ios iphone uitableview

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

无论如何在SDK上知道uitableview加载何时完成?我在委托和数据源协议上都没有看到任何内容。

由于仅加载了可见单元,我无法使用数据源的计数。

21 个答案:

答案 0 :(得分:211)

改进@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];
    }
}

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

答案 1 :(得分:26)

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

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

答案 2 :(得分:20)

Swift 3&amp; 4版本:

from apache_beam.utils.pipeline_options

答案 3 :(得分: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");
}

如果您希望找到整个UITableView的结束加载,而不仅仅是可见的单元格,我发现此方法效果最佳。根据您的需要,您可能只需要可见的单元格,在这种情况下folex's answer是一条很好的路径。

答案 4 :(得分: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()
    }
}

答案 5 :(得分: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];
    }
}

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

答案 6 :(得分: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
        }
    }
}

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

答案 7 :(得分:6)

我所知道的最佳方法是Eric的回答:Get notified when UITableView has finished asking for data?

更新:要使其正常工作,我必须将这些来电置于-tableView:cellForRowAtIndexPath:

[tableView beginUpdates];
[tableView endUpdates];

答案 8 :(得分:1)

这是我在Swift 3中的表现方式

let threshold: CGFloat = 76.0 // threshold from bottom of tableView

internal func scrollViewDidScroll(_ scrollView: UIScrollView) {

    let contentOffset = scrollView.contentOffset.y
    let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height;

    if  (!isLoadingMore) &&  (maximumOffset - contentOffset <= threshold) {
        self.loadVideosList()
    }
}

答案 9 :(得分:1)

这就是我要做的事。

  1. 在您的基类中(可以是rootVC BaseVc等),

    一个。编写一个协议来发送&#34; DidFinishReloading&#34;回调。

    @protocol ReloadComplition <NSObject>
    @required
    - (void)didEndReloading:(UITableView *)tableView;
    @end
    

    B中。编写一个通用方法来重新加载表视图。

    -(void)reloadTableView:(UITableView *)tableView withOwner:(UIViewController *)aViewController;
    
  2. 在基类方法实现中,调用reloadData,然后调用delegateMethod并延迟。

    -(void)reloadTableView:(UITableView *)tableView withOwner:(UIViewController *)aViewController{
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            [tableView reloadData];
            if(aViewController && [aViewController respondsToSelector:@selector(didEndReloading:)]){
                [aViewController performSelector:@selector(didEndReloading:) withObject:tableView afterDelay:0];
            }
        }];
    }
    
  3. 在需要回调的所有视图控制器中确认重新加载完成协议。

    -(void)didEndReloading:(UITableView *)tableView{
        //do your stuff.
    }
    
  4. 参考:https://discussions.apple.com/thread/2598339?start=0&tstart=0

答案 10 :(得分:1)

以下是你在Swift 3中的表现:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    if indexPath.row == 0 {
        // perform your logic here, for the first row in the table
    }

    // ....
}

答案 11 :(得分:0)

要知道表视图何时完成加载其内容,我们首先需要对如何在屏幕上显示视图有基本的了解。

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

  1. 该应用收到事件(触摸,计时器,已分派等)
  2. 该应用处理事件(它修改约束,开始动画,更改背景等)
  3. 该应用会计算新的视图层次结构
  4. 该应用呈现视图层次结构并显示

2次和3次完全分开。 为什么?出于性能原因,我们不希望每次修改后都执行时刻3的所有计算。

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

tableView.reloadData()
tableView.visibleCells.count // wrong count oO

这是怎么了?

与任何视图一样,表视图会延迟加载其内容。实际上,如果您多次致电reloadData,不会造成性能问题。表格视图仅基于其委托实现重新计算其内容大小,并等待3时刻加载其单元格。这次称为布局通行证。

好吧,如何进入版式传递?

在布局过程中,应用程序将计算视图层次结构的所有框架。要参与其中,您可以覆盖layoutSubviews中的专用方法updateLayoutConstraintsUIView等,以及视图控制器子类中的等效方法。

这正是表格视图的功能。它会覆盖layoutSubviews,并根据您的委托实现添加或删除单元格。它会在添加和布置新单元格之前立即调用cellForRow,紧随其后调用willDisplay。如果您调用了reloadData或只是将表格视图添加到层​​次结构中,则表格视图会在此关键时刻添加所需数量的单元格来填充其框架。

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

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

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

class MyTableView: UITableView {
    func layoutSubviews() {
        super.layoutSubviews()
        // the displayed cells are loaded
    }
}

请注意,此方法在表视图的生命周期中被多次调用。由于表格视图的滚动和出队行为,经常修改,删除和添加单元格。但这是可行的,在super.layoutSubviews()之后立即加载了单元。此解决方案等效于等待最后一个索引路径的willDisplay事件。对于添加的每个单元格,在执行表视图的layoutSubviews期间都会调用此事件。

另一种方法是在应用完成版式传递时被调用。

documentation中所述,您可以使用UIView.animate(withDuration:completion)的选项:

tableView.reloadData()
UIView.animate(withDuration: 0) {
    // layout done
}

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

或者,我希望强制使用表格视图的布局

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

tableView.reloadData()
table.layoutIfNeeded()
// layout done

但是请小心,这样做会删除系统使用的延迟加载。重复调用这些方法可能会导致性能问题。在计算表格视图的框架之前,请确保不会调用它们,否则表格视图将再次加载,并且不会收到通知。


我认为没有完美的解决方案。子类化可能导致混乱。布局遍历从顶部开始一直到底部,因此在完成所有布局后不容易得到通知。并且layoutIfNeeded()可能会导致性能问题,但是了解这些选项,您应该能够考虑一种满足您需求的替代方案。

答案 12 :(得分:0)

很偶然,我碰到了这个解决方案:

tableView.tableFooterView = UIView()
tableViewHeight.constant = tableView.contentSize.height

您需要先设置footerView,然后再获取contentSize,例如在viewDidLoad中。 顺便说一句。设置footeView可以删除“未使用的”分隔符

答案 13 :(得分:0)

如果您有多个部分,这里是如何获取最后一部分中的最后一行(Swift 3):

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if let visibleRows = tableView.indexPathsForVisibleRows, let lastRow = visibleRows.last?.row, let lastSection = visibleRows.map({$0.section}).last {
        if indexPath.row == lastRow && indexPath.section == lastSection {
            // Finished loading visible rows

        }
    }
}

答案 14 :(得分:0)

我知道这已经得到解答,我只是在添加推荐。

根据以下文档

https://www.objc.io/issues/2-concurrency/thread-safe-class-design/

使用dispatch_async修复计时问题是个坏主意。我建议我们应该通过添加FLAG或其他东西来处理这个问题。

答案 15 :(得分:0)

在Swift中你可以做这样的事情。每次到达tableView结束时,以下条件都将成立

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        if indexPath.row+1 == postArray.count {
            println("came to last row")
        }
}

答案 16 :(得分:0)

@folex答案是对的。

但如果tableView一次显示多个部分,它将失败

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
   if([indexPath isEqual:((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject])]){
    //end of loading

 }
}

答案 17 :(得分:-1)

我正在复制安德鲁的代码并将其扩展到考虑到表中只有一行的情况。它对我来说到目前为止工作!

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
// detect when all visible cells have been loaded and displayed
// NOTE: iOS7 workaround used - see: http://stackoverflow.com/questions/4163579/how-to-detect-the-end-of-loading-of-uitableview?lq=1
NSArray *visibleRows = [tableView indexPathsForVisibleRows];
NSIndexPath *lastVisibleCellIndexPath = [visibleRows lastObject];
BOOL isPreviousCallForPreviousCell = self.previousDisplayedIndexPath.row + 1 == lastVisibleCellIndexPath.row;
BOOL isLastCell = [indexPath isEqual:lastVisibleCellIndexPath];
BOOL isFinishedLoadingTableView = isLastCell && ([tableView numberOfRowsInSection:0] == 1 || isPreviousCallForPreviousCell);

self.previousDisplayedIndexPath = indexPath;

if (isFinishedLoadingTableView) {
    [self hideSpinner];
}
}

注意:我只使用安德鲁代码中的1个部分,所以请记住这一点。

答案 18 :(得分:-1)

您是在查找将在表格中显示的项目总数还是当前可见的项目总数?无论哪种方式..我相信'viewDidLoad'方法在调用所有数据源方法后执行。但是,这仅适用于第一次加载数据(如果使用单个alloc ViewController)。

答案 19 :(得分:-2)

目标C

[self.tableView reloadData];
[self.tableView performBatchUpdates:^{}
                              completion:^(BOOL finished) {
                                  /// table-view finished reload
                              }];

夫特

self.tableView?.reloadData()
self.tableView?.performBatchUpdates({ () -> Void in

                            }, completion: { (Bool finished) -> Void in
                                /// table-view finished reload
                            })

答案 20 :(得分:-3)

在iOS7.0x中,解决方案有点不同。这就是我想出来的。

    - (void)tableView:(UITableView *)tableView 
      willDisplayCell:(UITableViewCell *)cell 
    forRowAtIndexPath:(NSIndexPath *)indexPath
{
    BOOL isFinishedLoadingTableView = [self isFinishedLoadingTableView:tableView  
                                                             indexPath:indexPath];
    if (isFinishedLoadingTableView) {
        NSLog(@"end loading");
    }
}

- (BOOL)isFinishedLoadingTableView:(UITableView *)tableView 
                         indexPath:(NSIndexPath *)indexPath
{
    // The reason we cannot just look for the last row is because 
    // in iOS7.0x the last row is updated before
    // looping through all the visible rows in ascending order 
    // including the last row again. Strange but true.
    NSArray * visibleRows = [tableView indexPathsForVisibleRows];   // did verify sorted ascending via logging
    NSIndexPath *lastVisibleCellIndexPath = [visibleRows lastObject];
    // For tableviews with multiple sections this will be more complicated.
    BOOL isPreviousCallForPreviousCell = 
             self.previousDisplayedIndexPath.row + 1 == lastVisibleCellIndexPath.row;
    BOOL isLastCell = [indexPath isEqual:lastVisibleCellIndexPath];
    BOOL isFinishedLoadingTableView = isLastCell && isPreviousCallForPreviousCell;
    self.previousDisplayedIndexPath = indexPath;
    return isFinishedLoadingTableView;
}