Come sapere quando UITableView ha completato ReloadData?


183

Sto cercando di scorrere fino alla fine di un UITableView dopo che è stato eseguito [self.tableView reloadData]

Inizialmente avevo

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

Ma poi ho letto che reloadData è asincrono, quindi lo scorrimento non avviene dal self.tableView, [self.tableView numberOfSections]e [self.tableView numberOfRowsinSectionsono tutti 0.

Grazie!

La cosa strana è che sto 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);

Nella console restituisce Sezioni = 1, Riga = -1;

Quando eseguo esattamente gli stessi NSLogs in cellForRowAtIndexPathottengo Sections = 1 e Row = 8; (8 ha ragione)


Eventuali duplicati di questa domanda: stackoverflow.com/questions/4163579/...
PMK

2
la migliore soluzione che abbia mai visto. stackoverflow.com/questions/1483581/...
Khaled Annajar

La mia risposta per il seguente potrebbe aiutare, stackoverflow.com/questions/4163579/...
Suhas Aithal

Risposte:


288

Il ricaricamento avviene durante il successivo passaggio di layout, che normalmente si verifica quando si restituisce il controllo al ciclo di esecuzione (dopo, ad esempio, l'azione del pulsante o qualunque cosa ritorni).

Quindi un modo per eseguire qualcosa dopo il ricaricamento della vista tabella è semplicemente forzare la vista tabella per eseguire immediatamente il layout:

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

Un altro modo è pianificare l'esecuzione del codice post-layout in un secondo momento utilizzando 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];
});

AGGIORNARE

Dopo ulteriori accertamenti, trovo che la vista tabella invii tableView:numberOfSections:e tableView:numberOfRowsInSection:alla sua origine dati prima di tornare da reloadData. Se il delegato implementa tableView:heightForRowAtIndexPath:, anche la vista tabella lo invia (per ogni riga) prima di tornare da reloadData.

Tuttavia, la vista tabella non invia tableView:cellForRowAtIndexPath:o tableView:headerViewForSectionfino alla fase di layout, che si verifica per impostazione predefinita quando si restituisce il controllo al ciclo di esecuzione.

Trovo anche che in un piccolo programma di test, il codice nella tua domanda scorre correttamente fino alla fine della vista della tabella, senza che io faccia nulla di speciale (come l'invio layoutIfNeededo l'utilizzo dispatch_async).


3
@rob, a seconda di quanto è grande l'origine dati della tabella, puoi animare andando in fondo alla vista tabella nello stesso ciclo di esecuzione. Se provi il tuo codice di test con una tabella enorme, il tuo trucco nell'utilizzare GCD per ritardare lo scorrimento fino al prossimo ciclo di esecuzione funzionerà, mentre lo scorrimento immediato fallirà. Ma comunque, grazie per questo trucco !!
Mr. T

7
Il metodo 2 non ha funzionato per me per qualche motivo sconosciuto, ma ha scelto invece il primo metodo.
Raj Pawan Gumdal,

4
il dispatch_async(dispatch_get_main_queue())metodo non è garantito per funzionare. Sto vedendo un comportamento non deterministico con esso, in cui a volte il sistema ha completato layoutSubviews e il rendering della cella prima del blocco di completamento, e talvolta dopo. Di seguito posterò una risposta che ha funzionato per me.
Tyler Sheaffer,

3
D'accordo con dispatch_async(dispatch_get_main_queue())non sempre funzionante. Vedere risultati casuali qui.
Vojto,

1
Il thread principale esegue un NSRunLoop. Un ciclo di esecuzione ha fasi diverse ed è possibile pianificare un callback per una fase specifica (utilizzando a CFRunLoopObserver). UIKit pianifica il layout in modo che avvenga in una fase successiva, dopo il ritorno del gestore dell'evento.
Rob Mayoff, il

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
Ciò equivale a inviare qualcosa sul thread principale nel "prossimo futuro". È probabile che tu stia solo vedendo la vista della tabella renderizzare gli oggetti prima che il thread principale deques il blocco di completamento. Non è consigliabile fare questo tipo di hack in primo luogo, ma in ogni caso, dovresti usare dispatch_after se hai intenzione di provare questo.
seo,

