如何判断UITableView何时完成ReloadData?


183

执行完后,我试图滚动到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;

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


这个问题的可能重复:stackoverflow.com/questions/4163579/…–
pmk

2
我见过的最佳解决方案。stackoverflow.com/questions/1483581/...
哈立德Annajar

我对以下问题的回答可能对您有帮助,stackoverflow.com
questions/4163579/…

Answers:


288

重新加载发生在下一个布局遍,通常在将控制权返回到运行循环时发生(例如,在您执行按钮操作或返回任何内容之后)。

因此,在重新加载表格视图后运行某种方法的一种方法就是简单地强制表格视图立即执行布局:

[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];
});

更新

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

但是,表视图不会发送tableView:cellForRowAtIndexPath:tableView:headerViewForSection直到布局阶段才发送,这默认情况是在将控制权返回到运行循环时发生的。

我还发现,在一个很小的测试程序中,您问题中的代码可以正确滚动到表视图的底部,而无需我做任何特殊的事情(例如发送layoutIfNeeded或使用dispatch_async)。


3
@rob,根据表数据源的大小,您可以在同一运行循环中动画化tableview的底部。如果您使用巨大的表尝试测试代码,则使用GCD延迟滚动直到下一个运行循环有效的技巧,而立即滚动将失败。但是无论如何,谢谢你的把戏!
T先生

7
方法2出于某种未知原因对我不起作用,而是选择了第一种方法。
Raj Pawan Gumdal 2014年

4
dispatch_async(dispatch_get_main_queue())方法不能保证有效。我看到的是不确定性行为,其中有时系统在完成块之前(有时在之后)完成了layoutSubviews和单元格渲染。我将在下面发布对我有用的答案。
泰勒·谢弗

3
同意dispatch_async(dispatch_get_main_queue())并非总是有效。在这里看到随机结果。
Vojto

1
主线程运行一个NSRunLoop。运行循环具有不同的阶段,您可以安排特定阶段的回调(使用CFRunLoopObserver)。UIKit计划在事件处理程序返回后在以后的阶段进行布局。
rob mayoff

106

迅速:

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

...somewhere later...

tableView.reloadData {
    println("done")
}

目标C:

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

16
这等效于在“不久的将来”在主线程上分发某些内容。您很可能只是在主线程使完成块出队之前看到表格视图已渲染对象。不建议首先进行这种黑客攻击,但是在任何情况下,如果要尝试这样做,都应该使用dispatch_after。
seo 2014年

1
Rob的解决方案很好,但是如果表视图中没有任何行,则该解决方案将不起作用。即使表不包含任何行而仅包含各节,Aviel的解决方案仍具有工作上的优势。
Chrstph SLN 2014年

@Christophe到目前为止,通过在我的Mock视图控制器tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int中重写该方法并在我的重写中插入任何我想通知重新加载已完成的内容,我可以在表视图中使用Rob更新,而没有任何行。
Gobe

49

从Xcode 8.2.1,iOS 10和Swift 3开始,

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

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

上面的方法还可以确定UICollectionView的reloadData()和UIPickerView的reloadAllComponents()的结尾。


if如果您要执行自定义重新加载,例如在表视图中的beginUpdatesendUpdates调用中手动插入,删除或移动行,我也可以工作。
达尔拉尔斯基'18

我相信这实际上是现代的解决方案。确实,这是iOS中的常见模式,例如... stackoverflow.com/a/47536770/294884
Fattie

我试过了 我的行为很奇怪。我的tableview正确显示两个headerViews。在setCompletionBlock我的numberOfSections节目2内...到目前为止还不错。但是如果setCompletionBlock我在里面做,tableView.headerView(forSection: 1)它会返回nil!!!因此,我认为此块是在重新加载之前发生的,还是在捕获之前发生的,或者我做错了什么。仅供参考,我确实尝试过泰勒的答案,并且行得通!@Fattie
Honey,

32

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。希望您不要介意
Jon Vogel

2
调用其中的块之后layoutSubviews,应将其设置为nil对的后续调用layoutSubviews(不一定是由于reloadData被调用而定),因为存在强引用,这将导致执行该块,这不是所需的行为。
Mark Bourke

为什么我不能将其用于UITableView?它没有显示可见的界面。我也导入了标头文件,但仍然相同
Julfikar,2017年

2
该答案的补充内容是,如果只有一个回调,则有可能破坏现有的回调,这意味着多个调用者将具有竞争条件。解决方案是创建reloadDataCompletionBlock一个块数组,并在执行时对其进行迭代,然后再清空该数组。
Tyler Sheaffer

