ios iphone uitableview pagination

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



iphone pagination (12)

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 (i.e., 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..<end) } catch _ { print("query failed") } // update UITableView with new batch of items on main thread after query finishes DispatchQueue.main.async { if let newItems = thisBatchOfItems { // append the new items to the data source for the table view self.myObjectArray.appendContentsOf(newItems) // reload the table view self.tableView.reloadData() // check if this was the last of the data if newItems.count < self.itemsPerBatch { self.reachedEndOfItems = true print("reached end of data. Batch count: /(newItems.count)") } // reset the offset for the next data query self.offset += self.itemsPerBatch } } } }

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


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

<?xml version="1.0" encoding="UTF-8"?> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16E195" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r"> <device id="retina4_7" orientation="portrait"> <adaptation id="fullscreen"/> </device> <dependencies> <deployment identifier="iOS"/> <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <scenes> <!--View Controller--> <scene sceneID="tne-QT-ifu"> <objects> <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="_20269474" customModuleProvider="target" sceneMemberID="viewController"> <layoutGuides> <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/> <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/> </layoutGuides> <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC"> <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> <tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="28" sectionFooterHeight="28" translatesAutoresizingMaskIntoConstraints="NO" id="6la-L6-Fo3"> <rect key="frame" x="0.0" y="20" width="375" height="647"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> </tableView> </subviews> <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <constraints> <constraint firstItem="6la-L6-Fo3" firstAttribute="bottom" secondItem="wfy-db-euE" secondAttribute="top" id="GvO-lp-VW8"/> <constraint firstAttribute="trailing" secondItem="6la-L6-Fo3" secondAttribute="trailing" id="aGF-Ie-T6Y"/> <constraint firstItem="6la-L6-Fo3" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="obO-Sn-WYo"/> <constraint firstItem="6la-L6-Fo3" firstAttribute="top" secondItem="y3c-jy-aDJ" secondAttribute="bottom" id="zp5-B1-sWe"/> </constraints> </view> <connections> <outlet property="tableView" destination="6la-L6-Fo3" id="4lR-IX-vtT"/> </connections> </viewController> <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/> </objects> <point key="canvasLocation" x="48.799999999999997" y="37.331334332833585"/> </scene> </scenes> </document>

Resultado


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


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


Implementé una solución que encontré en , 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]; } }


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:

<NSIndexPath: 0xc0000000002e0016> {length = 2, path = 0 - 92} dataLimit :100 <NSIndexPath: 0xc000000000298016> {length = 2, path = 0 - 83} dataLimit :100 <NSIndexPath: 0xc000000000278016> {length = 2, path = 0 - 79} dataLimit :100 <NSIndexPath: 0xc000000000238016> {length = 2, path = 0 - 71} dataLimit :75 <NSIndexPath: 0xc0000000001d8016> {length = 2, path = 0 - 59} dataLimit :75 <NSIndexPath: 0xc0000000001c0016> {length = 2, path = 0 - 56} dataLimit :75 <NSIndexPath: 0xc000000000138016> {length = 2, path = 0 - 39} dataLimit :50 <NSIndexPath: 0xc000000000120016> {length = 2, path = 0 - 36} dataLimit :50 <NSIndexPath: 0xc000000000008016> {length = 2, path = 0 - 1} dataLimit :25 <NSIndexPath: 0xc000000000008016> {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, :)


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.


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


Puede hacerlo agregando un control de dónde se encuentra en el método cellForRowAtIndexPath: . Este método es fácil de entender y de implementar:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { // Classic start method static NSString *cellIdentifier = @"MyCell"; MyCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier]; if (!cell) { cell = [[MyCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MainMenuCellIdentifier]; } MyData *data = [self.dataArray objectAtIndex:indexPath.row]; // Do your cell customisation // cell.titleLabel.text = data.title; BOOL lastItemReached = [data isEqual:[[self.dataArray] lastObject]]; if (!lastItemReached && indexPath.row == [self.dataArray count] - 1) { [self launchReload]; } }

EDITAR: agregó una marca en el último elemento para evitar llamadas recurrentes. Tendrá que implementar el método que define si se ha alcanzado o no el último elemento.

EDIT2: explicó lastItemReached


- (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 (presumably 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.


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


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