1
La soluzione di Rob è buona ma non funziona se non ci sono righe nella vista tabella. La soluzione di Aviel ha il vantaggio di funzionare anche quando la tabella non contiene righe ma solo sezioni.
Chrstph SLN,

@Christophe A partire da ora, sono stato in grado di utilizzare l'aggiornamento di Rob in una vista tabella senza righe sovrascrivendo il mio controller Mock view il tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Intmetodo e inserendo nella mia sostituzione tutto ciò che volevo notificare che il ricaricamento era terminato.
Gobe,

49

A partire da Xcode 8.2.1, iOS 10 e swift 3,

Puoi determinare tableView.reloadData()facilmente la fine usando un blocco CATransaction:

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

Quanto sopra funziona anche per determinare la fine di reloadData () di UICollectionView e reloadAllComponents () di UIPickerView.


👍 Funziono anche se stai eseguendo un ricaricamento personalizzato, come l'inserimento, l'eliminazione o lo spostamento manuale di righe nella vista tabella, all'interno beginUpdatese nelle endUpdateschiamate.
Darrarski,

Credo che questa sia in realtà la soluzione moderna. infatti è il modello comune in iOS, ad esempio ... stackoverflow.com/a/47536770/294884
Fattie

Ci ho provato Ho avuto un comportamento molto strano. Il mio tableview mostra correttamente due headerViews. All'interno dei setCompletionBlockmiei numberOfSectionsspettacoli 2 ... finora tutto bene. Eppure se dentro setCompletionBlocklo faccio tableView.headerView(forSection: 1)ritorna nil!!! quindi penso che questo blocco o stia accadendo prima della ricarica o catturi qualcosa prima o sto facendo qualcosa di sbagliato. Cordiali saluti, ho provato la risposta di Tyler e ha funzionato! @Fattie
Honey

32

Il dispatch_async(dispatch_get_main_queue())metodo sopra non è garantito per funzionare . Sto vedendo un comportamento non deterministico con esso, in cui a volte il sistema ha completato layoutSubviews e il rendering della cella prima del blocco di completamento, e talvolta dopo.

Ecco una soluzione che funziona al 100% per me, su iOS 10. Richiede la possibilità di creare un'istanza di UITableView o UICollectionView come sottoclasse personalizzata. Ecco la soluzione UICollectionView, ma è esattamente la stessa per 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

Esempio di utilizzo:

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

Vedi qui per una versione Swift di questa risposta


Questo è l'unico modo corretto. L'ho aggiunto al mio progetto perché avevo bisogno dei fotogrammi finali di alcune celle per scopi di animazione. Ho anche aggiunto e modificato per Swift. Spero non ti dispiaccia 😉
Jon Vogel,

2
Dopo aver chiamato il blocco, layoutSubviewsè necessario impostarlo su nilcome le chiamate successive a layoutSubviews, non necessariamente dovute alla reloadDatachiamata, comporteranno l'esecuzione del blocco poiché viene mantenuto un riferimento forte, che non è il comportamento desiderato.
Mark Bourke,

perché non posso usarlo per UITableView? non mostra alcuna interfaccia visibile. Ho importato anche il file di intestazione, ma sempre lo stesso
Julfikar il

2
Un addendum a questa risposta è che è possibile ostruire il callback esistente se ce n'è solo uno, il che significa che più chiamanti avranno una race condition. La soluzione è creare reloadDataCompletionBlockuna matrice di blocchi e scorrere su di essi durante l'esecuzione e svuotare la matrice successivamente.
Tyler Sheaffer,

