¿Cómo saber cuándo UITableView ha completado ReloadData?

183

Estoy tratando de desplazarme hasta la parte inferior de un UITableView después de que se realiza [self.tableView reloadData]

Originalmente tuve

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

Pero luego leí que reloadData es asíncrono, por lo que el desplazamiento no ocurre desde entonces self.tableView, [self.tableView numberOfSections]y [self.tableView numberOfRowsinSectiontodos son 0.

¡Gracias!

Lo extraño es que estoy 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);

En la consola, devuelve Sections = 1, Row = -1;

Cuando hago exactamente los mismos NSLogs en cellForRowAtIndexPath, obtengo Sections = 1 y Row = 8; (8 es correcto)

Alan
fuente
Posible duplicado de esta pregunta: stackoverflow.com/questions/4163579/…
pmk
2
La mejor solución que he visto. stackoverflow.com/questions/1483581/…
Khaled Annajar
Mi respuesta para lo siguiente podría ayudarlo, stackoverflow.com/questions/4163579/…
Suhas Aithal
Prueba mi respuesta aquí - stackoverflow.com/questions/4163579/…
Suhas Aithal

Respuestas:

288

La recarga ocurre durante la siguiente pasada de diseño, que normalmente ocurre cuando devuelve el control al ciclo de ejecución (después de, digamos, la acción del botón o lo que sea que regrese).

Entonces, una forma de ejecutar algo después de que la vista de tabla se vuelve a cargar es simplemente forzar a la vista de tabla a realizar el diseño de inmediato:

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

Otra forma es programar su código de diseño posterior para que se ejecute más tarde 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];
});

ACTUALIZAR

Luego de una investigación más profunda, encuentro que la vista de tabla envía tableView:numberOfSections:y tableView:numberOfRowsInSection:a su fuente de datos antes de regresar reloadData. Si el delegado implementa tableView:heightForRowAtIndexPath:, la vista de tabla también envía eso (para cada fila) antes de regresar reloadData.

Sin embargo, la vista de tabla no envía tableView:cellForRowAtIndexPath:o tableView:headerViewForSectionhasta la fase de diseño, que ocurre de forma predeterminada cuando devuelve el control al ciclo de ejecución.

También encuentro que en un pequeño programa de prueba, el código en su pregunta se desplaza correctamente hasta la parte inferior de la vista de tabla, sin que yo haga nada especial (como enviar layoutIfNeededo usar dispatch_async).

Rob Mayoff
fuente
3
@rob, dependiendo de qué tan grande sea la fuente de datos de su tabla, puede animar ir al final de la vista de tabla en el mismo ciclo de ejecución. Si prueba su código de prueba con una tabla enorme, su truco de usar GCD para retrasar el desplazamiento hasta que funcione el siguiente ciclo de ejecución, mientras que el desplazamiento inmediato fallará. Pero de todos modos, ¡gracias por este truco!
Sr. T
77
El método 2 no funcionó para mí por alguna razón desconocida, sino que elegí el primer método.
Raj Pawan Gumdal
44
dispatch_async(dispatch_get_main_queue())No se garantiza que el método funcione. Veo un comportamiento no determinista con él, en el que a veces el sistema ha completado las subvistas de diseño y la representación de la celda antes del bloque de finalización, y a veces después. Publicaré una respuesta que funcionó para mí a continuación.
Tyler Sheaffer
3
De acuerdo con dispatch_async(dispatch_get_main_queue())no siempre trabajando. Ver resultados aleatorios aquí.
Vojto
1
El hilo principal ejecuta un NSRunLoop. Un ciclo de ejecución tiene diferentes fases, y puede programar una devolución de llamada para una fase específica (usando a CFRunLoopObserver). UIKit programa el diseño para que ocurra durante una fase posterior, después de que regrese su controlador 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")
}

C objetivo:

[UIView animateWithDuration:0 animations:^{
    [myTableView reloadData];
} completion:^(BOOL finished) {
    //Do something after that...
}];
Aviel Gross
fuente
16
Eso es equivalente a enviar algo en el hilo principal en el "futuro cercano". Es probable que solo esté viendo que la vista de tabla representa los objetos antes de que el hilo principal elimine el bloque de finalización. No se recomienda hacer este tipo de pirateo en primer lugar, pero en cualquier caso, debe usar dispatch_after si va a intentar esto.
seo
1
La solución de Rob es buena, pero no funciona si no hay filas en la vista de tabla. La solución de Aviel tiene la ventaja de funcionar incluso cuando la tabla no contiene líneas sino solo secciones.
Chrstph SLN
@Christophe A partir de ahora, pude usar la actualización de Rob en una vista de tabla sin filas anulando en mi controlador de vista Mock el tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Intmétodo e insertando en mi anulación todo lo que quería notificar que la recarga había terminado.
Gobe
49

A partir de Xcode 8.2.1, iOS 10 y Swift 3,

Puede determinar el final tableView.reloadData()fácilmente utilizando un bloque CATransaction:

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

