Как узнать, когда UITableView завершил ReloadData?

183

Я пытаюсь прокрутить до конца 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я получаю Sections = 1 и Row = 8; (8 верно)

Алан
источник
Возможный дубликат этого вопроса: stackoverflow.com/questions/4163579/…
pmk
2
лучшее решение, которое я видел. stackoverflow.com/questions/1483581/…
Халед Аннажар
Мой ответ на следующий вопрос может вам помочь, stackoverflow.com/questions/4163579/…
Suhas Aithal
Попробуйте мой ответ здесь - stackoverflow.com/questions/4163579/…
Suhas Aithal

Ответы:

288

Перезагрузка происходит во время следующего прохода макета, что обычно происходит, когда вы возвращаете управление в цикл выполнения (например, после нажатия кнопки или любого другого возврата).

Таким образом, один из способов запустить что-то после перезагрузки табличного представления - просто заставить табличное представление немедленно выполнить компоновку:

[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).

Роб Майофф
источник
3
@rob, в зависимости от того, насколько велик ваш источник табличных данных, вы можете анимировать переход к нижней части табличного представления в том же цикле выполнения. Если вы попробуете свой тестовый код с огромной таблицей, ваш трюк с использованием GCD отложить прокрутку до следующего цикла выполнения будет работать, тогда как немедленная прокрутка не удастся. Но в любом случае, спасибо за этот трюк!
Мистер Т
7
Метод 2 не работает для меня по неизвестной причине, но вместо этого выбрал первый метод.
Радж Паван Гумдал
4
dispatch_async(dispatch_get_main_queue())метод не гарантирует работу. Я вижу недетерминированное поведение с ним, в котором иногда система завершает layoutSubviews и рендеринг ячейки до блока завершения, а иногда и после. Я выложу ответ, который работал для меня ниже.
Тайлер Шиаффер
3
Согласитесь, dispatch_async(dispatch_get_main_queue())не всегда работает. Видя случайные результаты здесь.
Войто
1
Основной поток запускает NSRunLoop. Цикл выполнения имеет разные фазы, и вы можете запланировать обратный вызов для определенной фазы (используя a CFRunLoopObserver). UIKit планирует размещение на более позднем этапе, после того, как ваш обработчик события вернется.
Роб Майофф
106

Swift:

extension UITableView {
    func reloadData(completion:@escaping ()->()) {
        UIView.animateWithDuration(0, animations: { self.reloadData() })
            { _ in completion() }
    }
}

...somewhere later...

tableView.reloadData {
    println("done")
}

Objective-C:

[UIView animateWithDuration:0 animations:^{
    [myTableView reloadData];
} completion:^(BOOL finished) {
    //Do something after that...
}];
Авиэль Гросс
источник
16
Это эквивалентно отправке чего-либо в основной поток в «ближайшем будущем». Вероятно, вы просто видите, как табличное представление отрисовывает объекты до того, как основной поток запросит блок завершения. Во-первых, не рекомендуется делать такой взлом, но в любом случае вам следует использовать dispatch_after, если вы собираетесь попытаться это сделать.
SEO
1
Решение Роба хорошо, но не работает, если в табличном представлении нет строк. Преимущество решения Aviel заключается в том, что он работает даже тогда, когда в таблице нет строк, а есть только разделы.
Chrstph SLN
@Christophe На данный момент я мог использовать обновление Роба в табличном представлении без каких-либо строк, переопределив в моем контроллере Mock view tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Intметод и вставив в свое переопределение все, что я хотел, чтобы уведомить о завершении перезагрузки.
Гоб
49

Начиная с 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()

Вышеприведенное также работает для определения конца reloadData () UICollectionView и reloadAllComponents UIPickerView.

