如何判断UITableVIew何时完成ReloadData?
我正在尝试滚动到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是asynchronous的,所以滚动不会发生,因为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
我得到部分= 1和行= 8; (8是对的)
重新加载发生在下一个布局过程中,这通常发生在将控制权返回给运行循环(在你的button动作或任何返回之后)之后。
因此,在表视图重新加载之后运行某些东西的方法就是强制表视图立即执行布局:
[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
经过进一步调查,我发现表视图在从reloadData
返回之前将tableView:numberOfRowsInSection:
发送到其数据源。 如果委托实现了tableView:heightForRowAtIndexPath:
则表视图也会在从reloadData
返回之前发送(每行)。
但是,表视图不会发送tableView:cellForRowAtIndexPath:
直到布局阶段,当您将控制权返回到运行循环时,默认情况下会发生这种情况。
而且我还发现,在一个小小的testing程序中,您的问题中的代码正确地滚动到表视图的底部, 而无需执行任何特殊的操作(如发送layoutIfNeeded
或使用dispatch_async
)。
迅速:
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... }];
上面的dispatch_async(dispatch_get_main_queue())
方法不能保证工作 。 我看到了它的非确定性行为,其中有时系统已经完成layoutSubviews和单元渲染之前完成块,有时之后。
这是一个解决scheme,在我的iOS 10上工作100%。它需要能够将UITableView或UICollectionView实例化为自定义子类。 这里是UICollectionView的解决scheme,但是和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 }];
我和Tyler Sheaffer有同样的问题。
我在Swift中实现了他的解决scheme ,解决了我的问题。
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 super.reloadData() } }
Swift 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 }
看来人们还在读这个问题和答案。 B / C的,我正在编辑我的答案删除单词同步这是真的不相关的。
When [tableView reloadData]
返回时,tableView后面的内部数据结构已被更新。 因此,方法完成后,您可以安全地滚动到底部。 我在我自己的应用程序中进行了validation @ rob-mayoff广泛接受的答案虽然在术语上也令人困惑,但在最近的更新中也承认这一点。
如果你的tableView
没有滚动到底部,你可能在你没有发布的其他代码中有问题。 也许你在滚动完成后正在改变数据,你不重新加载和/或滚动到底部呢?
按如下所示添加一些日志logging,以validationreloadData
之后的表数据是否正确。 我在示例应用程序中有以下代码,它完美的工作。
// 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];
我使用这个技巧,很确定我已经把它发布到这个问题的一个副本:
-(void)tableViewDidLoadRows:(UITableView *)tableView{ // do something after loading, eg 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; }
其实这个解决了我的问题:
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { NSSet *visibleSections = [NSSet setWithArray:[[tableView indexPathsForVisibleRows] valueForKey:@"section"]]; if (visibleSections) { // hide the activityIndicator/Loader }}
从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()的结束。
尝试这种方式,它会工作
[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
当表被完全加载时,我将执行
其他解决scheme是你可以inheritanceUITableView
我最终使用了Shawn解决scheme的一个变种:
用委托创build一个自定义的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:
还有一个基于kolaworld的答案的UICollectionView
版本:
https://stackoverflow.com/a/43162226/1452758
需要testing。 到目前为止,在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. ie 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) }
重新加载数据后,您可以使用它来做一些事情:
[UIView animateWithDuration:0 animations:^{ [self.contentTableView reloadData]; } completion:^(BOOL finished) { _isUnderwritingUpdate = NO; }];
尝试设置延迟:
[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2]; [_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];