1) non è equivalente alla prima risposta di Rob, ovvero usare layoutIfNeeded? 2) perché hai menzionato iOS 10, non funziona su iOS 9 ?!
Miele

30

Ho avuto gli stessi problemi di Tyler Sheaffer.

Ho implementato la sua soluzione in Swift e ho risolto i miei problemi.

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

Esempio di utilizzo:

tableView.reloadDataWithCompletion() {
 // reloadData is guaranteed to have completed
}

1
simpatico! piccolo nit-pick, puoi rimuoverlo if letdicendo reloadDataCompletionBlock?()che chiamerà iff not nil 💥
Tyler Sheaffer

Nessuna fortuna con questo nella mia situazione su iOS9
Matjan,

self.reloadDataCompletionBlock? { completion() }avrebbe dovuto essereself.reloadDataCompletionBlock?()
emem

Come gestisco il ridimensionamento dell'altezza della vista tabella? In precedenza stavo chiamando tableView.beginUpdates () tableView.layoutIfNeeded () tableView.endUpdates ()
Parth Tamane

10

E una UICollectionViewversione, basata sulla risposta di kolaworld:

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

Esigenze di test. Funziona finora su iOS 9.2, Xcode 9.2 beta 2, con scorrimento di una raccolta Visualizza come indice come chiusura.

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

6

Sembra che la gente stia ancora leggendo questa domanda e le risposte. B / c di quello, sto modificando la mia risposta per rimuovere la parola sincrona che è davvero irrilevante per questo.

When [tableView reloadData]ritorna, le strutture di dati interne dietro tableView sono state aggiornate. Pertanto, al termine del metodo è possibile scorrere in sicurezza fino in fondo. Ho verificato questo nella mia app. La risposta ampiamente accettata da @ rob-mayoff, pur confondendo la terminologia, riconosce la stessa cosa nel suo ultimo aggiornamento.

Se tableViewnon stai scorrendo fino in fondo, potresti avere un problema con un altro codice che non hai pubblicato. Forse stai cambiando i dati dopo che lo scorrimento è completo e non stai ricaricando e / o scorrendo verso il basso allora?

Aggiungere un po 'di registrazione come segue per verificare che i dati della tabella siano corretti dopo reloadData. Ho il seguente codice in un'app di esempio e funziona perfettamente.

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

Ho aggiornato le mie domande. Sai perché i miei NSLogs sarebbero usciti così?
Alan,

8
reloadDatanon è sincrono. Ha usato essere - questa risposta: stackoverflow.com/a/16071589/193896
bendytree

1
È sincrono. È molto facile testarlo e vederlo con un'app di esempio. Ti sei collegato alla risposta di @ rob in questa domanda. Se leggi il suo aggiornamento in fondo, ha verificato anche questo. Forse stai parlando delle modifiche al layout visivo. È vero che tableView non viene visibilmente aggiornato in modo sincrono ma i dati lo sono. Ecco perché i valori richiesti dall'OP sono corretti immediatamente dopo i reloadDataritorni.
XJones,

1
Potresti essere confuso su cosa dovrebbe succedere reloadData. Usa il mio caso di test in viewWillAppearaccetta per la scrollToRowAtIndexPath:riga b / c che non ha senso se tableViewnon viene visualizzata. Vedrai che reloadDataha aggiornato i dati memorizzati nella cache tableViewnell'istanza e che reloadDataè sincrono. Se ti riferisci ad altri tableViewmetodi delegati chiamati quando il tableViewlayout è in uscita, questi non verranno chiamati se tableViewnon vengono visualizzati. Se sto fraintendendo il tuo scenario, ti preghiamo di spiegare.
XJones,

3
Che tempi divertenti. È il 2014 e ci sono argomenti sul fatto che alcuni metodi siano sincroni e asincroni o meno. Sembra una congettura. Tutti i dettagli di implementazione sono completamente opachi dietro quel nome di metodo. La programmazione non è eccezionale?
Fatuhoku,

