objective-c uitableview

objective c - ¿Cómo saber cuándo UITableVIew ha completado ReloadData?



objective-c (13)

Estoy tratando de desplazarme hacia la parte inferior de una UITableView después de que [self.tableView reloadData] realizar [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 ya que self.tableView , [self.tableView numberOfSections] y [self.tableView numberOfRowsinSection son todos 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)


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

Puede determinar el final de tableView.reloadData() fácilmente mediante el uso de 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.


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


Intenta de esta forma que funcione

[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 tabla esté completamente cargada

Otra solución es la subclase UITableView


Intenta establecer retrasos:

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


La recarga ocurre durante el siguiente pase de disposición, que normalmente ocurre cuando devuelve el control al ciclo de ejecución (después de, digamos, la acción de su botón o lo que sea que vuelva).

De modo que una forma de ejecutar algo después de que la vista de tabla se recargue es simplemente forzar a la vista de tabla a realizar el diseño inmediatamente:

[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 utilizando 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

Tras una investigación adicional, encuentro que la vista de tabla envía tableView:numberOfRowsInSection: a su fuente de datos antes de regresar de reloadData . Si el delegado implementa tableView:heightForRowAtIndexPath: la vista de tabla también lo envía (para cada fila) antes de regresar de reloadData .

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

Y también me parece que en un pequeño programa de prueba, el código en su pregunta se desplaza correctamente al final de la vista de tabla, sin que yo haga nada especial (como enviar layoutIfNeeded o usar dispatch_async ).


Parece que la gente sigue leyendo esta pregunta y las respuestas. B / c de eso, estoy editando mi respuesta para eliminar la palabra Sincrónico que es realmente irrelevante para esto.

When [tableView reloadData] retorna, las estructuras internas de datos detrás de tableView han sido actualizadas. Por lo tanto, cuando el método finalice, puede desplazarse con seguridad hacia abajo. Lo verifiqué en mi propia aplicación. La respuesta ampliamente aceptada por @ rob-mayoff, aunque también confusa en terminología, reconoce lo mismo en su última actualización.

Si su tableView no se desplaza hacia abajo, puede tener un problema en 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 desplazando hacia abajo a continuación?

Agregue algunos registros de la siguiente manera para verificar que los datos de la tabla sean correctos después de 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];


Puede usarlo para hacer algo después de volver a cargar datos:

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


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 configurar su vista de tabla en CustomTableView en el constructor de interfaz:


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 super.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 super.reloadData() } }

Ejemplo de uso:

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


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


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

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

Necesita pruebas. Funciona hasta el momento en iOS 9.2, Xcode 9.2 beta 2, con el 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) }


No se garantiza que el método dispatch_async(dispatch_get_main_queue()) anterior funcione . Estoy viendo un comportamiento no determinista con él, en el que a veces el sistema ha completado el layoutSubviews y la representación de celda antes del bloque de finalización, y algunas veces después.

Aquí hay una solución que funciona al 100% para mí, en iOS 10. Requiere la capacidad de crear instancias de 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; [super 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 }];


Rápido:

extension UITableView { func reloadData(completion: ()->()) { 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... }];