Lo anterior también funciona para determinar el final de reloadData () de UICollectionView y reloadAllComponents () de UIPickerView.

kolaworld
fuente
👍 También funciona si realiza una recarga personalizada, como insertar, eliminar o mover filas manualmente en la vista de tabla, dentro de beginUpdatesy endUpdatesllamadas.
Darrarski
Creo que esta es en realidad la solución moderna. de hecho es el patrón común en iOS, ejemplo ... stackoverflow.com/a/47536770/294884
Fattie
Intenté esto Tengo un comportamiento muy extraño. Mi tableview muestra correctamente dos headerViews. Dentro de setCompletionBlockmi numberOfSectionsmuestra 2 ... hasta ahora todo bien. Sin embargo, si por dentro setCompletionBlocklo hago tableView.headerView(forSection: 1), nil¡ vuelve ! Por lo tanto, creo que este bloqueo ocurre antes de la recarga o captura algo antes o estoy haciendo algo mal. FYI ¡Intenté la respuesta de Tyler y funcionó! @Fattie
Miel
32

El dispatch_async(dispatch_get_main_queue())método anterior se no se garantiza que funcione . Veo un comportamiento no determinista con él, en el que a veces el sistema ha completado las subvistas de diseño y la representación de la celda antes del bloque de finalización, y a veces después.

Aquí hay una solución que funciona al 100% para mí, en iOS 10. Requiere la capacidad de instanciar UITableView o UICollectionView como una subclase personalizada. Aquí está la solución UICollectionView, pero es exactamente lo mismo para 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

Ejemplo de uso:

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

Vea aquí una versión rápida de esta respuesta

Tyler Sheaffer
fuente
Esta es la única forma correcta. Lo agregué a mi proyecto porque necesitaba los cuadros finales de algunas celdas para fines de animación. También agregué y edité para Swift. Espero que no te importe
Jon
2
Después de llamar al bloque layoutSubviews, debe establecerse nilcomo llamadas posteriores a layoutSubviews, no necesariamente debido a que reloadDatase llama, dará como resultado que el bloque se ejecute, ya que existe una fuerte referencia, que no es el comportamiento deseado.
Mark Bourke
¿Por qué no puedo usar esto para UITableView? No muestra ninguna interfaz visible. También
importé
2
Una adición a esta respuesta es que es posible bloquear la devolución de llamada existente si solo hay una, lo que significa que varias personas que llaman tendrán una condición de carrera. La solución es hacer reloadDataCompletionBlockuna matriz de bloques e iterar sobre ellos en la ejecución y vaciar la matriz después de eso.
Tyler Sheaffer
1) ¿No es esto equivalente a la primera respuesta de Rob, es decir, usar layoutIfNeeded? 2) ¿por qué mencionaste iOS 10, no funciona en iOS 9?
Miel
30

Tuve los mismos problemas que Tyler Sheaffer.

Implementé su solución en Swift y resolvió mis 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()
  }
}

Ejemplo de uso:

tableView.reloadDataWithCompletion() {
 // reloadData is guaranteed to have completed
}
Shawn Aukstak
fuente
1
¡bonito! pequeña selección, puede eliminar el if letdiciendo reloadDataCompletionBlock?()que llamará iff no nil Ty
Tyler Sheaffer
No tuve suerte con este en mi situación en ios9
Matjan
self.reloadDataCompletionBlock? { completion() }debería haber sidoself.reloadDataCompletionBlock?()
emem
¿Cómo manejo el cambio de tamaño de la altura de la vista de la tabla? Anteriormente estaba llamando a tableView.beginUpdates () tableView.layoutIfNeeded () tableView.endUpdates ()
Parth Tamane el
10

Y una UICollectionViewversión, basada en la respuesta de kolaworld:

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

Necesita pruebas. Funciona hasta ahora en iOS 9.2, Xcode 9.2 beta 2, con desplazamiento de un collectionView a un índice, como un cierre.

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
fuente
6

Parece que la gente todavía está leyendo esta pregunta y las respuestas. B / c de eso, estoy editando mi respuesta para eliminar la palabra Synchronous que es realmente irrelevante para esto.

When [tableView reloadData]devuelve, las estructuras de datos internos detrás de tableView se han actualizado. Por lo tanto, cuando se complete el método, puede desplazarse con seguridad hasta la parte inferior. Verifiqué esto en mi propia aplicación. La respuesta ampliamente aceptada por @ rob-mayoff, aunque también es confusa en términos, reconoce lo mismo en su última actualización.

Si tableViewno se desplaza hacia abajo, es posible que tenga un problema con otro código que no haya publicado. ¿Tal vez está cambiando los datos después de que se completa el desplazamiento y no está recargando y / o desplazándose al fondo entonces?

Agregue algunos registros de la siguiente manera para verificar que los datos de la tabla sean correctos después reloadData. Tengo el siguiente código en una aplicación de muestra y funciona perfectamente.

