Как сказать, когда UITableView завершил ReloadData?
Я пытаюсь прокрутить в нижней части UITableView после того, как он будет выполнен выполнение [self.tableView reloadData]
у меня изначально было
[self.tableView reloadData]
NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
но потом я прочитал, что reloadData является асинхронным, поэтому прокрутка не происходит с self.tableView,[self.tableView numberOfSections] и [self.tableView numberOfRowsinSection все 0.
спасибо!
что странно, что я использую:
[self.tableView reloadData];
NSLog(@"Number of Sections %d", [self.tableView numberOfSections]);
NSLog(@"Number of Rows %d", [self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1);
в консоли он возвращает Sections = 1, Row = -1;
когда я делаю точно такие же NSLogs в cellForRowAtIndexPath Я получаю разделы = 1 и строка = 8; (8 правильно)
13 ответов:
перезагрузка происходит во время следующего прохода макета, что обычно происходит, когда вы возвращаете управление в цикл запуска (после, скажем, действия кнопки или любого другого возврата).
таким образом, один из способов запустить что-то после перезагрузки табличного представления-просто заставить табличное представление немедленно выполнить макет:
[self.tableView reloadData]; [self.tableView layoutIfNeeded]; NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)]; [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];другой способ-запланировать ваш код после макета для запуска позже с помощью
dispatch_async:[self.tableView reloadData]; dispatch_async(dispatch_get_main_queue(), ^{ NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection:([self.tableView numberOfSections]-1)]; [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES]; });обновление
при дальнейшем расследование, я считаю, что представление таблицы отправляет
tableView:numberOfSections:иtableView:numberOfRowsInSection:к своему источнику данных перед возвращением изreloadData. Если делегат реализуетtableView:heightForRowAtIndexPath:, представление таблицы также отправляет это (для каждой строки) перед возвращением изreloadData.однако табличное представление не отправляет
tableView:cellForRowAtIndexPath:илиtableView:headerViewForSectionдо этапа компоновки, который происходит по умолчанию, когда вы возвращаете управление в цикл выполнения.я также нахожу, что в крошечной тестовой программе, Код в вашем вопросе правильно прокручивается в нижней части таблицы,без я делаю что-то особенное (например, отправка
layoutIfNeededили черезdispatch_async).
Swift:
extension UITableView { func reloadData(completion: ()->()) { UIView.animateWithDuration(0, animations: { self.reloadData() }) { _ in completion() } } } ...somewhere later... tableView.reloadData { println("done") }Цель-C:
[UIView animateWithDuration:0 animations:^{ [myTableView reloadData]; } completion:^(BOOL finished) { //Do something after that... }];
The
dispatch_async(dispatch_get_main_queue())способ выше не гарантируется работа. Я вижу недетерминированное поведение с ним, в котором иногда система завершила layoutSubviews и рендеринг ячейки до блока завершения, а иногда и после.вот решение, которое работает на 100% для меня, на iOS 10. Для этого требуется возможность создания экземпляра UITableView или UICollectionView в качестве пользовательского подкласса. Вот решение UICollectionView, но это точно то же самое для UITableView:
CustomCollectionView.h:
#import <UIKit/UIKit.h> @interface CustomCollectionView: UICollectionView - (void)reloadDataWithCompletion:(void (^)(void))completionBlock; @endCustomCollectionView.м:
#import "CustomCollectionView.h" @interface CustomCollectionView () @property (nonatomic, copy) void (^reloadDataCompletionBlock)(void); @end @implementation CustomCollectionView - (void)reloadDataWithCompletion:(void (^)(void))completionBlock { self.reloadDataCompletionBlock = completionBlock; [super reloadData]; } - (void)layoutSubviews { [super layoutSubviews]; if (self.reloadDataCompletionBlock) { self.reloadDataCompletionBlock(); self.reloadDataCompletionBlock = nil; } } @endпример использования:
[self.collectionView reloadDataWithCompletion:^{ // reloadData is guaranteed to have completed }];посмотреть здесь для быстрой версии этого ответа
начиная с Xcode 8.2.1, iOS 10 и swift 3,
Вы можете определить конец
tableView.reloadData()легко с помощью блока CATransaction:CATransaction.begin() CATransaction.setCompletionBlock({ print("reload completed") //Your completion code here )} print("reloading") tableView.reloadData() CATransaction.commit()выше также работает для определения конца UICollectionView reloadData () и uipickerview reloadAllComponents ().
у меня были те же проблемы, что и у Тайлера Шиффера.
я реализовал решение в Swift и это решило мои проблемы.
Swift 3.0:
final class UITableViewWithReloadCompletion: UITableView { private var reloadDataCompletionBlock: (() -> Void)? override func layoutSubviews() { super.layoutSubviews() reloadDataCompletionBlock?() reloadDataCompletionBlock = nil } func reloadDataWithCompletion(completion: @escaping () -> Void) { reloadDataCompletionBlock = completion super.reloadData() } }Swift 2:
class UITableViewWithReloadCompletion: UITableView { var reloadDataCompletionBlock: (() -> Void)? override func layoutSubviews() { super.layoutSubviews() self.reloadDataCompletionBlock?() self.reloadDataCompletionBlock = nil } func reloadDataWithCompletion(completion:() -> Void) { reloadDataCompletionBlock = completion super.reloadData() } }Пример Использования:
tableView.reloadDataWithCompletion() { // reloadData is guaranteed to have completed }
похоже, люди все еще читают этот вопрос и ответы. B / c этого, я редактирую свой ответ, чтобы удалить слово синхронно который действительно не имеет отношения к этому.
When [tableView reloadData]возвращает, внутренние структуры данных за tableView были обновлены. Поэтому, когда метод завершается, вы можете безопасно прокрутить вниз. Я проверил это в своем собственном приложении. Широко распространенный ответ @rob-mayoff, хотя и запутанный в терминологии, признает то же самое в его последнем обновлении.если
tableViewне прокручивается вниз у вас может быть проблема в другом коде, который вы не опубликовали. Возможно, вы меняете данные после завершения прокрутки, и вы не перезагружаете и/или не прокручиваете вниз?добавьте некоторые записи в журнал следующим образом, чтобы убедиться, что данные таблицы верны после
reloadData. У меня есть следующий код в приложение, и она отлично работает.// change the data source NSLog(@"Before reload / sections = %d, last row = %d", [self.tableView numberOfSections], [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]); [self.tableView reloadData]; NSLog(@"After reload / sections = %d, last row = %d", [self.tableView numberOfSections], [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]); [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]-1 inSection:[self.tableView numberOfSections] - 1] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
и
UICollectionViewверсия, основанная на ответе kolaworld:https://stackoverflow.com/a/43162226/1452758
нуждается в тестировании. Работает до сих пор на iOS 9.2, Xcode 9.2 beta 2, с прокруткой collectionView до индекса, как закрытие.
extension UICollectionView { /// Calls reloadsData() on self, and ensures that the given closure is /// called after reloadData() has been completed. /// /// Discussion: reloadData() appears to be asynchronous. i.e. the /// reloading actually happens during the next layout pass. So, doing /// things like scrolling the collectionView immediately after a /// call to reloadData() can cause trouble. /// /// This method uses CATransaction to schedule the closure. func reloadDataThenPerform(_ closure: @escaping (() -> Void)) { CATransaction.begin() CATransaction.setCompletionBlock(closure) self.reloadData() CATransaction.commit() } }использование:
myCollectionView.reloadDataThenPerform { myCollectionView.scrollToItem(at: indexPath, at: .centeredVertically, animated: true) }
Я использую этот трюк, довольно уверен, что я уже отправил его в дубликат этого вопроса:
-(void)tableViewDidLoadRows:(UITableView *)tableView{ // do something after loading, e.g. select a cell. } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // trick to detect when table view has finished loading. [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tableViewDidLoadRows:) object:tableView]; [self performSelector:@selector(tableViewDidLoadRows:) withObject:tableView afterDelay:0]; // specific to your controller return self.objects.count; }
на самом деле это решило мою проблему:
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { NSSet *visibleSections = [NSSet setWithArray:[[tableView indexPathsForVisibleRows] valueForKey:@"section"]]; if (visibleSections) { // hide the activityIndicator/Loader }}
попробуйте этот способ он будет работать
[tblViewTerms performSelectorOnMainThread:@selector(dataLoadDoneWithLastTermIndex:) withObject:lastTermIndex waitUntilDone:YES];waitUntilDone:YES]; @interface UITableView (TableViewCompletion) -(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex; @end @implementation UITableView(TableViewCompletion) -(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex { NSLog(@"dataLoadDone"); NSIndexPath* indexPath = [NSIndexPath indexPathForRow: [lastTermIndex integerValue] inSection: 0]; [self selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone]; } @endЯ выполню, когда таблица будет полностью загружена
другое решение, вы можете подкласс UITableView
в итоге я использовал вариант решения Шона:
создайте пользовательский класс UITableView с делегатом:
protocol CustomTableViewDelegate { func CustomTableViewDidLayoutSubviews() } class CustomTableView: UITableView { var customDelegate: CustomTableViewDelegate? override func layoutSubviews() { super.layoutSubviews() self.customDelegate?.CustomTableViewDidLayoutSubviews() } }тогда в моем коде, я использую
class SomeClass: UIViewController, CustomTableViewDelegate { @IBOutlet weak var myTableView: CustomTableView! override func viewDidLoad() { super.viewDidLoad() self.myTableView.customDelegate = self } func CustomTableViewDidLayoutSubviews() { print("didlayoutsubviews") // DO other cool things here!! } }также убедитесь, что вы установили представление таблицы в CustomTableView в построителе интерфейса:
вы можете использовать его для сделать что-то после перезагрузки данных:
[UIView animateWithDuration:0 animations:^{ [self.contentTableView reloadData]; } completion:^(BOOL finished) { _isUnderwritingUpdate = NO; }];
попробуйте установить задержки:
[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2]; [_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];

Comments