如何辨别真假时的UITableView已完成ReloadData?如何辨别真假时的UITableVi

2019-05-08 14:41发布

我想它完成后执行滚动到一个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);

在控制台它返回第= 1,行= -1;

当我做同样的NSLogs中cellForRowAtIndexPath我获得第= 1和行= 8; (图8是右)

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

UPDATE

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

然而,表视图不会发送tableView:cellForRowAtIndexPath:tableView:headerViewForSection直到布局阶段,在默认情况下,当您返回控制运行循环发生。

我还发现,在一个很小的测试程序,在你的问题的代码可以正确滚动到表视图的底部, 没有我做什么特别的(如发送layoutIfNeeded或使用dispatch_async )。



Answer 2:

迅速:

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

...somewhere later...

tableView.reloadData {
    println("done")
}

Objective-C的:

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


Answer 3:

作为Xcode的8.2.1,iOS装置10,和迅速3,

您可以确定的端tableView.reloadData()容易地通过使用CATransaction块:

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

上述也适用于确定UICollectionView的reloadData()和UIPickerView的reloadAllComponents()的末尾。



Answer 4:

所述dispatch_async(dispatch_get_main_queue())方法上面不能保证正常工作 。 我看到它不确定性的行为,其中有时系统已完成layoutSubviews和完成块之前的单元格呈现,有时后。

下面是对我的作品100%的溶液,在iOS 10.需要实例化的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;
    [super reloadData];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.reloadDataCompletionBlock) {
        self.reloadDataCompletionBlock();
        self.reloadDataCompletionBlock = nil;
    }
}

@end

实例:

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

见这里的这个答案的斯威夫特版本



Answer 5:

我有同样的问题,如泰勒犀飞利。

我实现了自己的解决方案在斯威夫特和它解决了我的问题。

雨燕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
    super.reloadData()
  }
}

斯威夫特2:

class UITableViewWithReloadCompletion: UITableView {

  var reloadDataCompletionBlock: (() -> Void)?

  override func layoutSubviews() {
    super.layoutSubviews()

    self.reloadDataCompletionBlock?()
    self.reloadDataCompletionBlock = nil
  }

  func reloadDataWithCompletion(completion:() -> Void) {
      reloadDataCompletionBlock = completion
      super.reloadData()
  }
}

实例应用:

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


Answer 6:

看来人们还在读这些问题和答案。 B的/ C,我编辑我的回答字去掉同步这实在是无关紧要了这一点。

When [tableView reloadData]返回时,后面的tableView内部数据结构已被更新。 因此,该方法完成时,你可以放心地滚动至底部。 我在自己的应用程序验证了这一点。 通过@广为接受的答案抢-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];


Answer 7:

UICollectionView版本,基于kolaworld的回答:

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

需要测试。 工作迄今在iOS 9.2时,Xcode 9.2的β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)
}


Answer 8:

我用这一招,很肯定我已经张贴到这个问题的重复:

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


Answer 9:

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

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

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


Answer 10:

试试这个方法,将工作

[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



Answer 11:

最后我用肖恩的解决方案的变化:

创建一个代表一个自定义的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在界面生成器:



Answer 12:

试试这个:

tableView.backgroundColor =。黑

tableView.reloadData()

DispatchQueue.main.async(执行:{

tableView.backgroundColor = .green

})

//该颜色的tableView从黑色后,才reloadData()函数完全变成绿色。



Answer 13:

在雨燕3.0 +,我们可以创建一个扩展UITableViewescaped Closure象下面这样:

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

并使用它像下面在任何你想要的:

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

希望这将帮助别人。 干杯!



Answer 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()


Answer 15:

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

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


Answer 16:

尝试设置延迟:

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


文章来源: How to tell when UITableView has completed ReloadData?