Получать уведомление, когда UITableView закончил запрашивать данные?
Есть ли способ узнать, когда UITableView
закончил запрашивать данные из своего источника данных?
Ни один из методов viewDidLoad
/viewWillAppear
/viewDidAppear
связанного контроллера представления (UITableViewController
) здесь не используется, поскольку все они запускаются слишком рано. Ни один из них (вполне понятно) не гарантирует, что запросы к источнику данных закончатся (например, пока прокрутка будет прокручена).
Обходной путь, который я нашел, заключается в вызове reloadData
в viewDidAppear
, поскольку, когда reloadData
возвращает, представление таблицы, как гарантируется, завершает запрос источника данных столько, сколько ему нужно.
Однако это кажется довольно неприятным, так как я полагаю, что он дважды запрашивает одну и ту же информацию о источнике данных (один раз автоматически и один раз из-за вызова reloadData
) при первом загрузке.
Причина, по которой я хочу сделать это, заключается в том, что я хочу сохранить положение прокрутки UITableView
, но вплоть до уровня пикселя, а не только до ближайшей строки.
При восстановлении позиции прокрутки (используя scrollRectToVisible:animated:
) мне нужно, чтобы в представлении таблицы уже было достаточно данных, иначе вызов метода scrollRectToVisible:animated:
ничего не делает (что происходит, если вы поместите вызов на свой принадлежит в любом из viewDidLoad
, viewWillAppear
или viewDidAppear
).
Ответы
Ответ 1
Этот ответ, похоже, больше не работает, из-за некоторых изменений, внесенных в реализацию UITableView с момента написания ответа. См. Этот комментарий: Получать уведомление, когда UITableView закончил запрашивать данные?
Я играю с этой проблемой в течение нескольких дней и думаю, что подкласс UITableView
reloadData
- лучший подход:
- (void)reloadData {
NSLog(@"BEGIN reloadData");
[super reloadData];
NSLog(@"END reloadData");
}
reloadData
не заканчивается до того, как таблица завершит перезагрузку своих данных. Итак, когда второй NSLog
запущен, представление таблицы фактически завершает запрос данных.
Я подклассифицировал UITableView
, чтобы отправить методы делегату до и после reloadData
. Он работает как шарм.
Ответ 2
У меня был такой же сценарий в моем приложении, и я подумал, что вы ответите мне, ребята, потому что другие ответы, упомянутые здесь, не работают для меня для 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)
})
Итак, как это работает.
В основном, когда вы выполняете перезагрузку, основной поток становится занятым, поэтому в то время, когда мы выполняем асинхронный поток отправки, блок будет ждать завершения основного потока. Поэтому, как только таблица будет полностью загружена, основной поток будет завершен, и поэтому он отправит наш блок методов
Протестировано в iOS7 и iOS8, и он работает потрясающе;)
Обновление для iOS9:. Это просто отлично работает с iOS9. Я создал образец проекта в github как POC.
https://github.com/ipraba/TableReloadingNotifier
Я прилагаю скриншот моего теста здесь.
Протестированная среда: iOS9 iPhone6 симулятор от Xcode7
![введите описание изображения здесь]()
Ответ 3
EDIT: этот ответ на самом деле не является решением. Вероятно, он работает сначала, потому что перезагрузка может произойти довольно быстро, но на самом деле блок завершения не обязательно вызывается после того, как данные полностью завершили перезагрузку, потому что 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
}];
Ответ 4
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
}
Ответ 5
Это мое решение. 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
Это похоже на решение Джоша Брауна за одним исключением. В методе 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];
Enjoy.:)
Ответ 6
Это ответ на несколько иной вопрос: мне нужно было знать, когда 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
, у которого уже есть свойство delegate, указывающее на MyTableViewController
, нет необходимости добавлять еще один. "@dynamic delegate" сообщает компилятору использовать это свойство. (Здесь ссылка, описывающая это: http://farhadnoorzay.com/2012/01/20/objective-c-how-to-add-delegate-methods-in-a-subclass/)
Свойство UITableView
в MyTableViewController
должно быть изменено для использования нового класса SDTableView
. Это делается в Индексе идентификации идентификатора интерфейса. Выберите UITableView
внутри UITableViewController
и установите его "Пользовательский класс" на SDTableView
.
Ответ 7
Я нашел нечто подобное, чтобы получить уведомление об изменении в 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
}
}
}
}
Возможно, вам понадобятся небольшие изменения в проверке изменений. Это работало для меня, хотя.
Ура!:)
Ответ 8
Вот возможное решение, хотя это взломать:
[self.tableView reloadData];
[self performSelector:@selector(scrollTableView) withObject:nil afterDelay:0.3];
Где ваш метод -scrollTableView
прокручивает представление таблицы с помощью -scrollRectToVisible:animated:
. И, конечно же, вы можете настроить задержку в коде выше от 0,3 до того, что, похоже, сработает для вас. Да, это смешно взломано, но это работает для меня на iPhone 5 и 4S...
Ответ 9
У меня было что-то похожее, я верю. Я добавил переменную экземпляра BOOL, которая сообщает мне, восстановлено ли смещение и проверьте, что в -viewWillAppear:
. Когда он не был восстановлен, я восстанавливаю его в этом методе и устанавливаю BOOL, чтобы указать, что я восстановил смещение.
Это своего рода взлом, и, вероятно, это может быть сделано лучше, но это работает для меня в данный момент.
Ответ 10
Похоже, вы хотите обновить содержимое ячейки, но без внезапных переходов, которые могут сопровождать вставки и удаления ячеек.
Есть несколько статей об этом. Это один.
Я предлагаю использовать setContentOffset: анимированный: вместо scrollRectToVisible: анимированный: для идеальных для пикселя настроек прокрутки.
Ответ 11
Вы можете попробовать следующую логику:
-(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, и эта логика выглядит нормально. Вы можете настроить/улучшить по мере необходимости.
Ответ 12
Разве не UITableView
layoutSubviews
вызывается непосредственно перед отображением его в виде таблицы? Я заметил, что он вызывается после того, как представление таблицы завершило загрузку своих данных, возможно, вам следует исследовать в этом направлении.
Ответ 13
Наконец, я сделал свой код с этим -
[tableView scrollToRowAtIndexPath:scrollToIndex atScrollPosition:UITableViewScrollPositionTop animated:YES];
было немного вещей, о которых нужно было позаботиться -
- назовите его "
- (UITableViewCell *)MyTableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
"
- просто убедитесь, что сообщение "scrollToRowAtIndexPath" отправлено в соответствующий экземпляр UITableView, который в данном случае определенно является MyTableview.
- В моем случае UIView - это представление, которое содержит экземпляр UITableView
- Кроме того, это будет вызвано для каждой загрузки ячеек. Поэтому, выведите логику внутри "cellForRowAtIndexPath", чтобы избежать вызова "scrollToRowAtIndexPath" более одного раза.
Ответ 14
Вы можете изменить размер табличного представления или задать его размер содержимого в этом методе, когда все загруженные данные:
- (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);
}
Ответ 15
Я просто запускаю повторный запланированный таймер и аннулирую его, только когда table contentSize больше при высоте tableHeaderView (означает, что в таблице есть содержимое строк). Код в С# (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;
}
}));
}
}
Ответ 16
Начиная с iOS 6, метод делегата UITableview
, называемый:
-(void)tableView:(UITableView *)tableView willDisplayHeaderView:(UIView *)view forSection:(NSInteger)section
будет выполняться после успешной перезагрузки вашей таблицы. Вы можете выполнить настройку, как требуется в этом методе.
Ответ 17
Лучшее решение, которое я нашел в Swift
extension UITableView {
func reloadData(completion: ()->()) {
self.reloadData()
dispatch_async(dispatch_get_main_queue()) {
completion()
}
}
}
Ответ 18
Почему просто не растягивается?
@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];
}
}];
Не проверено с большим количеством данных