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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1483581/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:28:06  来源:igfitidea点击:

Get notified when UITableView has finished asking for data?

iosiphoneuitableview

提问by kennethmac2000

Is there some way to find out when a UITableViewhas finished asking for data from its data source?

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

None of the viewDidLoad/viewWillAppear/viewDidAppearmethods of the associated view controller (UITableViewController) are of use?here, as they all fire too early. None of them (entirely understandably) guarantee that queries to the data source have finished for the time being (eg, until the view is scrolled).

的无viewDidLoad/ viewWillAppear/viewDidAppear关联的视图控制器(方法UITableViewController)是使用的?在这里,因为他们都火得太早。它们中没有一个(完全可以理解)保证对数据源的查询暂时完成(例如,直到滚动视图)。

One workaround I have found is to call reloadDatain viewDidAppear, since, when reloadDatareturns, the table view isguaranteed to have finished querying the data source as much as it needs to for the time being.

我发现的一种解决方法是调用reloadDatain viewDidAppear,因为当reloadData返回时,表视图可以保证暂时完成对数据源的查询。

However, this seems rather nasty, as I assume it is causing the data source to be asked for the same information twice (once automatically, and once because of the reloadDatacall) when it is first loaded.

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

The reason I want to do this at all is that I want to preserve the scroll position of the UITableView- but right down to the pixel level, not just to the nearest row.

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

When restoring the scroll position (using scrollRectToVisible:animated:), I need the table view to already have sufficient data in it, or else the scrollRectToVisible:animated:method call does nothing (which is what happens if you place the call on its own in any of viewDidLoad, viewWillAppearor viewDidAppear).

在恢复滚动位置(使用scrollRectToVisible:animated:)时,我需要表视图中已经有足够的数据,否则scrollRectToVisible:animated:方法调用什么都不做(如果您将调用放在viewDidLoadviewWillAppear或中的任何一个中,就会发生这种情况viewDidAppear)。

回答by Eric MORAND

This answer doesn't seem to be working anymore, due to some changes made to UITableView implementation since the answer was written. See this comment : Get notified when UITableView has finished asking for data?

由于自编写答案以来对 UITableView 实现进行了一些更改,此答案似乎不再有效。请参阅此评论:在 UITableView 完成请求数据时收到通知?

I've been playing with this problem for a couple of days and think that subclassing UITableView's reloadDatais the best approach :

我一直在玩这个问题困扰了几天,觉得子类UITableViewreloadData是最好的方法:

- (void)reloadData {

    NSLog(@"BEGIN reloadData");

    [super reloadData];

    NSLog(@"END reloadData");

}

reloadDatadoesn't end before the table has finish reload its data. So, when the second NSLogis fired, the table view has actually finish asking for data.

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

I've subclassed UITableViewto send methods to the delegate before and after reloadData. It works like a charm.

我已经UITableView将方法子类化为在 之前和之后向委托发送方法reloadData。它就像一个魅力。

回答by iPrabu

I did have a same scenario in my app and thought would post my answer to you guys as other answers mentioned here does not work for me for iOS7 and later

我的应用程序中确实有相同的场景,我认为会向你们发布我的答案,因为这里提到的其他答案不适用于 iOS7 及更高版本

Finally this is the only thing that worked out for me.

最后,这是唯一对我有用的东西。

[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 Update:

迅捷更新:

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)

})

So how this works.

那么这是如何工作的。

Basically when you do a reload the main thread becomes busy so at that time when we do a dispatch async thread, the block will wait till the main thread gets finished. So once the tableview has been loaded completely the main thread will gets finish and so it will dispatch our method block

基本上,当您重新加载时,主线程变得忙碌,因此当我们执行调度异步线程时,该块将等待主线程完成。因此,一旦 tableview 完全加载,主线程将完成,因此它将调度我们的方法块

Tested in iOS7 and iOS8and it works awesome;)

在 iOS7 和 iOS8 中测试过,效果很棒;)

Update for iOS9:This just works fine is iOS9 also. I have created a sample project in github as a POC. https://github.com/ipraba/TableReloadingNotifier

iOS9 更新:这也适用于 iOS9。我在 github 中创建了一个示例项目作为 POC。 https://github.com/ipraba/TableReloadingNotifier

I am attaching the screenshot of my test here.

我在这里附上我的测试截图。

Tested Environment: iOS9 iPhone6 simulator from Xcode7

测试环境:来自Xcode7的iOS9 iPhone6模拟器

enter image description here

在此处输入图片说明

回答by bandejapaisa

EDIT: This answer is actually not a solution. It probably appears to work at first because reloading can happen pretty fast, but in fact the completion block doesn't necessarily get called after the data has fully finished reloading - because reloadData doesn't block. You should probably search for a better solution.

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

To expand on @Eric MORAND's answer, lets put a completion block in. Who doesn't love a block?

为了扩展@Eric MORAND 的回答,让我们放入一个完成块。谁不喜欢一个块?

@interface DUTableView : UITableView

   - (void) reloadDataWithCompletion:( void (^) (void) )completionBlock;