kolaworld
источник
Also Я также работаю, если вы выполняете пользовательскую перезагрузку, например, вставку, удаление или перемещение строк вручную в представлении таблицы, внутри beginUpdatesи endUpdatesвызовах.
Даррарски
Я считаю, что это на самом деле современное решение. на самом деле, это общий шаблон в iOS, пример ... stackoverflow.com/a/47536770/294884
Толстяк
Я попробовал это. У меня очень странное поведение. Мой табличный вид правильно показывает два заголовка. Внутри setCompletionBlockмои numberOfSectionsшоу 2 ... пока все хорошо. И все же, если внутри setCompletionBlockя делаю tableView.headerView(forSection: 1)это возвращается nil!!! следовательно, я думаю, что этот блок либо происходит до перезагрузки, либо фиксирует что-то раньше, либо я делаю что-то не так. К вашему сведению, я попробовал ответить Тайлеру, и это сработало! @ Толстяк
Мед
32

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;

@end

CustomCollectionView.m:

#import "CustomCollectionView.h"

@interface CustomCollectionView ()

@property (nonatomic, copy) void (^reloadDataCompletionBlock)(void);

@end

@implementation CustomCollectionView

- (void)reloadDataWithCompletion:(void (^)(void))completionBlock
{
    self.reloadDataCompletionBlock = completionBlock;
    [self reloadData];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.reloadDataCompletionBlock) {
        self.reloadDataCompletionBlock();
        self.reloadDataCompletionBlock = nil;
    }
}

@end

Пример использования:

[self.collectionView reloadDataWithCompletion:^{
    // reloadData is guaranteed to have completed
}];

Смотрите здесь для Swift версию этого ответа

Тайлер Шиффер
источник
Это единственный правильный путь. Добавил его в свой проект, потому что для анимации мне понадобились последние кадры некоторых ячеек. Я также добавил и редактировать для Swift. Надеюсь, ты не возражаешь 😉
Джон Фогель
2
После вызова блока в layoutSubviewsнем должно быть установлено значение, так nilкак последующие вызовы layoutSubviews, не обязательно вызванные вызовом reloadData, приведут к выполнению блока, поскольку удерживается сильная ссылка, что не является желаемым поведением.
Марк Бурк
почему я не могу использовать это для UITableView? это не показывает видимый интерфейс. Я импортировал заголовочный файл также, но все тот же
Julfikar
2
Дополнение к этому ответу состоит в том, что можно обойти существующий обратный вызов, если он только один, что означает, что несколько вызывающих абонентов будут иметь состояние гонки. Решение состоит в том, чтобы reloadDataCompletionBlockсоздать массив блоков и перебрать их при выполнении, а затем очистить массив.
Тайлер
1) Разве это не эквивалентно первому ответу Роба, то есть использовать layoutIfNeeded? 2) почему вы упомянули iOS 10, он не работает на iOS 9 ?!
Мед
30

У меня были те же проблемы, что и у Тайлера Шиффера.

Я реализовал его решение в 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
    self.reloadData()
  }
}

Свифт 2:

class UITableViewWithReloadCompletion: UITableView {

  var reloadDataCompletionBlock: (() -> Void)?

  override func layoutSubviews() {
    super.layoutSubviews()

    self.reloadDataCompletionBlock?()
    self.reloadDataCompletionBlock = nil
  }

  func reloadDataWithCompletion(completion:() -> Void) {
      reloadDataCompletionBlock = completion
      self.reloadData()
  }
}

Пример использования:

tableView.reloadDataWithCompletion() {
 // reloadData is guaranteed to have completed
}
Шон Аукстак
источник
1
отлично! Небольшой придирки, вы можете удалить if let, сказав, reloadDataCompletionBlock?()что будет называть, если не ноль Ty
Тайлер
Не повезло с этим в моей ситуации на ios9
Matjan
self.reloadDataCompletionBlock? { completion() }должен былself.reloadDataCompletionBlock?()
эм
Как мне справиться с изменением размера таблицы? Ранее я вызывал tableView.beginUpdates () tableView.layoutIfNeeded () tableView.endUpdates ()
Партх Тамане
10

И 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)
}
Уомбл
источник
6

