UITableView carga más cuando se desplaza hacia abajo como aplicación de Facebook

Estoy desarrollando una aplicación que usa SQLite. Quiero mostrar una lista de usuarios (UITableView) usando un mecanismo de paginación. ¿Podría alguien decirme cómo cargar más datos en mi lista cuando el usuario se desplaza hasta el final de la lista (como en la página de inicio de la aplicación de Facebook)?

Rápido

Método 1: se desplazó hacia abajo

Aquí está la versión Swift de la respuesta de Pedro Romão . Cuando el usuario deja de desplazarse, comprueba si ha llegado al final.

 func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) { // UITableView only moves in one direction, y axis let currentOffset = scrollView.contentOffset.y let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height // Change 10.0 to adjust the distance from bottom if maximumOffset - currentOffset <= 10.0 { self.loadMore() } } 

Método 2: se alcanzó la última fila

Y aquí está la versión Swift de la respuesta de shinyuX . Comprueba si el usuario ha llegado a la última fila.

 func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { // set up cell // ... // Check if the last row number is the same as the last current data element if indexPath.row == self.dataArray.count - 1 { self.loadMore() } } 

Ejemplo de un método loadMore()

Configuré estas tres variables de clase para buscar lotes de datos.

 // number of items to be fetched each time (ie, database LIMIT) let itemsPerBatch = 50 // Where to start fetching items (database OFFSET) var offset = 0 // a flag for when all database items have already been loaded var reachedEndOfItems = false 

Esta es la función para cargar más elementos de la base de datos en la vista de tabla.

 func loadMore() { // don't bother doing another db query if already have everything guard !self.reachedEndOfItems else { return } // query the db on a background thread DispatchQueue.global(qos: .background).async { // determine the range of data items to fetch var thisBatchOfItems: [MyObjects]? let start = self.offset let end = self.offset + self.itemsPerBatch // query the database do { // SQLite.swift wrapper thisBatchOfItems = try MyDataHelper.findRange(start.. 

Es mejor utilizar el método willDisplayCell para verificar si se cargará la celda. Una vez que obtengamos el indexPath.row actual. indexPath.row es la última vez que podemos cargar más células. Esto cargará más celdas al desplazarse hacia abajo.

  - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { // check if indexPath.row is last row // Perform operation to load new Cell's. } 
 - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { NSInteger lastSectionIndex = [tableView numberOfSections] - 1; NSInteger lastRowIndex = [tableView numberOfRowsInSection:lastSectionIndex] - 1; if ((indexPath.section == lastSectionIndex) && (indexPath.row == lastRowIndex)) { // This is the last cell [self loadMore]; } } 

Si está utilizando Core Data y NSFetchedResultsController , loadMore podría tener el siguiente aspecto:

 // Load more - (void)loadMore { [self.fetchedResultsController.fetchRequest setFetchLimit:newFetchLimit]; [NSFetchedResultsController deleteCacheWithName:@"cache name"]; NSError *error; if (![self.fetchedResultsController performFetch:&error]) { // Update to handle the error appropriately. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); } [self.tableView reloadData]; } 

Implementé una solución que encontré en stackoverflow, y funciona bien, pero creo que la solución de shinyuX es muy fácil de implementar y funciona bien para mi propuesta. Si alguien quiere una solución diferente, puede usar esta a continuación.

 - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{ // UITableView only moves in one direction, y axis CGFloat currentOffset = scrollView.contentOffset.y; CGFloat maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height; //NSInteger result = maximumOffset - currentOffset; // Change 10.0 to adjust the distance from bottom if (maximumOffset - currentOffset <= 10.0) { [self loadOneMorePage]; //[self methodThatAddsDataAndReloadsTableView]; } } 

Detalles

xCode 8.3.1, Swift 3.1

Solución

 import UIKit class LoadMoreActivityIndicator { let spacingFromLastCell: CGFloat let spacingFromLastCellWhenLoadMoreActionStart: CGFloat let activityIndicatorView: UIActivityIndicatorView weak var tableView: UITableView! private var defaultY: CGFloat { return tableView.contentSize.height + spacingFromLastCell } init (tableView: UITableView, spacingFromLastCell: CGFloat, spacingFromLastCellWhenLoadMoreActionStart: CGFloat) { self.tableView = tableView self.spacingFromLastCell = spacingFromLastCell self.spacingFromLastCellWhenLoadMoreActionStart = spacingFromLastCellWhenLoadMoreActionStart let size:CGFloat = 40 let frame = CGRect(x: (tableView.frame.width-size)/2, y: tableView.contentSize.height + spacingFromLastCell, width: size, height: size) activityIndicatorView = UIActivityIndicatorView(frame: frame) activityIndicatorView.color = .black activityIndicatorView.isHidden = false activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin] tableView.addSubview(activityIndicatorView) activityIndicatorView.isHidden = isHidden } required init(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private var isHidden: Bool { if tableView.contentSize.height < tableView.frame.size.height { return true } else { return false } } func scrollViewDidScroll(scrollView: UIScrollView, loadMoreAction: ()->()) { let offsetY = scrollView.contentOffset.y activityIndicatorView.isHidden = isHidden if !isHidden && offsetY >= 0 { let contentDelta = scrollView.contentSize.height - scrollView.frame.size.height let offsetDelta = offsetY - contentDelta let newY = defaultY-offsetDelta if newY < tableView.frame.height { activityIndicatorView.frame.origin.y = newY } else { if activityIndicatorView.frame.origin.y != defaultY { activityIndicatorView.frame.origin.y = defaultY } } if !activityIndicatorView.isAnimating { if offsetY > contentDelta && offsetDelta >= spacingFromLastCellWhenLoadMoreActionStart && !activityIndicatorView.isAnimating { activityIndicatorView.startAnimating() loadMoreAction() } } if scrollView.isDecelerating { if activityIndicatorView.isAnimating && scrollView.contentInset.bottom == 0 { UIView.animate(withDuration: 0.3) { [weak self] in if let bottom = self?.spacingFromLastCellWhenLoadMoreActionStart { scrollView.contentInset = UIEdgeInsetsMake(0, 0, bottom, 0) } } } } } } func loadMoreActionFinshed(scrollView: UIScrollView) { let contentDelta = scrollView.contentSize.height - scrollView.frame.size.height let offsetDelta = scrollView.contentOffset.y - contentDelta if offsetDelta >= 0 { // Animate hiding when activity indicator displaying UIView.animate(withDuration: 0.3) { scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0) } } else { // Hiding without animation when activity indicator displaying scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0) } activityIndicatorView.stopAnimating() activityIndicatorView.isHidden = false } } 

Uso

 extension ViewController: UITableViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { activityIndicator.scrollViewDidScroll(scrollView: scrollView) { DispatchQueue.global(qos: .utility).async { for i in 0..<3 { print(i) sleep(1) } DispatchQueue.main.async { [weak self] in self?.activityIndicator.loadMoreActionFinshed(scrollView: scrollView) } } } } } 

Muestra completa

ViewController.swift

 import UIKit class ViewController: UIViewController { var count = 30 @IBOutlet weak var tableView: UITableView! fileprivate var activityIndicator: LoadMoreActivityIndicator! override func viewDidLoad() { super.viewDidLoad() tableView.dataSource = self tableView.delegate = self tableView.tableFooterView = UIView() activityIndicator = LoadMoreActivityIndicator(tableView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60) print(tableView.frame) } } extension ViewController: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell() cell.textLabel?.text = "\(indexPath)" return cell } } extension ViewController: UITableViewDelegate { func scrollViewDidScroll(_ scrollView: UIScrollView) { activityIndicator.scrollViewDidScroll(scrollView: scrollView) { DispatchQueue.global(qos: .utility).async { for i in 0..<3 { print("!!!!!!!!! \(i)") sleep(1) } DispatchQueue.main.async { [weak self] in self?.activityIndicator.loadMoreActionFinshed(scrollView: scrollView) } } } } } 

Main.storyboard

                                               

enter image description here

Resultado

enter image description here

Use el límite y la compensación en sus consultas y complete su vista de tabla con ese contenido. Cuando el usuario se desplaza hacia abajo, carga el siguiente desplazamiento.

Implemente el tableView:willDisplayCell:forRowAtIndexPath: en su UITableViewDelegate y verifique si es la última fila

A continuación, el enlace proporcionará un código de muestra. # Swift3

El usuario necesita desplegar la última celda de la vista de tabla, al menos la altura de 2 celdas para obtener más datos del servidor.

Encontrará la celda Proceso también para mostrar el proceso de carga como en la última celda.

Está en Swift3

https://github.com/yogendrabagoriya/YBTableViewPullData

Una opción más para usar ( Swift 3 e iOS 10+):

 class DocumentEventsTableViewController: UITableViewController, UITableViewDataSourcePrefetching { var currentPage: Int = 1 override func viewDidLoad() { super.viewDidLoad() self.tableView.prefetchDataSource = self } func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) { let upcomingRows = indexPaths.map { $0.row } if let maxIndex = upcomingRows.max() { let nextPage: Int = Int(ceil(Double(maxIndex) / Double(APICall.defaultPageSize))) + 1 if nextPage > currentPage { // Your function, which attempts to load respective page from the local database loadLocalData(page: nextPage) // Your function, which makes a network request to fetch the respective page of data from the network startLoadingDataFromNetwork(page: nextPage) currentPage = nextPage } } } } 

En el caso de páginas bastante pequeñas (~ 10 elementos), es posible que desee agregar datos manualmente para las páginas 1 y 2, ya que nextPage podría estar en algún lugar entre 1 y 2 hasta que la tabla tenga algunos elementos que se desplazarán bien. Pero funcionará de maravilla para todas las páginas siguientes.

 - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (news.count == 0) { return 0; } else { return news.count + 1 ; } } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { @try { uint position = (uint) (indexPath.row); NSUInteger row = [indexPath row]; NSUInteger count = [news count]; //show Load More if (row == count) { UITableViewCell *cell = nil; static NSString *LoadMoreId = @"LoadMore"; cell = [tableView dequeueReusableCellWithIdentifier:LoadMoreId]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:LoadMoreId]; } if (!hasMoreLoad) { cell.hidden = true; } else { cell.textLabel.text = @"Load more items..."; cell.textLabel.textColor = [UIColor blueColor]; cell.textLabel.font = [UIFont boldSystemFontOfSize:14]; NSLog(@"Load more"); if (!isMoreLoaded) { isMoreLoaded = true; [self performSelector:@selector(loadMoreNews) withObject:nil afterDelay:0.1]; } } return cell; } else { NewsRow *cell = nil; NewsObject *newsObject = news[position]; static NSString *CellIdentifier = @"NewsRow"; cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { // Load the top-level objects from the custom cell XIB. NSArray *topLevelObjects = [[NSBundle mainBundle] loadNibNamed:CellIdentifier owner:self options:nil]; // Grab a pointer to the first object (presumbly the custom cell, as that's all the XIB should contain). cell = topLevelObjects[0]; // Configure the cell... } cell.title.text = newsObject.title; return cell; } } @catch (NSException *exception) { NSLog(@"Exception occurred: %@, %@", exception, [exception userInfo]); } return nil; } 

muy buena explicación en esta publicación.

http://useyourloaf.com/blog/2010/10/02/dynamically-loading-new-rows-into-a-table.html

simple, tiene que agregar la última fila y ocultarla, y cuando la fila de la tabla golpea la última fila, muestra la fila y carga más elementos.

Solo quiero compartir este enfoque:

 - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { NSLog(@"%@", [[YourTableView indexPathsForVisibleRows] lastObject]); [self estimatedTotalData]; } - (void)estimatedTotalData { long currentRow = ((NSIndexPath *)[[YourTableView indexPathsForVisibleRows] lastObject]).row; long estimateDataCount = 25; while (currentRow > estimateDataCount) { estimateDataCount+=25; } dataLimit = estimateDataCount; if (dataLimit == currentRow+1) { dataLimit+=25; } NSLog(@"dataLimit :%ld", dataLimit); [self requestForData]; // this answers the question.. // if(YourDataSource.count-1 == currentRow) { NSLog(@"LAST ROW"); //loadMore data } } 

NSLog(...); salida sería algo así como:

  {length = 2, path = 0 - 92} dataLimit :100  {length = 2, path = 0 - 83} dataLimit :100  {length = 2, path = 0 - 79} dataLimit :100  {length = 2, path = 0 - 71} dataLimit :75  {length = 2, path = 0 - 59} dataLimit :75  {length = 2, path = 0 - 56} dataLimit :75  {length = 2, path = 0 - 39} dataLimit :50  {length = 2, path = 0 - 36} dataLimit :50  {length = 2, path = 0 - 1} dataLimit :25  {length = 2, path = 0 - 1} dataLimit :25 

Esto es bueno para mostrar datos almacenados localmente. Inicialmente declaro el dataLimit a 25, eso significa que uitableview tendrá 0-24 (inicialmente).

Si el usuario se desplazó hacia abajo y la última celda está visible, se agregará dataLimit con 25 …

Nota: Esto es más como una paginación de datos UITableView, 🙂

 -(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { NSInteger sectionsAmount = [tableView numberOfSections]; NSInteger rowsAmount = [tableView numberOfRowsInSection:[indexPath section]]; if ([indexPath section] == sectionsAmount - 1 && [indexPath row] == rowsAmount - 1) { //get last row if (!isSearchActive && !isFilterSearchActive) { if (totalRecords % 8 == 0) { int64_t delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void) { [yourTableView beginUpdates]; [yourTableView insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic]; [yourTableView endUpdates]; }); } } } } 

La mejor forma de resolver este problema es agregar una celda en la parte inferior de la tabla, y esta celda contendrá el indicador.

En swift necesitas agregar esto:

  1. Crear una nueva celda de tipo cellLoading esto mantendrá el indicador. Mira el siguiente código
  2. Mire el número de filas y agregue 1 a él (Esto es para cargar la celda).
  3. debe verificar el rawAtIndex if idexPath.row == yourArray.count y luego devolver la celda de carga.

mira el código a continuación:

 import UIKit class LoadingCell: UITableViewCell { @IBOutlet weak var indicator: UIActivityIndicatorView! } 

Para la vista de tabla: numOfRows:

 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return yourArray.count + 1 } 

cellForRawAt indexPath:

 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { if indexPath.row == users.count { // need to change let loading = Bundle.main.loadNibNamed("LoadingCell", owner: LoadingCell.self , options: nil)?.first as! LoadingCell return loading } let yourCell = tableView.dequeueReusableCell(withIdentifier: "cellCustomizing", for: indexPath) as! UITableViewCell return yourCell } 

Si observa que mi celda de carga se crea a partir de un archivo de punta. Este video explicará lo que hice.

 let threshold = 100.0 // threshold from bottom of tableView var isLoadingMore = false // flag func scrollViewDidScroll(scrollView: UIScrollView) { let contentOffset = scrollView.contentOffset.y let maximumOffset = scrollView.contentSize.height - scrollView.frame.size.height; if !isLoadingMore && (maximumOffset - contentOffset <= threshold) { // Get more data - API call self.isLoadingMore = true // Update UI dispatch_async(dispatch_get_main_queue()) { tableView.reloadData() self.isLoadingMore = false } } }