@end

and...

和...

#import "DUTableView.h"

@implementation DUTableView

- (void) reloadDataWithCompletion:( void (^) (void) )completionBlock {
    [super reloadData];
    if(completionBlock) {
        completionBlock();
    }
}

@end

Usage:

用法:

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

回答by Black Coder

reloadData just asking for data for the visible cells. Says, to be notified when specify portion of your table is loaded, please hook the tableView: willDisplayCell:method.

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
}

回答by Mark Kryzhanouski

That is my solution. 100% works and used in many projects. It's a simple UITableView subclass.

这就是我的解决方案。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

It's similar to Josh Brown's solutionwith one exception. No delay is needed in performSelector method. No matter how long reloadDatatakes. tableViewDidLoadData:always fires when tableViewfinishes asking dataSourcecellForRowAtIndexPath.

它类似于Josh Brown 的解决方案,但有一个例外。performSelector 方法不需要延迟。不管需要多久reloadDatatableViewDidLoadData:tableView完成询问时总是触发dataSourcecellForRowAtIndexPath

Even if you do not want to subclass UITableViewyou can simply call [performSelector:@selector(finishReload) withObject:nil afterDelay:0.0f]and your selector will be called right after the table finishes reloading. But you should ensure that selector is called only once per call to reloadData:

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

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

Enjoy. :)

享受。:)

回答by Symmetric

This is an answer to a slightly different question: I needed to know when UITableViewhad also finished calling cellForRowAtIndexPath(). I subclassed layoutSubviews()(thanks @Eric MORAND) and added a delegate callback:

这是对一个稍微不同的问题的回答:我需要知道何时UITableView也完成了调用cellForRowAtIndexPath()。我子类化layoutSubviews()(感谢@Eric MORAND)并添加了一个委托回调:

SDTableView.h:

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:

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

Usage:

用法:

MyTableViewController.h:

MyTableViewController.h:

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

MyTableViewController.m:

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;
    }
}

NOTES:Since this is a subclass of UITableViewwhich already has a delegate property pointing to MyTableViewControllerthere's no need to add another one. The "@dynamic delegate" tells the compiler to use this property. (Here's a link describing this: http://farhadnoorzay.com/2012/01/20/objective-c-how-to-add-delegate-methods-in-a-subclass/)

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

The UITableViewproperty in MyTableViewControllermust be changed to use the new SDTableViewclass. This is done in the Interface Builder Identity Inspector. Select the UITableViewinside of the UITableViewControllerand set its "Custom Class" to SDTableView.

UITableView物业MyTableViewController必须更改为使用新SDTableView类。这是在 Interface Builder Identity Inspector 中完成的。选择UITableView内部UITableViewController并将其“自定义类”设置为SDTableView

回答by Abdullah Umer

I had found something similar to get notification for change in contentSizeof TableView. I think that should work here as well since contentSize also changes with loading data.

我已经发现了类似以获取更改通知的东西contentSizeTableView。我认为这也应该在这里工作,因为 contentSize 也会随着加载数据而变化。

Try this:

尝试这个:

In viewDidLoadwrite,

viewDidLoad写,

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

and add this method to your viewController:

并将此方法添加到您的 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
            }
        }


    }
}

You might need slight modifications in the check for change. This had worked for me though.

您可能需要在检查更改时稍作修改。不过这对我有用。

Cheers! :)

干杯! :)

回答by Josh Brown

Here's a possible solution, though it's a hack:

这是一个可能的解决方案,尽管它是一个黑客:

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

Where your -scrollTableViewmethod scrolls the table view with -scrollRectToVisible:animated:. And, of course, you could configure the delay in the code above from 0.3 to whatever seems to work for you. Yeah, it's ridiculously hacky, but it works for me on my iPhone 5 and 4S...

您的-scrollTableView方法使用-scrollRectToVisible:animated:. 而且,当然,您可以将上面代码中的延迟从 0.3 配置为似乎对您有用的任何值。是的,这太荒谬了,但它在我的 iPhone 5 和 4S 上对我有用......

回答by Joost

I had something similar I believe. I added a BOOL as instance variable which tells me if the offset has been restored and check that in -viewWillAppear:. When it has not been restored, I restore it in that method and set the BOOL to indicate that I did recover the offset.

我有类似的东西我相信。我添加了一个 BOOL 作为实例变量,它告诉我偏移量是否已恢复并在-viewWillAppear:. 当它尚未恢复时,我用该方法恢复它并设置 BOOL 以指示我确实恢复了偏移量。

It's kind of a hack and it probably can be done better, but this works for me at the moment.

这是一种黑客攻击,它可能可以做得更好,但目前这对我有用。

回答by Walt Sellers

It sounds like you want to update cell content, but without the sudden jumps that can accompany cell insertions and deletions.

听起来您想要更新单元格内容,但没有伴随单元格插入和删除的突然跳跃。

There are several articles on doing that. This is one.

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

I suggest using setContentOffset:animated: instead of scrollRectToVisible:animated: for pixel-perfect settings of a scroll view.

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