UITableView完成请求数据时得到通知?

有什么方法可以找出UITableView何时完成从数据源请求数据?

这里没有关联视图控制器( UITableViewController )的viewDidLoad / viewWillAppear / viewDidAppear方法,因为它们都过早启动。 没有一个(完全可以理解)保证对数据源的查询已经完成(例如,直到视图滚动)。

我find的一个解决方法是在viewDidAppear调用reloadData ,因为当reloadData返回时,表视图保证已经完成查询数据源,只要它需要暂时。

然而,这似乎是相当讨厌的,因为我认为它导致数据源在第一次加载时被要求相同的信息两次(一次是自动的,一次是因为reloadData调用)。

我想这样做的原因是我想保留UITableView的滚动位置 – 但是直到像素级别,而不是最近的行。

当恢复滚动位置(使用scrollRectToVisible:animated: ,我需要表视图已经有足够的数据,否则scrollRectToVisible:animated:方法调用什么也不做(这是如果你自己的调用在任何viewDidLoadviewWillAppearviewDidAppear )。

这个答案似乎没有工作了,因为答案写了一些UITableView实现的变化。 看到这个评论: UITableView完成请求数据时得到通知?

我一直在玩这个问题几天,认为UITableViewreloadData子类是最好的方法:

 - (void)reloadData { NSLog(@"BEGIN reloadData"); [super reloadData]; NSLog(@"END reloadData"); } 

在表完成重新加载数据之前, reloadData不会结束。 所以,当第二个NSLog被触发时,表视图实际上已经完成了数据请求。

我已经subclassed UITableView发送方法到reloadData之前和之后的委托。 它像一个魅力。

我在我的应用程序中有相同的情况,并认为将我的答案发布给你们,因为这里提到的其他答案不适用于我的iOS7和更高版本

最后,这是我唯一解决的问题。

 [yourTableview reloadData]; dispatch_async(dispatch_get_main_queue(),^{ NSIndexPath *path = [NSIndexPath indexPathForRow:yourRow inSection:yourSection]; //Basically maintain your logic to get the indexpath [yourTableview scrollToRowAtIndexPath:path atScrollPosition:UITableViewScrollPositionTop animated:YES]; }); 

Swift更新:

 yourTableview.reloadData() dispatch_async(dispatch_get_main_queue(), { () -> Void in let path : NSIndexPath = NSIndexPath(forRow: myRowValue, inSection: mySectionValue) //Basically maintain your logic to get the indexpath yourTableview.scrollToRowAtIndexPath(path, atScrollPosition: UITableViewScrollPosition.Top, animated: true) }) 

所以这是如何工作的。

基本上当你重新加载主线程变得忙碌,所以当我们做一个调度asynchronous线程时,块将等待,直到主线程完成。 所以一旦tableview已经完全加载主线程将完成,所以它会调度我们的方法块

testingiOS7和iOS8 ,它的工作真棒;)

更新为iOS9:这个工作正常,也是iOS9。 我在github中创build了一个示例项目作为POC。 https://github.com/ipraba/TableReloadingNotifier

我附上了我的testing截图。

testing环境:Xcode7的iOS9 iPhone6模拟器

在这里输入图像描述

编辑:这个答案实际上不是一个解决scheme。 它可能起初似乎工作,因为重新加载可以发生得非常快,但实际上完成块不一定会在数据完全重新加载完成后调用 – 因为reloadData不会阻塞。 你可能应该寻找一个更好的解决scheme。

为了扩大@Eric MORAND的答案,让我们把一个完成块。谁不喜欢块?

 @interface DUTableView : UITableView - (void) reloadDataWithCompletion:( void (^) (void) )completionBlock; @end 

和…

 #import "DUTableView.h" @implementation DUTableView - (void) reloadDataWithCompletion:( void (^) (void) )completionBlock { [super reloadData]; if(completionBlock) { completionBlock(); } } @end 

用法:

 [self.tableView reloadDataWithCompletion:^{ //do your stuff here }]; 

reloadData只是要求可见单元格的数据。 说,要指定您的表的一部分被加载,请通知tableView: willDisplayCell:方法。

 - (void) reloadDisplayData { isLoading = YES; NSLog(@"Reload display with last index %d", lastIndex); [_tableView reloadData]; if(lastIndex <= 0){ isLoading = YES; //Notify completed } - (void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { if(indexPath.row >= lastIndex){ isLoading = NO; //Notify completed } 

这是我的解决scheme。 100%的作品,并在许多项目中使用。 这是一个简单的UITableView子类。

 @protocol MyTableViewDelegate<NSObject, UITableViewDelegate> @optional - (void)tableViewWillReloadData:(UITableView *)tableView; - (void)tableViewDidReloadData:(UITableView *)tableView; @end @interface MyTableView : UITableView { struct { unsigned int delegateWillReloadData:1; unsigned int delegateDidReloadData:1; unsigned int reloading:1; } _flags; } @end @implementation MyTableView - (id<MyTableViewDelegate>)delegate { return (id<MyTableViewDelegate>)[super delegate]; } - (void)setDelegate:(id<MyTableViewDelegate>)delegate { [super setDelegate:delegate]; _flags.delegateWillReloadData = [delegate respondsToSelector:@selector(tableViewWillReloadData:)]; _flags.delegateDidReloadData = [delegate respondsToSelector:@selector(tableViewDidReloadData:)]; } - (void)reloadData { [super reloadData]; if (_flags.reloading == NO) { _flags.reloading = YES; if (_flags.delegateWillReloadData) { [(id<MyTableViewDelegate>)self.delegate tableViewWillReloadData:self]; } [self performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f]; } } - (void)finishReload { _flags.reloading = NO; if (_flags.delegateDidReloadData) { [(id<MyTableViewDelegate>)self.delegate tableViewDidReloadData:self]; } } @end 

这与Josh Brown的解决scheme类似,只有一个例外。 performSelector方法不需要延迟。 不pipereloadData需要多长时间。 tableViewDidLoadData:tableView完成询问dataSource cellForRowAtIndexPath时总是触发。

即使你不想子类UITableView你可以简单地调用[performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f] ,你的select器将在表完成重载后立即调用。 但是您应该确保每次调用reloadData只调用一次select器:

 [self.tableView reloadData]; [self performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f]; 

请享用。 🙂

这是一个稍微不同的问题的答案:我需要知道什么时候UITableView也完成调用cellForRowAtIndexPath() 。 我subclassed layoutSubviews() (谢谢@埃里克莫兰德),并添加了一个委托callback:

SDTableView.h:

 @protocol SDTableViewDelegate <NSObject, UITableViewDelegate> @required - (void)willReloadData; - (void)didReloadData; - (void)willLayoutSubviews; - (void)didLayoutSubviews; @end @interface SDTableView : UITableView @property(nonatomic,assign) id <SDTableViewDelegate> delegate; @end; 

SDTableView.m:

 #import "SDTableView.h" @implementation SDTableView @dynamic delegate; - (void) reloadData { [self.delegate willReloadData]; [super reloadData]; [self.delegate didReloadData]; } - (void) layoutSubviews { [self.delegate willLayoutSubviews]; [super layoutSubviews]; [self.delegate didLayoutSubviews]; } @end 

用法:

MyTableViewController.h:

 #import "SDTableView.h" @interface MyTableViewController : UITableViewController <SDTableViewDelegate> @property (nonatomic) BOOL reloadInProgress; 

MyTableViewController.m:

 #import "MyTableViewController.h" @implementation MyTableViewController @synthesize reloadInProgress; - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { if ( ! reloadInProgress) { NSLog(@"---- numberOfSectionsInTableView(): reloadInProgress=TRUE"); reloadInProgress = TRUE; } return 1; } - (void)willReloadData {} - (void)didReloadData {} - (void)willLayoutSubviews {} - (void)didLayoutSubviews { if (reloadInProgress) { NSLog(@"---- layoutSubviewsEnd(): reloadInProgress=FALSE"); reloadInProgress = FALSE; } } 

注意:由于这是UITableView的子类,它已经有一个指向MyTableViewController的委托属性,所以不需要添加另一个属性。 “@dynamic委托”告诉编译器使用这个属性。 (这里有一个链接描述这个: http : //farhadnoorzay.com/2012/01/20/objective-c-how-to-add-delegate-methods-in-a-subclass/ )

必须将MyTableViewControllerUITableView属性更改为使用新的SDTableView类。 这是在Interface Builder Identity Inspector中完成的。 selectUITableViewUITableViewController ,并将其“Custom Class”设置为SDTableView

我发现了一些类似于获取TableView contentSize更改的通知。 我认为这也应该在这里工作,因为contentSize也随着加载数据而改变。

尝试这个:

viewDidLoad写入中,

 [self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:NULL]; 

并将此方法添加到您的viewController:

 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"contentSize"]) { DLog(@"change = %@", change.description) NSValue *new = [change valueForKey:@"new"]; NSValue *old = [change valueForKey:@"old"]; if (new && old) { if (![old isEqualToValue:new]) { // do your stuff } } } } 

您可能需要在检查更改时稍作修改。 这对我来说虽然工作。

干杯! 🙂

这是一个可能的解决scheme,虽然这是一个黑客:

 [self.tableView reloadData]; [self performSelector:@selector(scrollTableView) withObject:nil afterDelay:0.3]; 

其中-scrollTableView方法使用-scrollRectToVisible:animated:滚动表视图。 当然,你可以在上面的代码中configuration延迟从0.3到任何似乎适合你的工作。 是的,这是可笑的哈克,但它适用于我的iPhone 5和4S …

我有相似的东西我相信。 我添加了一个BOOL作为实例variables,它告诉我,如果偏移已经恢复,并检查在-viewWillAppear: 当它还没有被恢复时,我用这种方法恢复它,并设置BOOL来表明我确实恢复了偏移量。

这是一种黑客,它可能会做得更好,但这对我来说目前是有效的。

这听起来像你想更新单元格的内容,但没有突然的跳跃,可以伴随细胞插入和删除。

有几个关于这样做的文章。 这是一个。

我build议使用setContentOffset:animated:而不是scrollRectToVisible:animated:滚动视图的像素完美设置。

你可以尝试下面的逻辑:

 -(UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyIdentifier"]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"MyIdentifier"]; cell.selectionStyle = UITableViewCellSelectionStyleNone; } if ( [self chkIfLastCellIndexToCreate:tableView :indexPath]){ NSLog(@"Created Last Cell. IndexPath = %@", indexPath); //[self.activityIndicator hide]; //Do the task for TableView Loading Finished } prevIndexPath = indexPath; return cell; } -(BOOL) chkIfLastCellIndexToCreate:(UITableView*)tableView : (NSIndexPath *)indexPath{ BOOL bRetVal = NO; NSArray *visibleIndices = [tableView indexPathsForVisibleRows]; if (!visibleIndices || ![visibleIndices count]) bRetVal = YES; NSIndexPath *firstVisibleIP = [visibleIndices objectAtIndex:0]; NSIndexPath *lastVisibleIP = [visibleIndices objectAtIndex:[visibleIndices count]-1]; if ((indexPath.row > prevIndexPath.row) && (indexPath.section >= prevIndexPath.section)){ //Ascending - scrolling up if ([indexPath isEqual:lastVisibleIP]) { bRetVal = YES; //NSLog(@"Last Loading Cell :: %@", indexPath); } } else if ((indexPath.row < prevIndexPath.row) && (indexPath.section <= prevIndexPath.section)) { //Descending - scrolling down if ([indexPath isEqual:firstVisibleIP]) { bRetVal = YES; //NSLog(@"Last Loading Cell :: %@", indexPath); } } return bRetVal; } 

在调用reloadData之前,将prevIndexPath设置为nil。 喜欢:

 prevIndexPath = nil; [mainTableView reloadData]; 

我用NSLogstesting,这个逻辑似乎没问题。 您可以根据需要自定义/改进。

最后,我已经使我的代码与这个工作 –

 [tableView scrollToRowAtIndexPath:scrollToIndex atScrollPosition:UITableViewScrollPositionTop animated:YES]; 

有几件事情需要照顾 –

  1. 调用它“ - (UITableViewCell *)MyTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  2. 只要确保“scrollToRowAtIndexPath”消息被发送到UITableView的相关实例,在这种情况下这肯定是MyTableview。
  3. 在我的情况下,UIView是包含UITableView实例的视图
  4. 此外,这将被称为每个细胞负荷。 因此,在“cellForRowAtIndexPath”中放置一个逻辑,以避免多次调用“scrollToRowAtIndexPath”。

所有数据加载时,您可以调整您的tableview或在此方法中设置内容大小:

 - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { tableView.frame =CGRectMake(tableView.frame.origin.x, tableView.frame.origin.y, tableView.frame.size.width, tableView.contentSize.height); } 

我只是运行重复计划的计时器,并且只有当tableHeaderView的高度(意味着有表中的行内容)时,表的contentSize大。 在C#(monotouch)中的代码,但我希望这个想法是明确的:

  public override void ReloadTableData() { base.ReloadTableData(); // don't do anything if there is no data if (ItemsSource != null && ItemsSource.Length > 0) { _timer = NSTimer.CreateRepeatingScheduledTimer(TimeSpan.MinValue, new NSAction(() => { // make sure that table has header view and content size is big enought if (TableView.TableHeaderView != null && TableView.ContentSize.Height > TableView.TableHeaderView.Frame.Height) { TableView.SetContentOffset( new PointF(0, TableView.TableHeaderView.Frame.Height), false); _timer.Invalidate(); _timer = null; } })); } } 

在表格视图显示内容之前,不是调用UITableView layoutSubviews吗? 我注意到,一旦表视图完成加载数据,就会调用它,也许你应该朝这个方向调查。

从iOS 6开始, UITableview委托方法称为:

 -(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section 

一旦你的表重新载入成功,将执行。 您可以根据需要在此方法中进行自定义。

我在Swift中find的最佳解决scheme

 extension UITableView { func reloadData(completion: ()->()) { self.reloadData() dispatch_async(dispatch_get_main_queue()) { completion() } } } 

为什么不延伸?

 @interface UITableView(reloadComplete) - (void) reloadDataWithCompletion:( void (^) (void) )completionBlock; @end @implementation UITableView(reloadComplete) - (void) reloadDataWithCompletion:( void (^) (void) )completionBlock { [self reloadData]; if(completionBlock) { completionBlock(); } } @end 

滚动到结尾:

 [self.table reloadDataWithCompletion:^{ NSInteger numberOfRows = [self.table numberOfRowsInSection:0]; if (numberOfRows > 0) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:numberOfRows-1 inSection:0]; [self.table scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO]; } }]; 

没有用大量的数据进行testing