How to tell when UITableView has completed ReloadD

2019-01-01 11:57发布

I am trying to scroll to the bottom of a UITableView after it is done performing [self.tableView reloadData]

I originally had

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

But then I read that reloadData is asynchronous, so the scrolling doesn't happen since the self.tableView, [self.tableView numberOfSections] and [self.tableView numberOfRowsinSection are all 0.

Thanks!

What's weird is that I am using:

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

In the console it returns Sections = 1, Row = -1;

When I do the exact same NSLogs in cellForRowAtIndexPath I get Sections = 1 and Row = 8; (8 is right)

14条回答
千与千寻千般痛.
2楼-- · 2019-01-01 12:34

Try this way it will work

[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

I will execute when table is completely loaded

Other Solution is you can subclass UITableView

查看更多
姐姐魅力值爆表
3楼-- · 2019-01-01 12:34

In Swift 3.0 + we can create a an extension for UITableView with a escaped Closure like below :

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

And Use it like Below where ever you want :

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

hope this will help to someone. cheers!

查看更多
春风洒进眼中
4楼-- · 2019-01-01 12:35

The dispatch_async(dispatch_get_main_queue()) method above is not guaranteed to work. I'm seeing non-deterministic behavior with it, in which sometimes the system has completed the layoutSubviews and the cell rendering before the completion block, and sometimes after.

Here's a solution that works 100% for me, on iOS 10. It requires the ability to instantiate the UITableView or UICollectionView as a custom subclass. Here's the UICollectionView solution, but it's exactly the same for 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

Example usage:

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

See here for a Swift version of this answer

查看更多
后来的你喜欢了谁
5楼-- · 2019-01-01 12:35

It appears folks are still reading this question and the answers. B/c of that, I'm editing my answer to remove the word Synchronous which is really irrelevant to this.

When [tableView reloadData] returns, the internal data structures behind the tableView have been updated. Therefore, when the method completes you can safely scroll to the bottom. I verified this in my own app. The widely accepted answer by @rob-mayoff, while also confusing in terminology, acknowledges the same in his last update.

If your tableView isn't scrolling to the bottom you may have an issue in other code you haven't posted. Perhaps you are changing data after scrolling is complete and you're not reloading and/or scrolling to the bottom then?

Add some logging as follows to verify that the table data is correct after reloadData. I have the following code in a sample app and it works perfectly.

// 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];
查看更多
只若初见
6楼-- · 2019-01-01 12:36

As of Xcode 8.2.1, iOS 10, and swift 3,

You can determine the end of tableView.reloadData() easily by using a CATransaction block:

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

The above also works for determining the end of UICollectionView's reloadData() and UIPickerView's reloadAllComponents().

查看更多
裙下三千臣
7楼-- · 2019-01-01 12:36

Try setting delays:

[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2];
[_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];
查看更多
登录 后发表回答