Кажется, что люди все еще читают этот вопрос и ответы. В связи с этим, я редактирую свой ответ, чтобы удалить слово Синхронный, которое на самом деле не имеет к этому отношения.

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];
XJones
источник
Я обновил свои вопросы. Вы знаете, почему мои NSLogs выводили бы так?
Алан
8
reloadDataне синхронно. Раньше было - смотрите этот ответ: stackoverflow.com/a/16071589/193896
bendytree
1
Это синхронно. Это очень легко проверить и увидеть с помощью примера приложения. Вы связались с ответом @ rob в этом вопросе. Если вы читаете его обновление внизу, он также это подтвердил. Возможно, вы говорите об изменениях визуального макета. Это правда, что tableView не обновляется визуально синхронно, но данные есть. Вот почему значения, необходимые ОП, верны сразу после reloadDataвозврата.
XJones
1
Вы можете быть озадачены тем, что ожидается в reloadData. Используйте мой тестовый сценарий в viewWillAppearaccept для scrollToRowAtIndexPath:строки b / c, которая не имеет смысла, если tableViewона не отображается. Вы увидите, что reloadDataобновили данные, кэшированные в tableViewэкземпляре, и это reloadDataсинхронно. Если вы ссылаетесь на другие tableViewметоды делегата, вызываемые при tableViewразметке, они не будут вызваны, если tableViewне отображается. Если я неправильно понимаю ваш сценарий, пожалуйста, объясните.
XJones
3
Какие веселые времена. Сейчас 2014 год, и есть аргументы в пользу того, является ли какой-то метод синхронным и асинхронным или нет. Похоже на догадки. Все детали реализации полностью непрозрачны за этим именем метода. Разве программирование не прекрасно?
фатухоку
5

Я использую этот трюк, уверен, я уже разместил его в дубликате этого вопроса:

-(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;
}
malhal
источник
@ Толстяк, неясно, если вы подразумеваете это как положительный комментарий или отрицательный комментарий. Но я видел, что вы прокомментировали другой ответ как «это, кажется, лучшее решение!» Я полагаю, что, по сути, вы не считаете это решение лучшим.
Cœur
1
Полагаясь на побочный эффект фальшивой анимации? Нет, это хорошая идея. Научитесь выполнять селектор или GCD и делайте это правильно. Кстати, теперь есть метод загрузки таблиц, который вы можете просто использовать, если не возражаете против использования частного протокола, что вполне нормально, потому что это среда, вызывающая ваш код, а не наоборот.
Малхал
3

На самом деле этот решил мою проблему:

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

NSSet *visibleSections = [NSSet setWithArray:[[tableView indexPathsForVisibleRows] valueForKey:@"section"]];
if (visibleSections) {
    // hide the activityIndicator/Loader
}}
Аша Антоний
источник
2

Попробуй так будет работать

[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

Shashi3456643
источник
1

В итоге я использовал вариант решения Шона:

Создайте пользовательский класс 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 в конструкторе интерфейса:

введите описание изображения здесь

Сэм
источник
это работает, но проблема заключается в том, что метод срабатывает каждый раз, когда выполняется загрузка одной ячейки, а НЕ ПЕРЕЗАГРУЗКА ВЕСЬ ТАБЛИЦЫ, поэтому ясно, что этот ответ не относится к заданному вопросу.
Яш Беди
Правда, он вызывается не раз, но не в каждой ячейке. Таким образом, вы можете прослушать первый делегат и игнорировать остальные, пока не вызовете reloadData снова.
Сэм
1

В Swift 3.0 + мы можем создать расширение для UITableViewс escaped Closureподобным ниже:

extension UITableView {
    func reloadData(completion: @escaping () -> ()) {
        UIView.animate(withDuration: 0, animations: { self.reloadData()})
        {_ in completion() }
    }
}

И используйте его как ниже, где вы хотите:

Your_Table_View.reloadData {
   print("reload done")
 }

надеюсь, это кому-нибудь поможет. ура!

Кальдера Чанака
источник
Гениальная идея ... только, чтобы избежать путаницы, я изменил имя функции t reload, а не reloadData (). Спасибо
Виджай Кумар AB
1

подробности

  • Версия Xcode 10.2.1 (10E1001), Swift 5

Решение

import UIKit

// MARK: - UITableView reloading functions

protocol ReloadCompletable: class { func reloadData() }

extension ReloadCompletable {
    func run(transaction closure: (() -> Void)?, completion: (() -> Void)?) {
        guard let closure = closure else { return }
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        closure()
        CATransaction.commit()
    }

    func run(transaction closure: (() -> Void)?, completion: ((Self) -> Void)?) {
        run(transaction: closure) { [weak self] in
            guard let self = self else { return }
            completion?(self)
        }
    }

    func reloadData(completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadData() }, completion: closure)
    }
}