5

Uso questo trucco, abbastanza sicuro di averlo già pubblicato in un duplicato di questa domanda:

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

@Fattie non è chiaro se lo intendi come un commento positivo o un commento negativo. Ma ho visto che hai commentato un'altra risposta come "questa sembra essere la soluzione migliore!" , quindi suppongo che relativamente parlando, non consideri questa soluzione la migliore.
Cuor

1
Fare affidamento su un effetto collaterale di una finta animazione? Non è una buona idea. Impara esegui il selettore o GCD e fallo correttamente. Tra l'altro ora esiste un metodo caricato in tabella che potresti semplicemente usare se non ti dispiace usare un protocollo privato che probabilmente va bene perché è il framework che chiama il tuo codice piuttosto che il contrario.
Malhal,

3

In realtà questo ha risolto il mio 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
}}

2

Prova in questo modo funzionerà

[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

Eseguirò quando la tabella è completamente caricata

Altra soluzione è che puoi sottoclassare UITableView


1

Ho finito per usare una variante della soluzione di Shawn:

Crea una classe UITableView personalizzata con un delegato:

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

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

Quindi nel mio codice, utilizzo

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

Assicurati inoltre di impostare la visualizzazione della tabella su CustomTableView nel generatore di interfaccia:

inserisci qui la descrizione dell'immagine


questo funziona, ma il problema è che il metodo viene colpito ogni volta che viene caricato un singolo cellulare, NON LA TABELLA COMPLETA VISUALIZZA RICARICA, quindi chiaramente questa risposta non è relativa alla domanda posta.
Yash Bedi,

È vero, viene chiamato più di una volta, ma non su ogni cella. Quindi è possibile ascoltare il primo delegato e ignorare il resto fino a quando non si chiama nuovamente reloadData.
Sam,

1

In Swift 3.0 + possiamo creare un'estensione per UITableViewun escaped Closuresimile di seguito:

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

E usalo come di seguito dove vuoi:

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

spero che questo possa aiutare qualcuno. Saluti!


Idea geniale..solo cosa, è solo per evitare confusione, ho cambiato il nome della funzione t ricaricare, piuttosto che reloadData (). Grazie
Vijay Kumar AB,

1

Dettagli

  • Xcode versione 10.2.1 (10E1001), Swift 5

Soluzione

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

Campione completo

Non dimenticare di aggiungere qui il codice della soluzione

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

risultati

inserisci qui la descrizione dell'immagine


0

Solo per offrire un altro approccio, basato sull'idea che il completamento sia l'ultima cella visibile a cui inviare 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
}

Un possibile problema è: se reloadData()è terminato prima dell'impostazione lastIndexPathToDisplay, l'ultima cella visibile verrà visualizzata prima lastIndexPathToDisplaydell'impostazione e il completamento non verrà chiamato (e sarà nello stato "in attesa"):

self.mainTable.reloadData()

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

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

Se invertiamo, potremmo finire con il completamento innescato dallo scorrimento prima reloadData().

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

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

self.mainTable.reloadData()

0

Prova questo:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

Il colore di tableView cambierà da nero a verde solo dopo il reloadData()completamento della funzione.


0

È possibile utilizzare la funzione performBatchUpdates di uitableview

Ecco come puoi raggiungere

self.tableView.performBatchUpdates({

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

        //Reload Completed Use your code here
    }

0

Creazione di un'estensione riutilizzabile di CATransaction:

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

Ora creando un'estensione di UITableView che utilizzerebbe il metodo di estensione di CATransaction:

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

Uso:

tableView.reloadData(completion: {
    //Do the stuff
})

-2

Puoi usarlo per fare qualcosa dopo aver ricaricato i dati:

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

-20

Prova a impostare i ritardi:

[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2];
[_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];

14
Questo è pericoloso. Cosa succede se il caricamento richiede più tempo del ritardo?
rapina il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.