代码之家  ›  专栏  ›  技术社区  ›  emenegro

如何检测UITableView加载结束

  •  125
  • emenegro  · 技术社区  · 14 年前

    我想更改加载完成时表的偏移量,该偏移量取决于加载到表中的单元格数。

    是否在SDK上知道uitableview加载何时完成?我在委托和数据源协议上都看不到任何东西。

    21 回复  |  直到 6 年前
        1
  •  224
  •   folex    12 年前

    提高到@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: willDisplay: 呼叫)。你也可以试试 tableView:didEndDisplayingCell: .

        2
  •  30
  •   Daniele Ceglia    5 年前

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        if let lastVisibleIndexPath = tableView.indexPathsForVisibleRows?.last {
            if indexPath == lastVisibleIndexPath {
                // do here...
            }
        }
    }
    
        3
  •  28
  •   RichX    14 年前

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

    -(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
    {
        if([indexPath row] == lastRow){
            //end of loading
            //for example [activityIndicator stopAnimating];
        }
    }
    
        4
  •  10
  •   Community CDub    8 年前

    这是另一个对我有用的选择。在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 是一条很好的路线。

        5
  •  10
  •   fatihyildizhan thumbmunkeys    9 年前

    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()
        }
    }
    
        6
  •  9
  •   malhal Benzy Neez    8 年前

    - (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 row,除非表知道行数,并且我希望在默认情况下选择一行。我有一个不是视图控制器的表视图委托,所以我不能简单地将表单元格选择放在视图中显示或加载委托方法,其他的答案都不是我喜欢的。

        7
  •  8
  •   Elijah    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变量,因为我想在进行默认单元格选择之前确保表已完成加载。如果不包括这个,那么每次滚动表时,它都会再次调用代码。

        8
  •  6
  •   Community CDub    8 年前

    我知道最好的方法是埃里克的回答是: Get notified when UITableView has finished asking for data?

    最新消息:要想成功,我必须打这些电话 -tableView:cellForRowAtIndexPath:

    [tableView beginUpdates];
    [tableView endUpdates];
    
        9
  •  1
  •   Ondrej Kvasnovsky    8 年前

    以下是您在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
        }
    
        // ....
    }
    
        10
  •  1
  •   AbdulMomen عبدالمؤمن    8 年前

    下面是我在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()
        }
    }
    
        11
  •  1
  •   Pang Ajmal PraveeN    7 年前

    这就是我要做的。

    1. A、 编写一个协议来发送“didffinishreloading”回调。

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

      B、 编写一个泛型方法来重新加载表视图。

      -(void)reloadTableView:(UITableView *)tableView withOwner:(UIViewController *)aViewController;
      
    2. -(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.
      }
      

    参考: https://discussions.apple.com/thread/2598339?start=0&tstart=0

        12
  •  1
  •   GaétanZ    6 年前

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

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

    1. 应用程序接收一个事件(触摸、计时器、发送的块等)
    2. 应用程序计算新的视图层次结构
    3. 应用程序呈现视图层次结构并显示它

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

    所以,我想你面对的是这样一个案子:

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

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

    好的,怎么进入布局通道?

    在布局过程中,应用程序将计算视图层次结构的所有框架。若要参与,可以重写专用方法 layoutSubviews , updateLayoutConstraints 等等 UIView

    这正是表视图的作用。它超越了 布局子视图 并基于委托实现添加或删除单元格。它叫 cellForRow willDisplay 刚好在…之后。如果你打电话来 重新加载数据 或者只是将表视图添加到层次结构中,表视图将根据需要添加尽可能多的单元格,以在此关键时刻填充其框架。

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

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

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

    super.layoutSubviews() ,单元格已加载。这个解决方案相当于等待 意志展示 布局子视图 添加的每个单元格的表视图。

    另一种方法是在应用程序完成布局传递时调用。

    documentation ,您可以使用 UIView.animate(withDuration:completion)

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

    这个解决方案可以工作,但是在布局完成和调用块之间,屏幕将刷新一次。这相当于 DispatchMain.async 解决方案,但已指定。

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

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

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


    我认为没有完美的解决方案。子类化类可能导致trubles。布局过程从顶部开始,到底部,因此在完成所有布局时不容易得到通知。以及 layoutIfNeeded() 可能会造成性能问题等,但知道这些选项,你应该能够思考一个选择,将适合你的需要。

        13
  •  0
  •   umakanta    10 年前

    @福尔克斯的回答是对的。

    但它会 如果tableView有 多个部分

    -(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
    {
       if([indexPath isEqual:((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject])]){
        //end of loading
    
     }
    }
    
        14
  •  0
  •   Codetard    9 年前

    在斯威夫特你可以做这样的事情。每次到达tableView的末尾时,以下条件都将为true

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

    我知道这是答案,我只是补充一个建议。

    根据以下文件

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

    用DexCuffiaSyc固定定时问题是一个坏主意。我建议我们应该加上旗子什么的来处理这个问题。

        16
  •  0
  •   Daniel McLean    8 年前

    如果有多个部分,下面是如何获取最后一部分中的最后一行(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
    
            }
        }
    }
    
        17
  •  0
  •   Kowboj    6 年前

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

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

    顺便说一下,设置footeView可以删除“未使用”的分隔符

        18
  •  -1
  •   DerekH    14 年前

    您要查找将在表中显示的项目总数还是当前可见的项目总数?不管怎样。。我相信“viewDidLoad”方法是在调用所有数据源方法之后执行的。但是,这只适用于第一次加载数据(如果您使用的是单个alloc ViewController)。

        19
  •  -1
  •   skrrgwasme    10 年前

    我正在复制Andrew的代码并将其展开,以解释表中只有一行的情况。到目前为止对我有效!

    - (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];
    }
    }
    

        20
  •  -3
  •   Andrew Raphael    11 年前

    在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;
    }
    
        21
  •  -3
  •   pkc456    9 年前

    目标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
                                })