【问题标题】:How to tell when UITableView has completed ReloadData?如何判断 UITableView 何时完成 ReloadData?
【发布时间】:2013-04-10 21:24:33
【问题描述】:

我正在尝试在 UITableView 执行完[self.tableView reloadData]后滚动到底部

我原来有

 [self.tableView reloadData]
 NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];

[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

但后来我读到 reloadData 是异步的,所以滚动不会发生,因为 self.tableView[self.tableView numberOfSections][self.tableView numberOfRowsinSection 都是 0。

谢谢!

奇怪的是我正在使用:

[self.tableView reloadData];
NSLog(@"Number of Sections %d", [self.tableView numberOfSections]);
NSLog(@"Number of Rows %d", [self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1);

在控制台中返回 Sections = 1, Row = -1;

当我在cellForRowAtIndexPath 中执行完全相同的 NSLogs 时,我得到 Sections = 1 和 Row = 8; (8对)

【问题讨论】:

标签: objective-c uitableview reloaddata


【解决方案1】:

重新加载发生在下一次布局传递期间,这通常发生在您将控制权返回到运行循环时(例如,在您的按钮操作或任何返回之后)。

所以在表格视图重新加载后运行某些东西的一种方法是简单地强制表格视图立即执行布局:

[self.tableView reloadData];
[self.tableView layoutIfNeeded];
 NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

另一种方法是使用dispatch_async 安排您的布局后代码稍后运行:

[self.tableView reloadData];

dispatch_async(dispatch_get_main_queue(), ^{
     NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection:([self.tableView numberOfSections]-1)];

    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});

更新

经过进一步调查,我发现表格视图在从reloadData 返回之前将tableView:numberOfSections:tableView:numberOfRowsInSection: 发送到其数据源。如果委托实现了tableView:heightForRowAtIndexPath:,则表视图也会在从reloadData 返回之前发送它(针对每一行)。

但是,表格视图在布局阶段之前不会发送tableView:cellForRowAtIndexPath:tableView:headerViewForSection,默认情况下,当您将控制权返回给运行循环时会发生这种情况。

我还发现,在一个小型测试程序中,您问题中的代码会正确滚动到表格视图的底部,没有我做任何特别的事情(比如发送 layoutIfNeeded 或使用 @987654332 @)。

【讨论】:

  • @rob,根据你的表数据源的大小,你可以在同一个运行循环中动画到表视图的底部。如果你用一个巨大的表尝试你的测试代码,你使用 GCD 延迟滚动直到下一个运行循环的技巧将起作用,而立即滚动将失败。但无论如何,感谢这个技巧!
  • 方法 2 因某种未知原因对我不起作用,而是选择了第一种方法。
  • dispatch_async(dispatch_get_main_queue()) 方法不能保证有效。我看到它的非确定性行为,有时系统在完成块之前完成了 layoutSubviews 和单元格渲染,有时在完成块之后。我将在下面发布一个对我有用的答案。
  • 同意dispatch_async(dispatch_get_main_queue()) 并不总是有效。在此处查看随机结果。
  • 主线程运行NSRunLoop。运行循环具有不同的阶段,您可以为特定阶段安排回调(使用CFRunLoopObserver)。 UIKit 安排布局在事件处理程序返回后的稍后阶段发生。
【解决方案2】:

看来人们仍在阅读这个问题和答案。 B/c,我正在编辑我的答案以删除与此无关的词 Synchronous

When [tableView reloadData] 返回,tableView 背后的内部数据结构已经更新。因此,当该方法完成时,您可以安全地滚动到底部。我在自己的应用程序中验证了这一点。 @rob-mayoff 得到广泛接受的答案,虽然在术语上也令人困惑,但在他的上次更新中也承认了这一点。

如果您的tableView 没有滚动到底部,您可能在未发布的其他代码中遇到问题。也许您在滚动完成后正在更改数据,然后您没有重新加载和/或滚动到底部?

如下添加一些日志,以验证reloadData之后的表数据是否正确。我在示例应用程序中有以下代码,它运行良好。

// change the data source

NSLog(@"Before reload / sections = %d, last row = %d",
      [self.tableView numberOfSections],
      [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);

[self.tableView reloadData];

NSLog(@"After reload / sections = %d, last row = %d",
      [self.tableView numberOfSections],
      [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);

[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]-1
                                                          inSection:[self.tableView numberOfSections] - 1]
                      atScrollPosition:UITableViewScrollPositionBottom
                              animated:YES];

【讨论】:

  • 我更新了我的问题。你知道为什么我的 NSLogs 会这样输出吗?
  • reloadData 不是同步的。它曾经是 - 看到这个答案:stackoverflow.com/a/16071589/193896
  • 它是同步的。使用示例应用程序很容易测试和查看。您在这个问题中链接到@rob 的答案。如果您在底部阅读他的更新,他也已经验证了这一点。也许您正在谈论视觉布局的变化。确实,tableView 没有明显同步更新,但数据是同步更新的。这就是为什么在 reloadData 返回后 OP 需要的值是正确的。
  • 您可能对reloadData 中预期发生的事情感到困惑。使用我在viewWillAppear 中的测试用例接受scrollToRowAtIndexPath: 行b/c,如果tableView 没有显示,那将毫无意义。您将看到reloadData 确实更新了缓存在tableView 实例中的数据,并且reloadData 是同步的。如果您指的是在布置tableView 时调用的其他tableView 委托方法,那么如果未显示tableView,则不会调用这些方法。如果我误解了您的情况,请解释一下。
  • 多么有趣的时光。现在是 2014 年,对于某些方法是否是同步和异步存在争论。感觉像是猜测。所有实现细节在该方法名称后面都是完全不透明的。编程不是很好吗?
【解决方案3】:

斯威夫特:

extension UITableView {
    func reloadData(completion:@escaping ()->()) {
        UIView.animate(withDuration: 0, animations: reloadData)
            { _ in completion() }
    } 
}

// ...somewhere later...

tableView.reloadData {
    print("done")
}

目标-C:

[UIView animateWithDuration:0 animations:^{
    [myTableView reloadData];
} completion:^(BOOL finished) {
    //Do something after that...
}];

【讨论】:

  • 这相当于在“不久的将来”在主线程上调度一些东西。您很可能只是在主线程将完成块出列之前看到表视图呈现对象。不建议一开始就进行这种 hack,但无论如何,如果您要尝试这样做,您应该使用 dispatch_after。
  • Rob 的解决方案很好,但如果 tableview 中没有行则不起作用。即使表格不包含行而只包含部分,Aviel 的解决方案也能正常工作。
  • @Christophe 到目前为止,我可以在没有任何行的表视图中使用 Rob 的更新,方法是在我的 Mock 视图控制器中覆盖 tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int 方法并在我的覆盖中插入我想要通知的任何内容重新加载已完成。
  • 这很棒,也适用于集合视图
【解决方案4】:

尝试设置延迟:

[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2];
[_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];

【讨论】:

  • 这很危险。如果重新加载的时间比延迟时间长怎么办?
【解决方案5】:

试试这个方法就行了

[tblViewTerms performSelectorOnMainThread:@selector(dataLoadDoneWithLastTermIndex:) withObject:lastTermIndex waitUntilDone:YES];waitUntilDone:YES];

@interface UITableView (TableViewCompletion)

-(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex;

@end

@implementation UITableView(TableViewCompletion)

-(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex
{
    NSLog(@"dataLoadDone");


NSIndexPath* indexPath = [NSIndexPath indexPathForRow: [lastTermIndex integerValue] inSection: 0];

[self selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];

}
@end

我将在表格完全加载时执行

其他解决方案是你可以继承 UITableView

【讨论】:

    【解决方案6】:

    其实这个解决了我的问题:

    -(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    
    NSSet *visibleSections = [NSSet setWithArray:[[tableView indexPathsForVisibleRows] valueForKey:@"section"]];
    if (visibleSections) {
        // hide the activityIndicator/Loader
    }}
    

    【讨论】:

      【解决方案7】:

      你可以在重新加载数据后使用它来做一些事情:

      [UIView animateWithDuration:0 animations:^{
          [self.contentTableView reloadData];
      } completion:^(BOOL finished) {
          _isUnderwritingUpdate = NO;
      }];
      

      【讨论】:

        【解决方案8】:

        上述dispatch_async(dispatch_get_main_queue()) 方法不保证有效。我看到它的非确定性行为,有时系统在完成块之前完成了 layoutSubviews 和单元格渲染,有时在完成块之后。

        这是一个在 iOS 10 上对我来说 100% 有效的解决方案。它需要能够将 UITableView 或 UICollectionView 实例化为自定义子类。下面是 UICollectionView 的解决方案,不过 UITableView 完全一样:

        CustomCollectionView.h:

        #import <UIKit/UIKit.h>
        
        @interface CustomCollectionView: UICollectionView
        
        - (void)reloadDataWithCompletion:(void (^)(void))completionBlock;
        
        @end
        

        CustomCollectionView.m:

        #import "CustomCollectionView.h"
        
        @interface CustomCollectionView ()
        
        @property (nonatomic, copy) void (^reloadDataCompletionBlock)(void);
        
        @end
        
        @implementation CustomCollectionView
        
        - (void)reloadDataWithCompletion:(void (^)(void))completionBlock
        {
            self.reloadDataCompletionBlock = completionBlock;
            [self reloadData];
        }
        
        - (void)layoutSubviews
        {
            [super layoutSubviews];
        
            if (self.reloadDataCompletionBlock) {
                self.reloadDataCompletionBlock();
                self.reloadDataCompletionBlock = nil;
            }
        }
        
        @end
        

        示例用法:

        [self.collectionView reloadDataWithCompletion:^{
            // reloadData is guaranteed to have completed
        }];
        

        有关此答案的 Swift 版本,请参阅 here

        【讨论】:

        • 这是唯一正确的方法。将它添加到我的项目中,因为我需要一些单元格的最终帧用于动画目的。我还为 Swift 添加和编辑。希望你不介意?
        • 在你调用layoutSubviews中的块之后,它应该被设置为nil,因为随后调用layoutSubviews,不一定是因为reloadData被调用,会导致块被执行因为有一个强参考被持有,这不是期望的行为。
        • 为什么我不能将它用于 UITableView?它显示没有可见的界面。我也导入了头文件,但还是一样
        • 这个答案的一个附录是,如果只有一个回调,就有可能破坏现有的回调,这意味着多个调用者将有一个竞争条件。解决方案是使reloadDataCompletionBlock 成为一个块数组,并在执行时对其进行迭代,然后清空数组。
        • 1) 这不等同于 Rob 的第一个答案,即使用 layoutIfNeeded? 2)你为什么提到iOS 10,它在iOS 9上不起作用?!
        【解决方案9】:

        我和 Tyler Sheaffer 有同样的问题。

        我在 Swift 中实现了his solution,它解决了我的问题。

        Swift 3.0:

        final class UITableViewWithReloadCompletion: UITableView {
          private var reloadDataCompletionBlock: (() -> Void)?
        
          override func layoutSubviews() {
            super.layoutSubviews()
        
            reloadDataCompletionBlock?()
            reloadDataCompletionBlock = nil
          }
        
        
          func reloadDataWithCompletion(completion: @escaping () -> Void) {
            reloadDataCompletionBlock = completion
            self.reloadData()
          }
        }
        

        斯威夫特 2:

        class UITableViewWithReloadCompletion: UITableView {
        
          var reloadDataCompletionBlock: (() -> Void)?
        
          override func layoutSubviews() {
            super.layoutSubviews()
        
            self.reloadDataCompletionBlock?()
            self.reloadDataCompletionBlock = nil
          }
        
          func reloadDataWithCompletion(completion:() -> Void) {
              reloadDataCompletionBlock = completion
              self.reloadData()
          }
        }
        

        示例用法:

        tableView.reloadDataWithCompletion() {
         // reloadData is guaranteed to have completed
        }
        

        【讨论】:

        • 不错!小挑剔,你可以通过说reloadDataCompletionBlock?() 删除if let,这将调用iff not nil ?
        • 在我在 ios9 上的情况下,这个运气不好
        • self.reloadDataCompletionBlock? { completion() } 应该是 self.reloadDataCompletionBlock?()
        • 如何调整表格视图高度的大小?我之前在调用 tableView.beginUpdates() tableView.layoutIfNeeded() tableView.endUpdates()
        【解决方案10】:

        我使用了这个技巧,很确定我已经将它发布到了这个问题的副本:

        -(void)tableViewDidLoadRows:(UITableView *)tableView{
            // do something after loading, e.g. select a cell.
        }
        
        - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
        {
            // trick to detect when table view has finished loading.
            [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tableViewDidLoadRows:) object:tableView];
            [self performSelector:@selector(tableViewDidLoadRows:) withObject:tableView afterDelay:0];
        
            // specific to your controller
            return self.objects.count;
        }
        

        【讨论】:

        • @Fattie 不清楚您的意思是正面评论还是负面评论。但是我看到您将另一个答案评论为“这似乎是最好的解决方案!”,所以我想相对而言,您并不认为这个解决方案是最好的。
        • 依赖假动画的副作用?这绝不是个好主意。学习执行选择器或 GCD 并正确执行。顺便说一句,现在有一个表格加载方法,如果您不介意使用私有协议,您可以使用它,这可能很好,因为它是调用您的代码的框架,而不是其他方式。
        【解决方案11】:

        从 Xcode 8.2.1、iOS 10 和 swift 3 开始,

        您可以使用 CATransaction 块轻松确定 tableView.reloadData() 的结尾:

        CATransaction.begin()
        CATransaction.setCompletionBlock({
            print("reload completed")
            //Your completion code here
        })
        print("reloading")
        tableView.reloadData()
        CATransaction.commit()
        

        上述方法也适用于确定 UICollectionView 的 reloadData() 和 UIPickerView 的 reloadAllComponents() 的结束。

        【讨论】:

        • ? 如果您在 beginUpdatesendUpdates 调用中执行自定义重新加载,例如手动插入、删除或移动表格视图中的行,我也可以工作。
        • 我相信这实际上是现代解决方案。确实这是 iOS 中的常见模式,例如 ...stackoverflow.com/a/47536770/294884
        • 我试过这个。我有一个非常奇怪的行为。我的 tableview 正确显示了两个 headerViews。在 setCompletionBlock 我的 numberOfSections 里面显示 2 ...到目前为止一切都很好。然而,如果在setCompletionBlock 里面我做tableView.headerView(forSection: 1) 它返回nil !!!因此我认为这个块要么在重新加载之前发生,要么在之前捕获一些东西,或者我做错了什么。仅供参考,我确实尝试了 Tyler 的答案,并且奏效了! @Fattie
        • 一旦重新加载表格数据,我将使用它滚动到表格顶部。它在大多数情况下工作得很好,但如果在重新加载之前和之后顶行的高度不同,它会有一个偏移量。这似乎与 rob mayoff 的发现有关。
        • 这很有帮助,谢谢!我有一个问题,在我的 tableview 上调用 reloadData() 有时会触发 tableview 的 scrollViewDidScroll() 方法。在完成块完成之前,我能够阻止调用 scrollViewDidScroll()。
        【解决方案12】:

        我最终使用了 Shawn 解决方案的变体:

        使用委托创建自定义 UITableView 类:

        protocol CustomTableViewDelegate {
            func CustomTableViewDidLayoutSubviews()
        }
        
        class CustomTableView: UITableView {
        
            var customDelegate: CustomTableViewDelegate?
        
            override func layoutSubviews() {
                super.layoutSubviews()
                self.customDelegate?.CustomTableViewDidLayoutSubviews()
            }
        }
        

        然后在我的代码中,我使用

        class SomeClass: UIViewController, CustomTableViewDelegate {
        
            @IBOutlet weak var myTableView: CustomTableView!
        
            override func viewDidLoad() {
                super.viewDidLoad()
        
                self.myTableView.customDelegate = self
            }
        
            func CustomTableViewDidLayoutSubviews() {
                print("didlayoutsubviews")
                // DO other cool things here!!
            }
        }
        

        还要确保在界面构建器中将表格视图设置为 CustomTableView:

        【讨论】:

        • 这行得通,但问题是每次加载单个单元格时都会遇到该方法,而不是整个表视图重新加载,所以显然这个答案与所提出的问题无关。
        • 没错,它被多次调用,但不是在每个单元格上。所以你可以听第一个委托并忽略其余的,直到你再次调用 reloadData。
        【解决方案13】:

        还有一个UICollectionView 版本,基于 kolaworld 的回答:

        https://stackoverflow.com/a/43162226/1452758

        需要测试。目前在 iOS 9.2、Xcode 9.2 beta 2 上工作,将 collectionView 滚动到索引,作为闭包。

        extension UICollectionView
        {
            /// Calls reloadsData() on self, and ensures that the given closure is
            /// called after reloadData() has been completed.
            ///
            /// Discussion: reloadData() appears to be asynchronous. i.e. the
            /// reloading actually happens during the next layout pass. So, doing
            /// things like scrolling the collectionView immediately after a
            /// call to reloadData() can cause trouble.
            ///
            /// This method uses CATransaction to schedule the closure.
        
            func reloadDataThenPerform(_ closure: @escaping (() -> Void))
            {       
                CATransaction.begin()
                    CATransaction.setCompletionBlock(closure)
                    self.reloadData()
                CATransaction.commit()
            }
        }
        

        用法:

        myCollectionView.reloadDataThenPerform {
            myCollectionView.scrollToItem(at: indexPath,
                    at: .centeredVertically,
                    animated: true)
        }
        

        【讨论】:

          【解决方案14】:

          只是提供另一种方法,基于完成是要发送到cellForRow 的“最后一个可见”单元格的想法。

          // Will be set when reload is called
          var lastIndexPathToDisplay: IndexPath?
          
          typealias ReloadCompletion = ()->Void
          
          var reloadCompletion: ReloadCompletion?
          
          func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
          
              // Setup cell
          
              if indexPath == self.lastIndexPathToDisplay {
          
                  self.lastIndexPathToDisplay = nil
          
                  self.reloadCompletion?()
                  self.reloadCompletion = nil
              }
          
              // Return cell
          ...
          
          func reloadData(completion: @escaping ReloadCompletion) {
          
              self.reloadCompletion = completion
          
              self.mainTable.reloadData()
          
              self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last
          }
          

          一个可能的问题是:如果reloadData() 在设置lastIndexPathToDisplay 之前完成,则“最后可见”单元格将在设置lastIndexPathToDisplay 之前显示,并且不会调用完成(并且将在'等待状态):

          self.mainTable.reloadData()
          
          // cellForRowAt could be finished here, before setting `lastIndexPathToDisplay`
          
          self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last
          

          如果我们反转,我们最终可能会通过在reloadData() 之前滚动来触发完成。

          self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last
          
          // cellForRowAt could trigger the completion by scrolling here since we arm 'lastIndexPathToDisplay' before 'reloadData()'
          
          self.mainTable.reloadData()
          

          【讨论】:

            【解决方案15】:

            试试这个:

            tableView.backgroundColor = .black
            
            tableView.reloadData()
            
            DispatchQueue.main.async(execute: {
            
                tableView.backgroundColor = .green
            
            })
            

            只有在reloadData()函数完成后,tableView的颜色才会从黑色变为绿色。

            【讨论】:

              【解决方案16】:

              详情

              • Xcode 版本 10.2.1 (10E1001),Swift 5

              解决方案

              import UIKit
              
              // MARK: - UITableView reloading functions
              
              protocol ReloadCompletable: class { func reloadData() }
              
              extension ReloadCompletable {
                  func run(transaction closure: (() -> Void)?, completion: (() -> Void)?) {
                      guard let closure = closure else { return }
                      CATransaction.begin()
                      CATransaction.setCompletionBlock(completion)
                      closure()
                      CATransaction.commit()
                  }
              
                  func run(transaction closure: (() -> Void)?, completion: ((Self) -> Void)?) {
                      run(transaction: closure) { [weak self] in
                          guard let self = self else { return }
                          completion?(self)
                      }
                  }
              
                  func reloadData(completion closure: ((Self) -> Void)?) {
                      run(transaction: { [weak self] in self?.reloadData() }, completion: closure)
                  }
              }
              
              // MARK: - UITableView reloading functions
              
              extension ReloadCompletable where Self: UITableView {
                  func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
                      run(transaction: { [weak self] in self?.reloadRows(at: indexPaths, with: animation) }, completion: closure)
                  }
              
                  func reloadSections(_ sections: IndexSet, with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
                      run(transaction: { [weak self] in self?.reloadSections(sections, with: animation) }, completion: closure)
                  }
              }
              
              // MARK: - UICollectionView reloading functions
              
              extension ReloadCompletable where Self: UICollectionView {
              
                  func reloadSections(_ sections: IndexSet, completion closure: ((Self) -> Void)?) {
                      run(transaction: { [weak self] in self?.reloadSections(sections) }, completion: closure)
                  }
              
                  func reloadItems(at indexPaths: [IndexPath], completion closure: ((Self) -> Void)?) {
                      run(transaction: { [weak self] in self?.reloadItems(at: indexPaths) }, completion: closure)
                  }
              }
              

              用法

              UITableView

              // Activate
              extension UITableView: ReloadCompletable { }
              
              // ......
              let tableView = UICollectionView()
              
              // reload data
              tableView.reloadData { tableView in print(collectionView) }
              
              // or
              tableView.reloadRows(at: indexPathsToReload, with: rowAnimation) { tableView in print(tableView) }
              
              // or
              tableView.reloadSections(IndexSet(integer: 0), with: rowAnimation) { _tableView in print(tableView) }
              

              UICollectionView

              // Activate
              extension UICollectionView: ReloadCompletable { }
              
              // ......
              let collectionView = UICollectionView()
              
              // reload data
              collectionView.reloadData { collectionView in print(collectionView) }
              
              // or
              collectionView.reloadItems(at: indexPathsToReload) { collectionView in print(collectionView) }
              
              // or
              collectionView.reloadSections(IndexSet(integer: 0)) { collectionView in print(collectionView) }
              

              完整样本

              不要忘记在此处添加解决方案代码

              import UIKit
              
              class ViewController: UIViewController {
              
                  private weak var navigationBar: UINavigationBar?
                  private weak var tableView: UITableView?
              
                  override func viewDidLoad() {
                      super.viewDidLoad()
                      setupNavigationItem()
                      setupTableView()
                  }
              }
              // MARK: - Activate UITableView reloadData with completion functions
              
              extension UITableView: ReloadCompletable { }
              
              // MARK: - Setup(init) subviews
              
              extension ViewController {
              
                  private func setupTableView() {
                      guard let navigationBar = navigationBar else { return }
                      let tableView = UITableView()
                      view.addSubview(tableView)
                      tableView.translatesAutoresizingMaskIntoConstraints = false
                      tableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true
                      tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
                      tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
                      tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
                      tableView.dataSource = self
                      self.tableView = tableView
                  }
              
                  private func setupNavigationItem() {
                      let navigationBar = UINavigationBar()
                      view.addSubview(navigationBar)
                      self.navigationBar = navigationBar
                      navigationBar.translatesAutoresizingMaskIntoConstraints = false
                      navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
                      navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
                      navigationBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
                      let navigationItem = UINavigationItem()
                      navigationItem.rightBarButtonItem = UIBarButtonItem(title: "all", style: .plain, target: self, action: #selector(reloadAllCellsButtonTouchedUpInside(source:)))
                      let buttons: [UIBarButtonItem] = [
                                                          .init(title: "row", style: .plain, target: self,
                                                                action: #selector(reloadRowButtonTouchedUpInside(source:))),
                                                          .init(title: "section", style: .plain, target: self,
                                                                action: #selector(reloadSectionButtonTouchedUpInside(source:)))
                                                          ]
                      navigationItem.leftBarButtonItems = buttons
                      navigationBar.items = [navigationItem]
                  }
              }
              
              // MARK: - Buttons actions
              
              extension ViewController {
              
                  @objc func reloadAllCellsButtonTouchedUpInside(source: UIBarButtonItem) {
                      let elementsName = "Data"
                      print("-- Reloading \(elementsName) started")
                      tableView?.reloadData { taleView in
                          print("-- Reloading \(elementsName) stopped \(taleView)")
                      }
                  }
              
                  private var randomRowAnimation: UITableView.RowAnimation {
                      return UITableView.RowAnimation(rawValue: (0...6).randomElement() ?? 0) ?? UITableView.RowAnimation.automatic
                  }
              
                  @objc func reloadRowButtonTouchedUpInside(source: UIBarButtonItem) {
                      guard let tableView = tableView else { return }
                      let elementsName = "Rows"
                      print("-- Reloading \(elementsName) started")
                      let indexPathToReload = tableView.indexPathsForVisibleRows?.randomElement() ?? IndexPath(row: 0, section: 0)
                      tableView.reloadRows(at: [indexPathToReload], with: randomRowAnimation) { _tableView in
                          //print("-- \(taleView)")
                          print("-- Reloading \(elementsName) stopped in \(_tableView)")
                      }
                  }
              
                  @objc func reloadSectionButtonTouchedUpInside(source: UIBarButtonItem) {
                      guard let tableView = tableView else { return }
                      let elementsName = "Sections"
                      print("-- Reloading \(elementsName) started")
                      tableView.reloadSections(IndexSet(integer: 0), with: randomRowAnimation) { _tableView in
                          //print("-- \(taleView)")
                          print("-- Reloading \(elementsName) stopped in \(_tableView)")
                      }
                  }
              }
              
              extension ViewController: UITableViewDataSource {
                  func numberOfSections(in tableView: UITableView) -> Int { return 1 }
                  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
                  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                      let cell = UITableViewCell()
                      cell.textLabel?.text = "\(Date())"
                      return cell
                  }
              }
              

              结果

              【讨论】:

                【解决方案17】:

                创建一个可重用的 CATransaction 扩展:

                public extension CATransaction {
                    static func perform(method: () -> Void, completion: @escaping () -> Void) {
                        begin()
                        setCompletionBlock {
                            completion()
                        }
                        method()
                        commit()
                    }
                }
                

                现在创建一个 UITableView 的扩展,它将使用 CATransaction 的扩展方法:

                public extension UITableView {
                    func reloadData(completion: @escaping (() -> Void)) {
                       CATransaction.perform(method: {
                           reloadData()
                       }, completion: completion)
                    }
                }
                

                用法:

                tableView.reloadData(completion: {
                    //Do the stuff
                })
                

                【讨论】:

                  【解决方案18】:

                  如果您在viewDidLoad 时重新加载数据,您可以将您的代码放入viewDidLayoutSubviews 方法中。但是你应该注意viewDidLayoutSubviews 可能会被多次调用。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2021-12-04
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2023-03-19
                    • 2018-03-01
                    • 2012-12-04
                    相关资源
                    最近更新 更多