UITableView完成请求数据时得到通知?
有什么方法可以找出UITableView
何时完成从数据源请求数据?
这里没有关联视图控制器( UITableViewController
)的viewDidLoad
/ viewWillAppear
/ viewDidAppear
方法,因为它们都过早启动。 没有一个(完全可以理解)保证对数据源的查询已经完成(例如,直到视图滚动)。
我find的一个解决方法是在viewDidAppear
调用reloadData
,因为当reloadData
返回时,表视图保证已经完成查询数据源,只要它需要暂时。
然而,这似乎是相当讨厌的,因为我认为它导致数据源在第一次加载时被要求相同的信息两次(一次是自动的,一次是因为reloadData
调用)。
我想这样做的原因是我想保留UITableView
的滚动位置 – 但是直到像素级别,而不是最近的行。
当恢复滚动位置(使用scrollRectToVisible:animated:
,我需要表视图已经有足够的数据,否则scrollRectToVisible:animated:
方法调用什么也不做(这是如果你自己的调用在任何viewDidLoad
, viewWillAppear
或viewDidAppear
)。
这个答案似乎没有工作了,因为答案写了一些UITableView实现的变化。 看到这个评论: UITableView完成请求数据时得到通知?
我一直在玩这个问题几天,认为UITableView
的reloadData
子类是最好的方法:
- (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/ )
必须将MyTableViewController
的UITableView
属性更改为使用新的SDTableView
类。 这是在Interface Builder Identity Inspector中完成的。 selectUITableView
的UITableViewController
,并将其“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];
有几件事情需要照顾 –
- 调用它“
- (UITableViewCell *)MyTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
” - 只要确保“scrollToRowAtIndexPath”消息被发送到UITableView的相关实例,在这种情况下这肯定是MyTableview。
- 在我的情况下,UIView是包含UITableView实例的视图
- 此外,这将被称为每个细胞负荷。 因此,在“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