Я хочу изменить смещение таблицы по завершении загрузки, и это смещение зависит от количества ячеек, загруженных в таблицу.
Есть ли в SDK знать, когда загрузка uitableview завершена? Я ничего не вижу ни в протоколах делегатов, ни в протоколах источников данных.
Я не могу использовать подсчет источников данных из-за загрузки только видимых ячеек.
ios
iphone
uitableview
emenegro
источник
источник
Ответы:
Улучшить ответ @RichX:
lastRow
может быть и то,[tableView numberOfRowsInSection: 0] - 1
и другое((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row
. Итак, код будет:-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { if([indexPath row] == ((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row){ //end of loading //for example [activityIndicator stopAnimating]; } }
ОБНОВЛЕНИЕ: Что ж, комментарий @htafoya правильный. Если вы хотите, чтобы этот код обнаруживал конец загрузки всех данных из источника, этого не произойдет, но это не исходный вопрос. Этот код предназначен для определения того, когда отображаются все ячейки, которые должны быть видимыми.
willDisplayCell:
используется здесь для более плавного пользовательского интерфейса (одна ячейка обычно быстро отображается послеwillDisplay:
вызова). Вы также можете попробоватьtableView:didEndDisplayingCell:
.источник
viewForFooterInSection
методе.Swift 3, 4 и 5 версии:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { if let lastVisibleIndexPath = tableView.indexPathsForVisibleRows?.last { if indexPath == lastVisibleIndexPath { // do here... } } }
источник
Я всегда использую это очень простое решение:
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { if([indexPath row] == lastRow){ //end of loading //for example [activityIndicator stopAnimating]; } }
источник
[tableView numberOfRowsInSection: 0] - 1
. Вы должны заменить0
на необходимое значение. Но проблема не в этом. Проблема в том, что UITableView загружается только видимым. Однако((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject]).row
решает проблему.Вот еще один вариант, который, кажется, мне подходит. В методе делегата viewForFooter проверьте, является ли это последним разделом, и добавьте туда свой код. Этот подход пришел в голову после осознания того, что willDisplayCell не учитывает нижние колонтитулы, если они у вас есть.
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { // Perform some final layout updates if (section == ([tableView numberOfSections] - 1)) { [self tableViewWillFinishLoading:tableView]; } // Return nil, or whatever view you were going to return for the footer return nil; } - (CGFloat)tableView:(UITableView *)tableView heightForFooterInSection:(NSInteger)section { // Return 0, or the height for your footer view return 0.0; } - (void)tableViewWillFinishLoading:(UITableView *)tableView { NSLog(@"finished loading"); }
Я считаю, что этот подход работает лучше всего, если вы хотите найти конечную нагрузку для всей
UITableView
, а не только для видимых ячеек. В зависимости от ваших потребностей вам могут понадобиться только видимые ячейки, и в этом случае ответ folex - хороший маршрут.источник
nil
изtableView:viewForFooterInSection:
испорченного макета в iOS 7 с помощью Auto Layout.return CGRectMake(0,0,0,0);
self.tableView.sectionFooterHeight = 0
. В любом случае кажется, что вставляется представление нижнего колонтитула раздела высотой около 10. Бьюсь об заклад, я мог бы исправить это, добавив ограничение высоты 0 к возвращаемому представлению. В любом случае, у меня все хорошо, потому что я действительно хотел выяснить, как запустить UITableView в последней ячейке, но увидел это первым.Решение Swift 2:
// willDisplay function override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { let lastRowIndex = tableView.numberOfRowsInSection(0) if indexPath.row == lastRowIndex - 1 { fetchNewDataFromServer() } } // data fetcher function func fetchNewDataFromServer() { if(!loading && !allDataFetched) { // call beginUpdates before multiple rows insert operation tableView.beginUpdates() // for loop // insertRowsAtIndexPaths tableView.endUpdates() } }
источник
Использование частного API:
@objc func tableViewDidFinishReload(_ tableView: UITableView) { print(#function) cellsAreLoaded = true }
Использование публичного API:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // cancel the perform request if there is another section [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tableViewDidLoadRows:) object:tableView]; // create a perform request to call the didLoadRows method on the next event loop. [self performSelector:@selector(tableViewDidLoadRows:) withObject:tableView afterDelay:0]; return [self.myDataSource numberOfRowsInSection:section]; } // called after the rows in the last section is loaded -(void)tableViewDidLoadRows:(UITableView*)tableView{ self.cellsAreLoaded = YES; }
Возможный лучший вариант - добавить видимые ячейки в набор, а затем, когда вам нужно проверить, загружена ли таблица, вы можете вместо этого выполнить цикл for вокруг этого набора, например
var visibleCells = Set<UITableViewCell>() override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { visibleCells.insert(cell) } override func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) { visibleCells.remove(cell) } // example property you want to show on a cell that you only want to update the cell after the table is loaded. cellForRow also calls configure too for the initial state. var count = 5 { didSet { for cell in visibleCells { configureCell(cell) } } }
источник
Для выбранной версии ответа в Swift 3:
var isLoadingTableView = true func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { if tableData.count > 0 && isLoadingTableView { if let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows, let lastIndexPath = indexPathsForVisibleRows.last, lastIndexPath.row == indexPath.row { isLoadingTableView = false //do something after table is done loading } } }
Мне нужна была переменная isLoadingTableView, потому что я хотел убедиться, что таблица загружена, прежде чем я выберу ячейку по умолчанию. Если вы не включите это, то каждый раз, когда вы прокручиваете таблицу, он снова будет вызывать ваш код.
источник
Лучший подход, который я знаю, - это ответ Эрика по адресу: Получать уведомление, когда UITableView закончил запрашивать данные?
Обновление: чтобы заставить его работать, мне нужно поместить эти звонки в
-tableView:cellForRowAtIndexPath:
[tableView beginUpdates]; [tableView endUpdates];
источник
Чтобы узнать, когда представление таблицы завершает загрузку своего содержимого, нам сначала нужно иметь общее представление о том, как представления выводятся на экран.
В жизненном цикле приложения есть 4 ключевых момента:
2 и 3 раза полностью разделены. Зачем ? По соображениям производительности мы не хотим выполнять все вычисления момента 3 каждый раз, когда выполняется модификация.
Итак, я думаю, вы столкнулись с таким случаем:
tableView.reloadData() tableView.visibleCells.count // wrong count oO
Что здесь не так?
Как и любое представление, представление таблицы лениво перезагружает свое содержимое. На самом деле, если вы вызовете
reloadData
несколько раз, это не вызовет проблем с производительностью. Представление таблицы только пересчитывает размер своего содержимого на основе реализации делегата и ожидает момента 3 для загрузки своих ячеек. Это время называется проходом по макету.Хорошо, как попасть на проход верстки?
Во время этапа макета приложение вычисляет все кадры иерархии представления. Чтобы принять участие, вы можете переопределить выделенные методы
layoutSubviews
иupdateLayoutConstraints
т. Д. В aUIView
и эквивалентные методы в подклассе контроллера представления.Это именно то, что делает табличное представление. Он переопределяет
layoutSubviews
и в зависимости от реализации вашего делегата добавляет или удаляет ячейки. Он вызываетсяcellForRow
прямо перед добавлением и размещением новой ячейкиwillDisplay
сразу после. Если вы вызвалиreloadData
или просто добавили представление таблицы в иерархию, представление таблицы добавляет столько ячеек, сколько необходимо, чтобы заполнить его фрейм в этот ключевой момент.Хорошо, но теперь, как узнать, когда представление таблиц завершило перезагрузку своего содержимого?
Теперь мы можем перефразировать этот вопрос: как узнать, когда табличное представление закончило выкладывать свои подвиды?
• Самый простой способ - попасть в макет представления таблицы:
class MyTableView: UITableView { func layoutSubviews() { super.layoutSubviews() // the displayed cells are loaded } }
Обратите внимание, что этот метод вызывается много раз в жизненном цикле табличного представления. Из-за прокрутки и поведения вывода из очереди в табличном представлении ячейки часто изменяются, удаляются и добавляются. Но он работает сразу после
super.layoutSubviews()
загрузки ячеек. Это решение эквивалентно ожиданиюwillDisplay
события последнего пути индекса. Это событие вызывается во время выполненияlayoutSubviews
табличного представления для каждой добавленной ячейки.• Другой способ - получить вызов, когда приложение завершит этап макета.
Как описано в документации , вы можете использовать один из следующих вариантов
UIView.animate(withDuration:completion)
:tableView.reloadData() UIView.animate(withDuration: 0) { // layout done }
Это решение работает, но экран обновится один раз между временем создания макета и моментом вызова блока. Это эквивалентно
DispatchMain.async
решению, но указано.• В качестве альтернативы я бы предпочел принудительно настроить макет таблицы.
Существует специальный метод, позволяющий заставить любое представление немедленно вычислять его кадры подпредставления
layoutIfNeeded
:tableView.reloadData() table.layoutIfNeeded() // layout done
Однако будьте осторожны, это устранит ленивую загрузку, используемую системой. Повторный вызов этих методов может вызвать проблемы с производительностью. Убедитесь, что они не будут вызваны до того, как будет вычислен фрейм табличного представления, иначе табличное представление будет загружено снова, и вы не получите уведомления.
Думаю, идеального решения не существует. Создание подклассов могло привести к неприятностям. Проход макета начинается сверху и идет вниз, поэтому получить уведомление о завершении макета непросто. И
layoutIfNeeded()
может создать проблемы с производительностью и т. Д. Но зная эти варианты, вы должны уметь думать об одной альтернативе, которая будет соответствовать вашим потребностям.источник
Вот как это делается в Swift 3:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.row == 0 { // perform your logic here, for the first row in the table } // .... }
источник
вот как я это делаю в Swift 3
let threshold: CGFloat = 76.0 // threshold from bottom of tableView internal func scrollViewDidScroll(_ scrollView: UIScrollView) { let contentOffset = scrollView.contentOffset.y let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height; if (!isLoadingMore) && (maximumOffset - contentOffset <= threshold) { self.loadVideosList() } }
источник
Вот что бы я сделал.
В вашем базовом классе (может быть rootVC BaseVc и т. Д.)
A. Напишите протокол для отправки обратного вызова DidFinishReloading.
@protocol ReloadComplition <NSObject> @required - (void)didEndReloading:(UITableView *)tableView; @end
B. Напишите общий метод для перезагрузки табличного представления.
-(void)reloadTableView:(UITableView *)tableView withOwner:(UIViewController *)aViewController;
В реализации метода базового класса вызовите reloadData, за которым следует delegateMethod с задержкой.
-(void)reloadTableView:(UITableView *)tableView withOwner:(UIViewController *)aViewController{ [[NSOperationQueue mainQueue] addOperationWithBlock:^{ [tableView reloadData]; if(aViewController && [aViewController respondsToSelector:@selector(didEndReloading:)]){ [aViewController performSelector:@selector(didEndReloading:) withObject:tableView afterDelay:0]; } }]; }
Подтвердите протокол завершения перезагрузки во всех контроллерах представления, где вам нужен обратный вызов.
-(void)didEndReloading:(UITableView *)tableView{ //do your stuff. }
Ссылка: https://discussions.apple.com/thread/2598339?start=0&tstart=0
источник
@folex ответ правильный.
Но это не удастся, если в tableView одновременно отображается более одного раздела .
-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { if([indexPath isEqual:((NSIndexPath*)[[tableView indexPathsForVisibleRows] lastObject])]){ //end of loading } }
источник
В Swift вы можете сделать что-то подобное. Следующее условие будет истинным каждый раз, когда вы достигнете конца tableView
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) { if indexPath.row+1 == postArray.count { println("came to last row") } }
источник
Я знаю, что на это дан ответ, я просто добавляю рекомендацию.
Согласно следующей документации
https://www.objc.io/issues/2-concurrency/thread-safe-class-design/
Исправлять проблемы с синхронизацией с помощью dispatch_async - плохая идея. Я предлагаю, чтобы мы справились с этим, добавив ФЛАГ или что-то в этом роде.
источник
Если у вас несколько разделов, вот как получить последнюю строку в последнем разделе (Swift 3):
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { if let visibleRows = tableView.indexPathsForVisibleRows, let lastRow = visibleRows.last?.row, let lastSection = visibleRows.map({$0.section}).last { if indexPath.row == lastRow && indexPath.section == lastSection { // Finished loading visible rows } } }
источник
Совершенно случайно я наткнулся на это решение:
tableView.tableFooterView = UIView() tableViewHeight.constant = tableView.contentSize.height
Вам необходимо установить footerView до получения contentSize, например, в viewDidLoad. Кстати. установка footeView позволяет удалять "неиспользуемые" разделители
источник
Вы ищете общее количество элементов, которые будут отображаться в таблице, или общее количество элементов, видимых в данный момент? В любом случае ... Я считаю, что метод viewDidLoad выполняется после вызова всех методов источника данных. Однако это будет работать только при первой загрузке данных (если вы используете один ViewController выделения).
источник
Я копирую код Эндрю и расширяю его, чтобы учесть случай, когда у вас всего одна строка в таблице. У меня пока работает!
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { // detect when all visible cells have been loaded and displayed // NOTE: iOS7 workaround used - see: http://stackoverflow.com/questions/4163579/how-to-detect-the-end-of-loading-of-uitableview?lq=1 NSArray *visibleRows = [tableView indexPathsForVisibleRows]; NSIndexPath *lastVisibleCellIndexPath = [visibleRows lastObject]; BOOL isPreviousCallForPreviousCell = self.previousDisplayedIndexPath.row + 1 == lastVisibleCellIndexPath.row; BOOL isLastCell = [indexPath isEqual:lastVisibleCellIndexPath]; BOOL isFinishedLoadingTableView = isLastCell && ([tableView numberOfRowsInSection:0] == 1 || isPreviousCallForPreviousCell); self.previousDisplayedIndexPath = indexPath; if (isFinishedLoadingTableView) { [self hideSpinner]; } }
ПРИМЕЧАНИЕ. Я использую только 1 раздел из кода Эндрю, так что имейте это в виду ..
источник
В iOS7.0x решение немного иное. Вот что я придумал.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { BOOL isFinishedLoadingTableView = [self isFinishedLoadingTableView:tableView indexPath:indexPath]; if (isFinishedLoadingTableView) { NSLog(@"end loading"); } } - (BOOL)isFinishedLoadingTableView:(UITableView *)tableView indexPath:(NSIndexPath *)indexPath { // The reason we cannot just look for the last row is because // in iOS7.0x the last row is updated before // looping through all the visible rows in ascending order // including the last row again. Strange but true. NSArray * visibleRows = [tableView indexPathsForVisibleRows]; // did verify sorted ascending via logging NSIndexPath *lastVisibleCellIndexPath = [visibleRows lastObject]; // For tableviews with multiple sections this will be more complicated. BOOL isPreviousCallForPreviousCell = self.previousDisplayedIndexPath.row + 1 == lastVisibleCellIndexPath.row; BOOL isLastCell = [indexPath isEqual:lastVisibleCellIndexPath]; BOOL isFinishedLoadingTableView = isLastCell && isPreviousCallForPreviousCell; self.previousDisplayedIndexPath = indexPath; return isFinishedLoadingTableView; }
источник
Цель C
[self.tableView reloadData]; [self.tableView performBatchUpdates:^{} completion:^(BOOL finished) { /// table-view finished reload }];
Swift
self.tableView?.reloadData() self.tableView?.performBatchUpdates({ () -> Void in }, completion: { (Bool finished) -> Void in /// table-view finished reload })
источник
UICollectionView
насколько я понимаю.