// 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
fuente
Actualicé mis preguntas. ¿Sabes por qué mis NSLogs saldrían así?
Alan
8
reloadDataNo es sincrónico. Solía ​​ser - mira esta respuesta: stackoverflow.com/a/16071589/193896
bendytree
1
Es sincrónico. Es muy fácil probar y ver esto con una aplicación de muestra. Usted se vinculó a la respuesta de @ rob en esta pregunta. Si lees su actualización en la parte inferior, él también lo ha verificado. Quizás estás hablando de los cambios en el diseño visual. Es cierto que tableView no se actualiza visiblemente sincrónicamente, pero los datos sí. Es por eso que los valores que necesita el OP son correctos inmediatamente después de los reloadDataretornos.
XJones
1
Puede estar confundido acerca de lo que se espera que suceda reloadData. Use mi caso de prueba viewWillAppearpara aceptar la scrollToRowAtIndexPath:línea b / c que no tiene sentido si tableViewno se muestra. Verá que reloadDataactualizó los datos almacenados en caché en la tableViewinstancia y que reloadDataes sincrónico. Si se refiere a otros tableViewmétodos de delegado llamados cuando tableViewse está diseñando, no se los llamará si tableViewno se muestra. Si no entiendo su escenario, explique.
XJones
3
Que momentos divertidos. Es 2014, y hay argumentos sobre si algún método es síncrono y asíncrono o no. Se siente como conjeturas. Todos los detalles de implementación son completamente opacos detrás de ese nombre de método. ¿No es genial la programación?
fatuhoku
5

Utilizo este truco, bastante seguro de que ya lo publiqué en un duplicado de esta pregunta:

-(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
fuente
@Fattie no está claro si lo dices como un comentario positivo o negativo. Pero vi que has comentado otra respuesta ya que "¡esta parece ser la mejor solución!" , así que supongo que, en términos relativos, no considera que esta solución sea la mejor.
Cœur
1
¿Confía en un efecto secundario de una animación falsa? De ninguna manera es una buena idea. Aprenda a realizar el selector o GCD y hágalo correctamente. Por cierto, ahora hay un método cargado de tabla que podría usar si no le importa usar un protocolo privado, lo cual está muy bien porque es el marco que llama a su código en lugar de al revés.
malhal
3

En realidad, este resolvió mi 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
fuente
2

Intenta de esta manera 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

Ejecutaré cuando la mesa esté completamente cargada

Otra solución es que puedes subclasificar UITableView

Shashi3456643
fuente
1

Terminé usando una variación de la solución de Shawn:

Cree una clase UITableView personalizada con un delegado:

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

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

Luego, en mi código, 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!!
    }
}

También asegúrese de establecer su vista de tabla en CustomTableView en el generador de interfaces:

ingrese la descripción de la imagen aquí

Sam
fuente
esto funciona, pero el problema es que el método se ve afectado cada vez que se carga una sola celda, NO TODA LA TABLA VUELVA A VER, por lo que claramente esta respuesta no se refiere a la pregunta formulada.
Yash Bedi
Es cierto que se llama más de una vez, pero no en todas las celdas. Por lo tanto, puede escuchar al primer delegado e ignorar el resto hasta que vuelva a llamar a reloadData.
Sam
1

En Swift 3.0 + podemos crear una extensión UITableViewcon un me escaped Closuregusta a continuación:

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

Y úsalo como Abajo donde quieras:

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

Espero que esto ayude a alguien. ¡salud!

Caldera Chanaka
fuente
Una idea brillante ... solo una cosa, es solo para evitar confusiones, cambié el nombre de la función t reload, en lugar de reloadData (). Gracias
Vijay Kumar AB
1

Detalles

  • Xcode versión 10.2.1 (10E1001), Swift 5

Solución

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

Muestra completa

No olvides agregar el código de la solución aquí

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

ingrese la descripción de la imagen aquí

Vasily Bodnarchuk
fuente
0

Solo para ofrecer otro enfoque, basado en la idea de que la finalización sea la celda 'última visible' a la que se enviará 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 posible problema es: si reloadData()ha finalizado antes de que lastIndexPathToDisplayse establezca, la celda 'última visible' se mostrará antes de que lastIndexPathToDisplayse establezca y no se llamará a la finalización (y estará en estado de 'espera'):

self.mainTable.reloadData()

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

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

Si retrocedemos, podríamos terminar con la activación activada al desplazarse 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
fuente
0

Prueba esto:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

El color de tableView cambiará de negro a verde solo después de reloadData()que se complete la función.

Nirbhay Singh
fuente
0

Puede usar la función performBatchUpdates de uitableview

Así es como puedes lograr

self.tableView.performBatchUpdates({

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

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

Crear una extensión reutilizable de CATransaction:

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

Ahora creando una extensión de UITableView que usaría el método de extensión de CATransaction:

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

Uso:

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

Puede usarlo para hacer algo después de recargar datos:

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

Intenta establecer retrasos:

[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2];
[_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];
Jake
fuente
14
Esto es peligroso. ¿Qué pasa si tarda más en recargar que su retraso?
robar el