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

ken*_*000 108 iphone uitableview ios

有没有办法找出什么时候UITableView从数据源请求数据?

相关视图控制器()的viewDidLoad/ viewWillAppear/ viewDidAppear方法UITableViewController都没有在这里使用,因为它们都过早发生.它们(完全可以理解)都不保证对数据源的查询暂时完成(例如,直到滚动视图).

一个解决办法,我发现是调用reloadDataviewDidAppear,因为当reloadData返回时,表视图保证完成尽可能多的,因为它需要暂时查询的数据源.

但是,这看起来相当令人讨厌,因为我认为这是因为reloadData它首次加载时会导致数据源被要求提供相同的信息两次(一次是自动的,一次是因为调用).

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

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

Eri*_*AND 60

由于自编写答案以来对UITableView实现进行了一些更改,因此这个答案似乎不再起作用了.看到此评论:当UITableView完成询问数据时收到通知?

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

- (void)reloadData {

    NSLog(@"BEGIN reloadData");

    [super reloadData];

    NSLog(@"END reloadData");

}
Run Code Online (Sandbox Code Playgroud)

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

我已经将子UITableView方法发送给委托之前和之后reloadData.它就像一个魅力.

  • 曾几何时,reloadData在结束之前完成了加载过程.但苹果在某些时候改变了它.现在,UITableView类可以使用所有行插入和删除调用来缓存reloadData调用.如果查看UITableView的@interface声明,您会在_insertItems和_deleteItems下找到NSMutableArray成员_reloadItems.(由于这种变化,我不得不重写我继承的代码.) (5认同)
  • Eric的回答是不是没有实现(读取不覆盖)reloaddata,调用reloaddata(基本上是[super reloaddata],然后在调用它之后,在完成时做你想要的东西? (3认同)
  • 发布在主队列块完成代码调用`[超级reloadData]`对我的作品后:`dispatch_async(dispatch_get_main_queue()^ {的NSLog(@ "重装完成");});`.它基本上跳过了由`reloadData`中的表视图发布的块. (3认同)
  • 不得不说,这似乎是最好的解决方案.刚刚实现它,就像你说的那样,它就像一个魅力.谢谢! (2认同)
  • @EricMORAND你说"reloadData在表完成重新加载数据之前不会结束." 你能澄清一下你的意思吗?我发现`reloadData`立即返回,我看到"END reloadData"_before_实际重新加载了单元格(即在调用`UITableViewDataSource`方法之前).我的实验证明了与你说的完全相反.我必须误解你想说的话. (2认同)

iPr*_*abu 52

我确实在我的应用程序中有相同的场景,并且认为会将我的答案发布给你们,因为这里提到的其他答案对我来说对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];
 });
Run Code Online (Sandbox Code Playgroud)

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)

})
Run Code Online (Sandbox Code Playgroud)

那么这是如何工作的.

基本上当你重新加载时,主线程变得很忙,所以当我们执行调度异步线程时,该块将等到主线程完成.因此,一旦完成了tableview的加载,主线程将完成,因此它将调度我们的方法块

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

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

我在这附上我测试的截图.

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

在此输入图像描述

  • 我发现最好的衣服! (8认同)

ban*_*isa 25

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

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

@interface DUTableView : UITableView

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

@end
Run Code Online (Sandbox Code Playgroud)

和...

#import "DUTableView.h"

@implementation DUTableView

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

@end
Run Code Online (Sandbox Code Playgroud)

用法:

[self.tableView reloadDataWithCompletion:^{
                                            //do your stuff here
                                        }];
Run Code Online (Sandbox Code Playgroud)

  • 这不是解决方案Completition块在cellForRowAtIndexPath之前开始执行 (12认同)
  • 我无法获得足够的积木.当你有一个好块时,谁需要一个代表! (2认同)
  • 这对我有用,只需稍作改动。不是直接调用完成块,而是在发布在主队列上的块中调用它:`dispatch_async(dispatch_get_main_queue(), ^{completionBlock();});`。这基本上跳过了`reloadData`中表格视图发布的块。 (2认同)

小智 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
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*ski 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
Run Code Online (Sandbox Code Playgroud)

它与Josh Brown的解决方案类似,只有一个例外.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];
Run Code Online (Sandbox Code Playgroud)

请享用.:)


Sym*_*ric 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;
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

用法:

MyTableViewController.h:

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

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

注意: 因为这是一个子类,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.


Abd*_*mer 6

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

试试这个:

viewDidLoad写,

[self.tableView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionPrior context:NULL];
Run Code Online (Sandbox Code Playgroud)

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


    }
}
Run Code Online (Sandbox Code Playgroud)

您可能需要在检查更改时稍作修改.这虽然对我有用.

干杯! :)

  • 优雅!我要添加的唯一内容是你需要删除自己和观察者(可能在-dealloc方法中).或者将自己添加为`-viewWillAppear`中的观察者,并在`-viewWillDisapear`方法中删除自己. (2认同)

Joo*_*ost 1

我相信我也有类似的事情。我添加了一个 BOOL 作为实例变量,它告诉我偏移量是否已恢复并在-viewWillAppear:. 当它还没有恢复时,我在该方法中恢复它并设置 BOOL 来指示我确实恢复了偏移量。

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

  • 以上所有内容都是正确的,那么问题是:我们有什么非黑客选项来找出 UITableView 何时从其数据源获取了足够的信息来*保证*滚动请求(即,scrollRectToVisible:animated :调用)实际上会起作用(而不是什么都不做)? (2认同)