Como saber quando o UITableView concluiu o ReloadData?

183

Estou tentando rolar para a parte inferior de um UITableView após concluir a execução [self.tableView reloadData]

Eu originalmente tinha

 [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];

Mas então eu li que reloadData é assíncrono, então a rolagem não acontece desde o self.tableView, [self.tableView numberOfSections]e [self.tableView numberOfRowsinSectionsão todos 0.

Obrigado!

O estranho é que estou usando:

[self.tableView reloadData];
NSLog(@"Number of Sections %d", [self.tableView numberOfSections]);
NSLog(@"Number of Rows %d", [self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1);

No console, ele retorna Seções = 1, Linha = -1;

Quando eu faço exatamente o mesmo NSLogs cellForRowAtIndexPath, recebo Sections = 1 e Row = 8; (8 está certo)

Alan
fonte
Possível duplicata desta pergunta: stackoverflow.com/questions/4163579/…
pmk 18/04/13
2
melhor solução que eu já vi. stackoverflow.com/questions/1483581/…
Khaled Annajar
Minha resposta para o seguinte pode ajudá-lo, stackoverflow.com/questions/4163579/…
Suhas Aithal
Tente minha resposta aqui - stackoverflow.com/questions/4163579/…
Suhas Aithal

Respostas:

288

A recarga acontece durante a próxima passagem de layout, o que normalmente acontece quando você retorna o controle ao loop de execução (depois, digamos, da ação do botão ou do que for retornado).

Portanto, uma maneira de executar algo depois que a exibição da tabela é recarregada é simplesmente forçar a exibição da tabela a executar o layout imediatamente:

[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];

Outra maneira é agendar seu código pós-layout para execução posterior usando 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];
});

ATUALIZAR

Após uma investigação mais aprofundada, acho que a exibição da tabela envia tableView:numberOfSections:e tableView:numberOfRowsInSection:para sua fonte de dados antes de retornar reloadData. Se o delegado implementar tableView:heightForRowAtIndexPath:, a exibição da tabela também enviará isso (para cada linha) antes de retornar de reloadData.

No entanto, a exibição da tabela não envia tableView:cellForRowAtIndexPath:ou tableView:headerViewForSectionaté a fase de layout, que ocorre por padrão quando você retorna o controle ao loop de execução.

Também acho que, em um pequeno programa de teste, o código na sua pergunta rola corretamente para a parte inferior da exibição da tabela, sem que eu faça nada de especial (como enviar layoutIfNeededou usar dispatch_async).

Rob Mayoff
fonte
3
@rob, dependendo do tamanho da fonte de dados da sua tabela, você pode animar indo para a parte inferior da tableview no mesmo loop de execução. Se você tentar seu código de teste com uma tabela enorme, seu truque de usar o GCD para atrasar a rolagem até o próximo ciclo de execução funcionará, enquanto a rolagem imediata falhará. Mas de qualquer forma, obrigado por esse truque !!
Sr. T
7
O método 2 não funcionou para mim por algum motivo desconhecido, mas escolheu o primeiro método.
Raj Pawan Gumdal
4
dispatch_async(dispatch_get_main_queue())não é garantido que o método funcione. Estou vendo um comportamento não determinístico com ele, no qual, às vezes, o sistema conclui as layoutSubviews e a renderização da célula antes do bloco de conclusão, e às vezes depois. Vou postar uma resposta que funcionou para mim abaixo.
Tyler Sheaffer 22/09
3
Concordo em dispatch_async(dispatch_get_main_queue())nem sempre trabalhar. Vendo resultados aleatórios aqui.
Vojto 26/01
1
O thread principal executa um NSRunLoop. Um loop de execução tem fases diferentes e você pode agendar um retorno de chamada para uma fase específica (usando a CFRunLoopObserver). O layout do cronograma do UIKit ocorre durante uma fase posterior, após o retorno do manipulador de eventos.
Rob mayoff
106

Rápido:

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

...somewhere later...

tableView.reloadData {
    println("done")
}

Objetivo-C:

[UIView animateWithDuration:0 animations:^{
    [myTableView reloadData];
} completion:^(BOOL finished) {
    //Do something after that...
}];
Aviel Gross
fonte
16
Isso é equivalente a despachar algo no thread principal no "futuro próximo". É provável que você esteja vendo a exibição da tabela renderizar os objetos antes que o encadeamento principal desenque o bloco de conclusão. Não é aconselhável fazer esse tipo de invasão em primeiro lugar, mas, em qualquer caso, você deve usar dispatch_after se quiser tentar isso.
seo
1
A solução do Rob é boa, mas não funciona se não houver linhas na tableview. A solução da Aviel tem a vantagem de funcionar mesmo quando a tabela não contém linhas, mas apenas seções.
Chrstph SLN
@Christophe A partir de agora, eu era capaz de usar a atualização de Rob em uma exibição de tabela sem nenhuma linha, substituindo no tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Intmétodo Mock View Controller o método e inserindo em minha substituição o que quisesse notificar que a recarga havia terminado.
Gobe
49

No Xcode 8.2.1, iOS 10 e swift 3,

Você pode determinar o final tableView.reloadData()facilmente usando um bloco CATransaction:

CATransaction.begin()
CATransaction.setCompletionBlock({
    print("reload completed")
    //Your completion code here
})
print("reloading")
tableView.reloadData()
CATransaction.commit()

O exemplo acima também funciona para determinar o final de reloadData () do UICollectionView e reloadAllComponents () do UIPickerView.

Kolaworld
fonte
Also Também funciona se você estiver executando um recarregamento personalizado, como inserir, excluir ou mover manualmente linhas na exibição de tabela, dentro beginUpdatese endUpdateschamadas.
Darrarski
Eu acredito que esta é realmente a solução moderna. na verdade, é o padrão comum no iOS, exemplo ... stackoverflow.com/a/47536770/294884
Fattie
Eu tentei isso. Eu tenho um comportamento muito estranho. Minha tableview mostra corretamente dois headerViews. Dentro dos setCompletionBlockmeus numberOfSectionsshows 2 ... até agora tudo bem. No entanto, se por dentro setCompletionBlockeu faço tableView.headerView(forSection: 1)isso retorna nil!!! portanto, acho que esse bloco está acontecendo antes da recarga ou captura algo antes ou estou fazendo algo errado. Para sua informação, tentei a resposta de Tyler e funcionou! @Fattie
Honey
32

Não dispatch_async(dispatch_get_main_queue())é garantido que o método acima funcione . Estou vendo um comportamento não determinístico com ele, no qual, às vezes, o sistema conclui as layoutSubviews e a renderização da célula antes do bloco de conclusão, e às vezes depois.

Aqui está uma solução que funciona 100% para mim, no iOS 10. Ele requer a capacidade de instanciar o UITableView ou UICollectionView como uma subclasse personalizada. Aqui está a solução UICollectionView, mas é exatamente a mesma para o 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

Exemplo de uso:

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

Veja aqui uma versão Swift desta resposta

Tyler Sheaffer
fonte
Esta é a única maneira correta. Adicionei-o ao meu projeto porque eu precisava dos quadros finais de algumas células para fins de animação. Também adicionei e editei para o Swift. Espero que você não se importa 😉
Jon Vogel
2
Depois que você chama o bloco, layoutSubviewsele deve ser definido nilcomo chamadas subsequentes para layoutSubviews, não necessariamente devido à reloadDatachamada, resultará na execução do bloco, pois há uma forte referência sendo mantida, que não é o comportamento desejado.
Mark Bourke
por que não posso usar isso para o UITableView? está mostrando nenhuma interface visível. Eu importado o arquivo de cabeçalho também, mas ainda mesmo
Julfikar
2
Um adendo a esta resposta é que é possível bloquear o retorno de chamada existente, se houver apenas um, o que significa que vários chamadores terão uma condição de corrida. A solução é criar reloadDataCompletionBlockuma matriz de blocos e iterar sobre eles na execução e esvaziar a matriz depois disso.
Tyler Sheaffer #
1) isso não é equivalente à primeira resposta de Rob, ou seja, usar layoutIfNeeded? 2) por que você mencionou o iOS 10, ele não funciona no iOS 9 ?!
Mel
30