// MARK: - UITableView reloading functions

extension ReloadCompletable where Self: UITableView {
    func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadRows(at: indexPaths, with: animation) }, completion: closure)
    }

    func reloadSections(_ sections: IndexSet, with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadSections(sections, with: animation) }, completion: closure)
    }
}

// MARK: - UICollectionView reloading functions

extension ReloadCompletable where Self: UICollectionView {

    func reloadSections(_ sections: IndexSet, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadSections(sections) }, completion: closure)
    }

    func reloadItems(at indexPaths: [IndexPath], completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadItems(at: indexPaths) }, completion: closure)
    }
}

использование

UITableView

// Activate
extension UITableView: ReloadCompletable { }

// ......
let tableView = UICollectionView()

// reload data
tableView.reloadData { tableView in print(collectionView) }

// or
tableView.reloadRows(at: indexPathsToReload, with: rowAnimation) { tableView in print(tableView) }

// or
tableView.reloadSections(IndexSet(integer: 0), with: rowAnimation) { _tableView in print(tableView) }

UICollectionView

// Activate
extension UICollectionView: ReloadCompletable { }

// ......
let collectionView = UICollectionView()

// reload data
collectionView.reloadData { collectionView in print(collectionView) }

// or
collectionView.reloadItems(at: indexPathsToReload) { collectionView in print(collectionView) }

// or
collectionView.reloadSections(IndexSet(integer: 0)) { collectionView in print(collectionView) }

Полный образец

Не забудьте добавить код решения здесь

import UIKit

class ViewController: UIViewController {

    private weak var navigationBar: UINavigationBar?
    private weak var tableView: UITableView?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupNavigationItem()
        setupTableView()
    }
}
// MARK: - Activate UITableView reloadData with completion functions

extension UITableView: ReloadCompletable { }

// MARK: - Setup(init) subviews

extension ViewController {

    private func setupTableView() {
        guard let navigationBar = navigationBar else { return }
        let tableView = UITableView()
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true
        tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        tableView.dataSource = self
        self.tableView = tableView
    }

    private func setupNavigationItem() {
        let navigationBar = UINavigationBar()
        view.addSubview(navigationBar)
        self.navigationBar = navigationBar
        navigationBar.translatesAutoresizingMaskIntoConstraints = false
        navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        navigationBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        let navigationItem = UINavigationItem()
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "all", style: .plain, target: self, action: #selector(reloadAllCellsButtonTouchedUpInside(source:)))
        let buttons: [UIBarButtonItem] = [
                                            .init(title: "row", style: .plain, target: self,
                                                  action: #selector(reloadRowButtonTouchedUpInside(source:))),
                                            .init(title: "section", style: .plain, target: self,
                                                  action: #selector(reloadSectionButtonTouchedUpInside(source:)))
                                            ]
        navigationItem.leftBarButtonItems = buttons
        navigationBar.items = [navigationItem]
    }
}

// MARK: - Buttons actions

extension ViewController {

    @objc func reloadAllCellsButtonTouchedUpInside(source: UIBarButtonItem) {
        let elementsName = "Data"
        print("-- Reloading \(elementsName) started")
        tableView?.reloadData { taleView in
            print("-- Reloading \(elementsName) stopped \(taleView)")
        }
    }

    private var randomRowAnimation: UITableView.RowAnimation {
        return UITableView.RowAnimation(rawValue: (0...6).randomElement() ?? 0) ?? UITableView.RowAnimation.automatic
    }

    @objc func reloadRowButtonTouchedUpInside(source: UIBarButtonItem) {
        guard let tableView = tableView else { return }
        let elementsName = "Rows"
        print("-- Reloading \(elementsName) started")
        let indexPathToReload = tableView.indexPathsForVisibleRows?.randomElement() ?? IndexPath(row: 0, section: 0)
        tableView.reloadRows(at: [indexPathToReload], with: randomRowAnimation) { _tableView in
            //print("-- \(taleView)")
            print("-- Reloading \(elementsName) stopped in \(_tableView)")
        }
    }

    @objc func reloadSectionButtonTouchedUpInside(source: UIBarButtonItem) {
        guard let tableView = tableView else { return }
        let elementsName = "Sections"
        print("-- Reloading \(elementsName) started")
        tableView.reloadSections(IndexSet(integer: 0), with: randomRowAnimation) { _tableView in
            //print("-- \(taleView)")
            print("-- Reloading \(elementsName) stopped in \(_tableView)")
        }
    }
}

extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int { return 1 }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = "\(Date())"
        return cell
    }
}

