有没有办法找出UITableView
何时完成从数据源请求数据?
相关视图控制器(viewDidLoad
)的viewWillAppear
/ viewDidAppear
/ UITableViewController
方法都没有在这里使用,因为它们都过早发生。其中没有一个(完全可以理解)保证对数据源的查询暂时完成(例如,直到滚动视图)。
我找到的一种解决方法是在reloadData
中调用viewDidAppear
,因为当reloadData
返回时,表格视图 保证已完成查询暂时需要的数据源。
然而,这看起来相当令人讨厌,因为我认为它首次加载时会导致数据源被要求提供相同的信息两次(一次是自动的,一次是因为reloadData
调用)。 / p>
我想要这样做的原因是我想要保留UITableView
的滚动位置 - 但是直到像素级别,而不仅仅是最近的行。
当恢复滚动位置时(使用scrollRectToVisible:animated:
),我需要表视图中已有足够的数据,否则scrollRectToVisible:animated:
方法调用什么都不做(如果放置则会发生这种情况在viewDidLoad
,viewWillAppear
或viewDidAppear
中的任意一项中自行调用。
答案 0 :(得分:60)
这个答案似乎不再起作用,因为自编写答案以来对UITableView实现进行了一些更改。请参阅此评论:Get notified when UITableView has finished asking for data?
我一直在玩这个问题几天,并认为继承UITableView
的{{1}}是最好的方法:
reloadData
在表重新加载其数据之前, - (void)reloadData {
NSLog(@"BEGIN reloadData");
[super reloadData];
NSLog(@"END reloadData");
}
没有结束。因此,当第二个reloadData
被触发时,表视图实际上已经完成了对数据的询问。
我已将NSLog
子类化为UITableView
之前和之后向委托发送方法。它就像一个魅力。
答案 1 :(得分:50)
我在我的应用程序中确实有相同的场景,并且我会将答案发给你们,因为这里提到的其他答案对我来说对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];
});
快速更新:
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)
})
这是如何运作的。
基本上当你重新加载时,主线程变得很忙,所以当我们执行调度异步线程时,该块将等到主线程完成。因此,一旦完成了tableview的加载,主线程将完成,因此它将调度我们的方法块
在iOS7和iOS8中测试,效果很棒;)
iOS9更新:这也很好用iOS9也行。我在github中创建了一个示例项目作为POC。 https://github.com/ipraba/TableReloadingNotifier
我在这里附上我的测试截图。
经测试的环境:来自Xcode7的iOS9 iPhone6模拟器
答案 2 :(得分:25)
编辑:这个答案实际上不是解决方案。它似乎首先起作用,因为重新加载可以非常快地发生,但实际上完成块不一定在数据完全重新加载后被调用 - 因为reloadData不会阻塞。您应该寻找更好的解决方案。
为了扩展@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
}];
答案 3 :(得分:11)
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
}
答案 4 :(得分:10)
这是我的解决方案。 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's solution类似,但有一个例外。 performSelector方法不需要延迟。 无论reloadData
多长时间。 tableViewDidLoadData:
完成询问tableView
dataSource
时始终会触发cellForRowAtIndexPath
。
即使您不想继承UITableView
,也可以简单地调用[performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f]
,并在表重新加载后立即调用您的选择器。但是你应该确保每次调用reloadData
时只调用一次选择器:
[self.tableView reloadData];
[self performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f];
享受。 :)
答案 5 :(得分:8)
这是一个稍微不同的问题的答案:我需要知道UITableView
何时也完成了调用cellForRowAtIndexPath()
。我将layoutSubviews()
子类化(感谢@Eric MORAND)并添加了一个委托回调:
<强> 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 delegate”告诉编译器使用此属性。 (这是一个描述这个的链接:http://farhadnoorzay.com/2012/01/20/objective-c-how-to-add-delegate-methods-in-a-subclass/)
必须更改UITableView
中的MyTableViewController
属性才能使用新的SDTableView
类。这是在Interface Builder Identity Inspector中完成的。选择UITableView
内的UITableViewController
,将其“自定义类”设置为SDTableView
。
答案 6 :(得分:5)
我找到了类似的内容,以便在contentSize
TableView
中收到有关更改的通知。我认为这也应该适用于此,因为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
}
}
}
}
您可能需要稍微修改检查更改。这虽然对我有用。
干杯! :)
答案 7 :(得分:2)
这是一个可能的解决方案,虽然它是一个黑客:
[self.tableView reloadData];
[self performSelector:@selector(scrollTableView) withObject:nil afterDelay:0.3];
您的-scrollTableView
方法使用-scrollRectToVisible:animated:
滚动表格视图。当然,您可以将上面代码中的延迟从0.3配置为适合您的任何内容。是的,这是荒谬的hacky,但它适用于我的iPhone 5和4S ......
答案 8 :(得分:1)
-viewWillAppear:
中检查。当它还没有恢复时,我在该方法中恢复它并设置BOOL以指示我确实恢复了偏移。
这是一种破解,它可能会做得更好,但这对我来说很有用。
答案 9 :(得分:1)
听起来你想要更新细胞内容,但没有伴随细胞插入和删除的突然跳跃。
有几篇关于这样做的文章。 This is one.
我建议使用setContentOffset:animated:而不是scrollRectToVisible:animated:用于滚动视图的像素完美设置。
答案 10 :(得分:1)
您可以尝试以下逻辑:
-(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];
我用NSLogs测试过,这个逻辑似乎没问题。您可以根据需要自定义/改进。
答案 11 :(得分:0)
您可以在加载所有数据时调整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);
}
答案 12 :(得分:0)
我在Swift中找到的最佳解决方案
extension UITableView {
func reloadData(completion: ()->()) {
self.reloadData()
dispatch_async(dispatch_get_main_queue()) {
completion()
}
}
}
答案 13 :(得分:0)
从iOS 6开始,UITableview
委托方法调用:
-(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
一旦表重新加载成功,将执行。您可以根据需要在此方法中进行自定义。
答案 14 :(得分:0)
我只是在tableHeaderView高度(表示表中有行内容)时,只有当table的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;
}
}));
}
}
答案 15 :(得分:0)
最后我让我的代码使用了这个 -
[tableView scrollToRowAtIndexPath:scrollToIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];
很少有事情需要照顾 -
- (UITableViewCell *)MyTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
”答案 16 :(得分:0)
在表格视图显示内容之前,是否UITableView
layoutSubviews
被调用了吗?我注意到一旦表视图加载完它的数据就会调用它,也许你应该调查那个方向。
答案 17 :(得分:-1)
为什么不延伸?
@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];
}
}];
未经过大量数据测试