Eu tive os mesmos problemas que Tyler Sheaffer.

Eu implementei sua solução no Swift e resolveu meus problemas.

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()
  }
}

Swift 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()
  }
}

Exemplo de uso:

tableView.reloadDataWithCompletion() {
 // reloadData is guaranteed to have completed
}
Shawn Aukstak
fonte
1
legais! pequena nit-pick, você pode remover o if letdizendo reloadDataCompletionBlock?()que irá chamar sse não nil 💥
Tyler Sheaffer
Sem sorte com um presente na minha situação no ios9
Matjan
self.reloadDataCompletionBlock? { completion() }deveria ter sidoself.reloadDataCompletionBlock?()
emem 26/05/19
Como lidar com o redimensionamento da altura da exibição da tabela? Eu estava previamente chamando tableView.beginUpdates () tableView.layoutIfNeeded () tableView.endUpdates ()
Parth tamane '
10

E uma UICollectionViewversão, baseada na resposta da kolaworld:

https://stackoverflow.com/a/43162226/1452758

Precisa de teste. Até o momento, funciona no iOS 9.2, Xcode 9.2 beta 2, com rolagem de uma coleção para um índice, como um fechamento.

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()
    }
}

Uso:

myCollectionView.reloadDataThenPerform {
    myCollectionView.scrollToItem(at: indexPath,
            at: .centeredVertically,
            animated: true)
}
Womble
fonte
6

Parece que as pessoas ainda estão lendo esta pergunta e as respostas. B / c disso, estou editando minha resposta para remover a palavra síncrona, que é realmente irrelevante para isso.

When [tableView reloadData]retorna, as estruturas de dados internas atrás do tableView foram atualizadas. Portanto, quando o método for concluído, você poderá rolar com segurança até o final. Eu verifiquei isso no meu próprio aplicativo. A resposta amplamente aceita por @ rob-mayoff, embora também seja confusa em terminologia, reconhece o mesmo em sua última atualização.

Se você tableViewnão estiver rolando para baixo, pode haver um problema em outro código que você não postou. Talvez você esteja alterando os dados depois que a rolagem estiver concluída e não estiver recarregando e / ou rolando para baixo, então?

Adicione algum log da seguinte maneira para verificar se os dados da tabela estão corretos depois reloadData. Eu tenho o código a seguir em um aplicativo de exemplo e funciona perfeitamente.

// 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
fonte
Eu atualizei minhas perguntas. Você sabe por que meus NSLogs sairiam assim?
Alan
8
reloadDatanão é síncrono. Ela costumava ser - veja esta resposta: stackoverflow.com/a/16071589/193896
bendytree
1
É síncrono. É muito fácil testar e ver isso com um aplicativo de exemplo. Você vinculou à resposta de @ rob nesta pergunta. Se você leu a atualização dele na parte inferior, ele também verificou isso. Talvez você esteja falando sobre as mudanças no layout visual. É verdade que o tableView não é visivelmente atualizado de forma síncrona, mas os dados são. É por isso que os valores que o OP precisa estão corretos imediatamente após o reloadDataretorno.
XJones
1
Você pode estar confuso sobre o que se espera que aconteça reloadData. Use meu caso de teste viewWillAppearpara aceitar a scrollToRowAtIndexPath:linha b / c que não faz sentido se tableViewnão for exibida. Você verá que reloadDataatualizou os dados armazenados em cache na tableViewinstância e reloadDataé síncrono. Se você estiver se referindo a outros tableViewmétodos delegados chamados quando o tableViewlayout estiver sendo layout, eles não serão chamados se o tableViewnão for exibido. Se estou entendendo mal o seu cenário, explique.
XJones 23/05
3
Que momentos divertidos. É 2014, e há argumentos sobre se algum método é síncrono e assíncrono ou não. Parece adivinhação. Todos os detalhes da implementação são completamente opacos por trás desse nome de método. A programação não é ótima?
Fatuhoku
5

Eu uso esse truque, com certeza já o publiquei em uma duplicata desta pergunta:

-(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
fonte
@Fattie, não está claro se você quer dizer um comentário positivo ou negativo. Mas vi que você comentou outra resposta como "esta parece ser a melhor solução!" , então acho que relativamente falando, você não considera esta solução a melhor.
Cœur
1
Confiando em um efeito colateral de uma animação falsa? De jeito nenhum é uma boa ideia. Aprenda a executar o seletor ou o GCD e faça-o corretamente. Agora, existe um método carregado de tabela que você poderia usar se não se importar em usar um protocolo privado, o que provavelmente é bom, porque é a estrutura que chama seu código e não o contrário.
malhal
3

Na verdade, este resolveu meu problema:

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

NSSet *visibleSections = [NSSet setWithArray:[[tableView indexPathsForVisibleRows] valueForKey:@"section"]];
if (visibleSections) {
    // hide the activityIndicator/Loader
}}
Asha Antony
fonte
2

Tente desta forma, vai funcionar

[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

Vou executar quando a tabela estiver completamente carregada

Outra solução é que você pode subclassificar UITableView

Shashi3456643
fonte
1

Acabei usando uma variação da solução de Shawn:

Crie uma classe UITableView personalizada com um delegado:

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

    override func layoutSubviews() {
        super.layoutSubviews()
        self.customDelegate?.CustomTableViewDidLayoutSubviews()
    }
}

Então, no meu código, eu uso

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!!
    }
}

Certifique-se também de definir sua exibição de tabela como CustomTableView no construtor de interfaces:

insira a descrição da imagem aqui

Sam
fonte
isso funciona, mas o problema é que o método é atingido toda vez que é feito o carregamento de uma única célula, NÃO É O TOPO DA VISTA DE TABELA RELACIONADA, portanto, claramente, essa resposta não se refere à pergunta.
Yash Bedi
É verdade que é chamado mais de uma vez, mas não em todas as células. Assim, você pode ouvir o primeiro delegado e ignorar o restante até ligar para reloadData novamente.
Sam
1

Em Swift 3.0 + podemos criar uma extensão para UITableViewcom um escaped Closurecomo abaixo:

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

E use-o como abaixo, onde quiser:

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

espero que isso ajude alguém. Felicidades!

Chanaka Caldera
fonte
Idéia brilhante ... apenas para evitar confusão, alterei o nome da função t recarregar, em vez de reloadData (). Graças
Vijay Kumar AB
1

Detalhes

  • Xcode versão 10.2.1 (10E1001), Swift 5

Solução

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)
    }
}

Uso

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) }

Amostra completa

Não se esqueça de adicionar o código da solução aqui

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
    }
}

Resultados

insira a descrição da imagem aqui

Vasily Bodnarchuk
fonte
0

Apenas para oferecer outra abordagem, com base na ideia de a conclusão ser a célula "última visível" a ser enviada 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
}

Um possível problema é: se reloadData()tiver terminado antes da lastIndexPathToDisplaydefinição, a célula 'last visível' será exibida antes da lastIndexPathToDisplaydefinição e a conclusão não será chamada (e estará no estado 'em espera'):

self.mainTable.reloadData()

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

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

Se revertermos, poderemos terminar com a conclusão sendo rolada antes 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
fonte
0

Tente o seguinte:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

A cor do tableView será alterada de preto para verde somente após a conclusão da reloadData()função.

Nirbhay Singh
fonte
0

Você pode usar a função performBatchUpdates do uitableview

Aqui está como você pode conseguir

self.tableView.performBatchUpdates({

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

        //Reload Completed Use your code here
    }
Noman Haroon
fonte
0

Criando uma extensão reutilizável do CATransaction:

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

Agora, criando uma extensão do UITableView que usaria o método de extensão do CATransaction:

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

Uso:

tableView.reloadData(completion: {
    //Do the stuff
})
Umair
fonte
-2

Você pode usá-lo para fazer alguma coisa após recarregar os dados:

[UIView animateWithDuration:0 animations:^{
    [self.contentTableView reloadData];
} completion:^(BOOL finished) {
    _isUnderwritingUpdate = NO;
}];
Vit
fonte
-20

Tente definir atrasos:

[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2];
[_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];
Jake
fonte
14
Isso é perigoso. E se demorar mais para recarregar do que o seu atraso?
rob