Полученные результаты

введите описание изображения здесь

Василий Боднарчук
источник
0

Просто чтобы предложить другой подход, основанный на идее завершения, являющейся «последней видимой» ячейкой для отправки cellForRow.

// Will be set when reload is called
var lastIndexPathToDisplay: IndexPath?

typealias ReloadCompletion = ()->Void

var reloadCompletion: ReloadCompletion?

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    // Setup cell

    if indexPath == self.lastIndexPathToDisplay {

        self.lastIndexPathToDisplay = nil

        self.reloadCompletion?()
        self.reloadCompletion = nil
    }

    // Return cell
...

func reloadData(completion: @escaping ReloadCompletion) {

    self.reloadCompletion = completion

    self.mainTable.reloadData()

    self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last
}

Одна из возможных проблем: Если reloadData()завершено до того, как lastIndexPathToDisplayбыло установлено, «последняя видимая» ячейка будет отображаться до того, как lastIndexPathToDisplayбыло установлено, и завершение не будет вызываться (и будет в состоянии «ожидания»):

self.mainTable.reloadData()

// cellForRowAt could be finished here, before setting `lastIndexPathToDisplay`

self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last

Если мы повернем вспять, мы можем получить завершение, вызванное прокруткой раньше reloadData().

self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last

// cellForRowAt could trigger the completion by scrolling here since we arm 'lastIndexPathToDisplay' before 'reloadData()'

self.mainTable.reloadData()
bauerMusic
источник
0

Попробуй это:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

Цвет tableView будет изменен с черного на зеленый только после завершения reloadData()функции.

Нирбхай Сингх
источник
0

Вы можете использовать функцию executeBatchUpdates в uitableview

Вот как вы можете достичь

self.tableView.performBatchUpdates({

      //Perform reload
        self.tableView.reloadData()
    }) { (completed) in

        //Reload Completed Use your code here
    }
Номан Харун
источник
0

Создание многоразового расширения CATransaction:

public extension CATransaction {
    static func perform(method: () -> Void, completion: @escaping () -> Void) {
        begin()
        setCompletionBlock {
            completion()
        }
        method()
        commit()
    }
}

Теперь создаем расширение UITableView, которое будет использовать метод расширения CATransaction:

public extension UITableView {
    func reloadData(completion: @escaping (() -> Void)) {
       CATransaction.perform(method: {
           reloadData()
       }, completion: completion)
    }
}

Использование:

tableView.reloadData(completion: {
    //Do the stuff
})
Umair
источник
-2

Вы можете использовать его для выполнения каких-либо действий после перезагрузки данных:

[UIView animateWithDuration:0 animations:^{
    [self.contentTableView reloadData];
} completion:^(BOOL finished) {
    _isUnderwritingUpdate = NO;
}];
Vit
источник
-20

Попробуйте установить задержки:

[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2];
[_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];
Джейк
источник
14
Это опасно Что если перезагрузка займет больше времени, чем ваша задержка?
ограбить