1)这不等同于Rob的第一个答案,即使用layoutIfNeeded吗?2)为什么提到iOS 10,但在iOS 9上不起作用?
亲爱的

30

我和泰勒·谢弗(Tyler Sheaffer)有同样的问题。

我在Swift中实现了他的解决方案,它解决了我的问题。

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
}

1
真好!小巧的选择,您可以if let说出reloadDataCompletionBlock?()会叫iff not nil的方式删除the
Tyler Sheaffer

对于我在ios9上的情况,这没有运气
Matjan'2

self.reloadDataCompletionBlock? { completion() }应该是self.reloadDataCompletionBlock?()
-emem

如何处理表格视图高度的调整大小?我以前打电话给tableView.beginUpdates()tableView.layoutIfNeeded()tableView.endUpdates()
Parth Tamane

10

还有一个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)
}

6

看来人们仍在阅读这个问题和答案。鉴于此,我正在编辑我的答案,以删除与该词实际上无关的“ 同步 ”一词。

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为什么会这样输出吗?
艾伦(Alan)

8
reloadData不同步。它曾经是-查看此答案:stackoverflow.com/a/16071589/193896
bentytree

1
它是同步的。使用示例应用程序进行测试并查看它非常容易。您已链接到该问题中@rob的答案。如果您在底部阅读了他的更新,他也已对此进行了验证。也许您在谈论视觉布局的变化。的确,tableView不会被可视地同步更新,而数据已被同步更新。这就是OP reloadData退货后立即需要正确的值的原因。
XJones

1
您可能对预期会发生什么感到困惑reloadData。用我的测试用例viewWillAppear接受scrollToRowAtIndexPath:b / c行,如果tableView未显示,则毫无意义。您将看到它reloadData确实更新了tableView实例中缓存的数据,并且reloadData是同步的。如果您引用的其他tableView委托方法tableView是在布局时调用的,则在tableView未显示时将不会调用这些方法。如果我误解了您的情况,请解释。
XJones

3
有趣的时刻。现在是2014年,关于某个方法是同步还是异步存在争议。感觉像猜测。该方法名称后面的所有实现细节都是完全不透明的。编程不好吗?
fatuhoku 2014年

5

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

-(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尚不清楚您是将其表示为正面评论还是负面评论。但是我看到您还评论了另一个答案,因为“这似乎是最好的解决方案!” ,所以我想相对而言,您认为此解决方案不是最佳解决方案。
心教堂

1
依靠假动画的副作用吗?绝不是一个好主意。学习执行选择器或GCD并正确执行。顺便说一句,现在有一个表加载方法,如果您不介意使用私有协议,则可以使用该方法,因为它是调用代码的框架,而不是其他方法,所以可能很好。
malhal

3

实际上,这解决了我的问题:

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

NSSet *visibleSections = [NSSet setWithArray:[[tableView indexPathsForVisibleRows] valueForKey:@"section"]];
if (visibleSections) {
    // hide the activityIndicator/Loader
}}

2

尝试这种方式将起作用

[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


1

我最终使用了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:

在此处输入图片说明


这是可行的,但问题是该方法在每次加载单个Cell时都会被命中,而不是整个表视图重新加载,因此很明显,此答案与所问问题无关。
Yash Bedi '18

是的,它被调用了不止一次,但并非在每个单元上都被调用。因此,您可以听第一个委托,而忽略其余的委托,直到再次调用reloadData为止。
山姆

1

在Swift 3.0及更高版本中,我们可以UITableView使用escaped Closure如下所示创建扩展:

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

并像下面一样在任何需要的地方使用它:

Your_Table_View.reloadData {
   print("reload done")
 }

希望这对某人有帮助。干杯!


绝妙的主意。为了避免混淆,我更改了函数名t reload,而不是reloadData()。谢谢
Vijay Kumar AB

1

细节

  • 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
    }
}

结果

在此处输入图片说明


0

只是提供另一种方法,其依据是完成是要发送给的“最后一个可见”单元格的想法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()

0

试试这个:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

仅在reloadData()功能完成后,tableView颜色才会从黑色变为绿色。


0

您可以使用uitableview的performBatchUpdates函数

这是您可以实现的方法

self.tableView.performBatchUpdates({

      //Perform reload
        self.tableView.reloadData()
    }) { (completed) in

        //Reload Completed Use your code here
    }

0

创建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
})

-2

您可以在重新加载数据后使用它来执行以下操作:

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

-20

尝试设置延迟:

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

14
这很危险。如果重新加载所需的时间比您的延迟时间长,该怎么办?
2014年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.