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

95

Estoy desarrollando una aplicación que usa SQLite. Quiero mostrar una lista de usuarios (UITableView) usando un mecanismo de paginación. ¿Alguien podría 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)?

rokridi
fuente

Respuestas:

103

Puede hacerlo agregando una marca de verificación en el cellForRowAtIndexPath:método. Este método es fácil de entender e 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: se agregó una verificación en el último elemento para evitar llamadas recursivas. Deberá implementar el método que define si se ha alcanzado el último elemento o no.

EDIT2: explicado lastItemReached

shinyuX
fuente
9
¿Qué pasa si el usuario se desplaza hacia arriba y hacia abajo, entonces cellForRowAtIndexPath se llama MUCHAS VECES?
onmyway133
La primera vez que se desplaza hasta el final, se recargará su lista. Y cada vez que toque fondo, se recopilará una nueva cantidad de datos. Si se debe aplicar algún tratamiento específico, será launchReloadresponsabilidad del método manejarlo (por ejemplo, solo una acción de recarga asincrónica a la vez)
shinyuX
4
Tuve que agregar una bandera para evitar un problema de recursividad cuando se alcanzó el último elemento:if !lastItemReached && indexPath.row == dataArray!.hits.count - 1 {
Albert Bori
Cual es el self.launchReloadmetodo
control deslizante
1
@shinyuX no funciona para mí, "si" siempre es falso ... pero si (lastItemReached && indexPath.row == [self.dataArray count] - 1) verdadero, ¿POR QUÉ?
Dijo el
69

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: última fila alcanzada

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 loadMore()método

Configuré estas tres variables de clase para obtener 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
            }

        }
    }
}
Suragch
fuente
Usé el método 1 porque quería tirar para buscar más. Funciona muy bien. ¡Gracias a los dos!
Bob Wakefield
37

Es mejor usar el willDisplayCellmétodo para verificar si se cargará qué celda. Una vez que tenemos la corriente indexPath.rowes la última, podemos cargar más celdas. 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.
}
Suraj Mirajkar
fuente
16
no es mejor ya que reloadData llamará a este método nuevamente, ¿verdad?
Marcin
24

Detalles

  • Swift 5.1, Xcode 11.2.1

Solución

Trabajó con UIScrollView / UICollectionView / UITableView

import UIKit

class LoadMoreActivityIndicator {

    private let spacingFromLastCell: CGFloat
    private let spacingFromLastCellWhenLoadMoreActionStart: CGFloat
    private weak var activityIndicatorView: UIActivityIndicatorView?
    private weak var scrollView: UIScrollView?

    private var defaultY: CGFloat {
        guard let height = scrollView?.contentSize.height else { return 0.0 }
        return height + spacingFromLastCell
    }

    deinit { activityIndicatorView?.removeFromSuperview() }

    init (scrollView: UIScrollView, spacingFromLastCell: CGFloat, spacingFromLastCellWhenLoadMoreActionStart: CGFloat) {
        self.scrollView = scrollView
        self.spacingFromLastCell = spacingFromLastCell
        self.spacingFromLastCellWhenLoadMoreActionStart = spacingFromLastCellWhenLoadMoreActionStart
        let size:CGFloat = 40
        let frame = CGRect(x: (scrollView.frame.width-size)/2, y: scrollView.contentSize.height + spacingFromLastCell, width: size, height: size)
        let activityIndicatorView = UIActivityIndicatorView(frame: frame)
        activityIndicatorView.color = .black
        activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
        activityIndicatorView.hidesWhenStopped = true
        scrollView.addSubview(activityIndicatorView)
        self.activityIndicatorView = activityIndicatorView
    }

    private var isHidden: Bool {
        guard let scrollView = scrollView else { return true }
        return scrollView.contentSize.height < scrollView.frame.size.height
    }

    func start(closure: (() -> Void)?) {
        guard let scrollView = scrollView, let activityIndicatorView = activityIndicatorView else { return }
        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 < scrollView.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()
                    closure?()
                }
            }

            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 = UIEdgeInsets(top: 0, left: 0, bottom: bottom, right: 0)
                        }
                    }
                }
            }
        }
    }

    func stop(completion: (() -> Void)? = nil) {
        guard let scrollView = scrollView , let activityIndicatorView = activityIndicatorView else { return }
        let contentDelta = scrollView.contentSize.height - scrollView.frame.size.height
        let offsetDelta = scrollView.contentOffset.y - contentDelta
        if offsetDelta >= 0 {
            UIView.animate(withDuration: 0.3, animations: {
                scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
            }) { _ in completion?() }
        } else {
            scrollView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)
            completion?()
        }
        activityIndicatorView.stopAnimating()
    }
}

Uso

en eso

activityIndicator = LoadMoreActivityIndicator(scrollView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60)

manejo

extension ViewController: UITableViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        activityIndicator.start {
            DispatchQueue.global(qos: .utility).async {
                sleep(3)
                DispatchQueue.main.async { [weak self] in
                    self?.activityIndicator.stop()
                }
            }
        }
    }
}

Muestra completa

No olvide pegar el código de la solución.

import UIKit

class ViewController: UIViewController {

    fileprivate var activityIndicator: LoadMoreActivityIndicator!

    override func viewDidLoad() {
        super.viewDidLoad()
        let tableView = UITableView(frame: view.frame)
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true

        tableView.dataSource = self
        tableView.delegate = self
        tableView.tableFooterView = UIView()
        activityIndicator = LoadMoreActivityIndicator(scrollView: tableView, spacingFromLastCell: 10, spacingFromLastCellWhenLoadMoreActionStart: 60)
    }
}

extension ViewController: UITableViewDataSource {

    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 30
    }

    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.start {
            DispatchQueue.global(qos: .utility).async {
                for i in 0..<3 {
                    print("!!!!!!!!! \(i)")
                    sleep(1)
                }
                DispatchQueue.main.async { [weak self] in
                    self?.activityIndicator.stop()
                }
            }
        }
    }
}

Resultado

ingrese la descripción de la imagen aquí

Vasily Bodnarchuk
fuente
Funciona perfecto. Pero tengo un encabezado en mi vista de tabla, después de arrastrar para cargar más, el encabezado irá debajo de la barra de navegación .. UIEdgeInsetsMake in loadMoreActionFinshed debe establecerse en (62, 0, 0, 0) considerando 66 = navbar.height + 22
Desmond
Debería funcionar en CollectionView cuando se desplaza verticalmente.
Vasily Bodnarchuk
Increíble ... ¡Genial!
Tà Truhoada
¿Alguna versión objetiva-c de esto?
Syed Ali Salman
@VasilyBodnarchuk no hay problema, lo haré y compartiré aquí para otros
Syed Ali Salman
18
- (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á usando Core Data y NSFetchedResultsController, entonces loadMorepodrí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];
}
samwize
fuente
Estoy tratando de implementar esto pero estoy usando una matriz de resultados y no sqlite, me preguntaba cómo haría para agregar más en el NSMutableArray actual que tengo y luego volver a cargar los datos, porque de lo contrario los datos se sobrescriben ... lo intenté this [nombres addObjectsFromArray: [responseObject valueForKeyPath: @ "nombre"]]; pero no funciona ... aquí hay un enlace a mi pregunta stackoverflow.com/questions/23446780/…
Lion789
1
¿Qué sentido tiene volver a obtener datos cada vez que obtiene nuevos datos? Si frc está configurado correctamente, la recuperación única es suficiente, se actualizará según sea necesario. Obtenerlo cada vez, asumiendo que la solicitud de recuperación de frc está configurada en un contexto de hilo principal bloqueará el hilo principal cuando llega al disco, eso no es nada bueno para la experiencia del usuario cuando el usuario quiere nuevos datos.
MANIAK_dobrii
La primera mitad de esto fue muy útil para mí, gracias. (Sin usar FetchedResultsVC)
weienw
@MANIAK_dobrii es correcto. Una de las características clave de NSFetchedResultsController es que calcula los datos de paginación para que pueda obtener un desplazamiento virtual de forma gratuita cuando lo conecta a un UITableView. La implementación de dicha función loadMore solo debería ser necesaria si realmente está llenando su tienda CoreData con más datos, en cuyo caso no es necesario realizar otro performFetch si su NSFetchedResultsController está configurado correctamente.
Ali Gangji
Mismos problemas que otras respuestas. reloadData hace que esto ocurra varias veces.
dyson regresa el
11

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];
    }
}
Pedro Romão
fuente
Creo que hay diferentes escenarios para ver presentación, en mi caso funcionó solución suya, que necesitaba algo como esto
Raheel Sadiq
Si el usuario lanza con fuerza, es decir, 1,5 pantallas de altura, se puede llegar al fondo sin que se active la actualización.
dyson regresa el
pero desplaza la lista hacia arriba
Mansuu ....
7

Detalles

  • Swift 5.1, Xcode 11.3.1

Solución

Extensión genética UITableView para Loadmore.

agregue esta extensión UITableView + en su nuevo archivo

extension UITableView{

    func indicatorView() -> UIActivityIndicatorView{
        var activityIndicatorView = UIActivityIndicatorView()
        if self.tableFooterView == nil{
            let indicatorFrame = CGRect(x: 0, y: 0, width: self.bounds.width, height: 40)
            activityIndicatorView = UIActivityIndicatorView(frame: indicatorFrame)
            activityIndicatorView.isHidden = false
            activityIndicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin]
            activityIndicatorView.isHidden = true
            self.tableFooterView = activityIndicatorView
            return activityIndicatorView
        }else{
            return activityIndicatorView
        }
    }

    func addLoading(_ indexPath:IndexPath, closure: @escaping (() -> Void)){
        indicatorView().startAnimating()
        if let lastVisibleIndexPath = self.indexPathsForVisibleRows?.last {
            if indexPath == lastVisibleIndexPath && indexPath.row == self.numberOfRows(inSection: 0) - 1 {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    closure()
                }
            }
        }
        indicatorView().isHidden = false
    }

    func stopLoading(){
        indicatorView().stopAnimating()
        indicatorView().isHidden = true
    }
}

Ahora, simplemente agregue la siguiente línea de código en UITableViewDelegate Method willDisplay Cell en su ViewController y asegúrese de que tableView.delegate = self

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    // need to pass your indexpath then it showing your indicator at bottom 
    tableView.addLoading(indexPath) {
        // add your code here
        // append Your array and reload your tableview
        tableView.stopLoading() // stop your indicator
    }
}

Resultado

ingrese la descripción de la imagen aquí

Eso es todo .. Espero que esto sea de ayuda. Gracias

Yogesh Patel
fuente
Cosas para considerar. Simplemente agregue 'tableFooterView = nil' dentro de la función de parada de carga, de lo contrario, el indicador girando no dejará de animarse. También hay una propiedad en el activityIndicator 'hidesWhenStopped' por lo que no es necesario configurar manualmente el indicador oculto verdadero / falso. Pero en general se ve muy bien :)
zramled
1
Gracias por la sugerencia, revisaré una vez y editaré esta respuesta :-)
Yogesh Patel
6

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

Implemente el tableView:willDisplayCell:forRowAtIndexPath:método en su UITableViewDelegatey verifique si es la última fila

Retterdesdialogs
fuente
5

El siguiente enlace proporcionará un código de muestra. # Swift3

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

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

Está en Swift3

https://github.com/yogendrabagoriya/YBTableViewPullData

Yogui
fuente
3

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

class DocumentEventsTableViewController: UITableViewController, UITableViewDataSourcePrefetching {

     var currentPage: Int = 1
     let pageSize: Int = 10 // num of items in one page

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

Para páginas bastante pequeñas (~ 10 elementos), es posible que desee agregar manualmente datos para las páginas 1 y 2 porque nextPage puede estar en algún lugar aproximadamente 1-2 hasta que la tabla tenga algunos elementos para desplazarse bien. Pero funcionará muy bien para todas las páginas siguientes.

Vitalii
fuente
1
Esto funciona solo para datos de solo lectura. No funciona Si tiene una funcionalidad como eliminar algunas filas y cargar más, ya que pageSize se ha corregido aquí y no puede cargar más incluso si hay más datos después de actualizar su fuente.
EI Captain v2.0
2
- (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 este post.

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 llegue a la última fila, muestre la fila y cargue más elementos.

vlad sol
fuente
1

debe verificar ios UITableViewDataSourcePrefetching.

class ViewController: UIViewController {
    @IBOutlet weak var mytableview: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()
        mytableview.prefetchDataSource = self
    }

 func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
        print("prefetchdRowsAtIndexpath \(indexPaths)")
    }

    func tableView(_ tableView: UITableView, cancelPrefetchingForRowsAt indexPaths: [IndexPath]) {
        print("cancelPrefetchingForRowsAtIndexpath \(indexPaths)")
    }


}
Bhavesh.iosDev
fuente
1

para cargar desde una API, funciona para mí, Xcode 10 , swift 4.2 :

1- crea un nuevo archivo Swift y haz esto:

//
//  apiTVCController.swift
//  ApiTestingTableView
//
//  Created by Hooma7n on 4/7/19.
//  Copyright © 2019 Hooma7n. All rights reserved.
//

import Foundation
import Alamofire

class apiget {

    var tableData : [Datum] = []
    var loadin : [Datum] = []
    var testfortotal : Int?


    func getfromapi(completionHandler : ((_ isSucess : Bool) -> Void)?) {
        let url = "https://reqres.in/api/users?page=1"
        Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil)
            .responseJSON(completionHandler : { response in
                switch response.result {
                case .success(let data):
                    guard let jsonData = try? JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions.prettyPrinted) else {return}
                    let decoder = JSONDecoder()
                    guard let result = try? decoder.decode(Welcome.self, from: jsonData) else {return}
                    self.tableData = result.data ?? []
                    self.testfortotal = result.total ?? 0
                    completionHandler?(true)

                //                    print(result)
                case .failure(let error):
                    print(error)
                }
            })
    }

    var pagecounter : Int = 2


    func loadmore(completionHandler : ((_ isSucess : Bool) -> Void)?){

        let url = "https://reqres.in/api/users?page=\(pagecounter)"
        Alamofire.request(url, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: nil)
            .responseJSON(completionHandler : { response in
                switch response.result {
                case .success(let data):
                    guard let jsonData = try? JSONSerialization.data(withJSONObject: data, options: JSONSerialization.WritingOptions.prettyPrinted) else {return}
                    let decoder = JSONDecoder()
                    guard let myresult = try? decoder.decode(Welcome.self, from: jsonData) else {return}
                    self.loadin = myresult.data ?? []
                    self.tableData.append(contentsOf: myresult.data ?? [])
                    completionHandler?(true)
                    print(self.pagecounter)
                    self.pagecounter += 1

                //                    print(myresult)
                case .failure(let error):
                    print(error)
                }
            })

    }

}

extension apiget {

    struct Welcome: Codable {
        let page, perPage, total, totalPages: Int?
        var data: [Datum]?

        enum CodingKeys: String, CodingKey {
            case page
            case perPage = "per_page"
            case total
            case totalPages = "total_pages"
            case data
        }
    }

    struct Datum: Codable {
        let id: Int?
        let firstName, lastName: String?
        let avatar: String?

        enum CodingKeys: String, CodingKey {
            case id
            case firstName = "first_name"
            case lastName = "last_name"
            case avatar
        }
    }


}

2- en su archivo ViewController (tableView Controller):

//
//  apiTVC.swift
//  ApiTestingTableView
//
//  Created by Hooma7n on 4/7/19.
//  Copyright © 2019 Hooma7n. All rights reserved.
//

import UIKit
import Alamofire

class apiTVC: UITableViewController {

    var datamodel = apiget()

    override func viewDidLoad() {
        super.viewDidLoad()

        datamodel.getfromapi(completionHandler: {finish in
            if finish {self.tableView.reloadData()
            }

        })

    }


    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return datamodel.tableData.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! apiTableViewCell
        cell.firstNameLabel.text = datamodel.tableData[indexPath.row].firstName
        cell.lastNameLabel.text = datamodel.tableData[indexPath.row].lastName
        cell.dateLabel.text = "\(datamodel.tableData[indexPath.row].id ?? 0)"
        cell.profileImageView.loadImage(fromURL: datamodel.tableData[indexPath.row].avatar ?? "")

        return cell

    }

    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        let lastElement = datamodel.tableData.count - 1
        let total = datamodel.testfortotal ?? 12
        if indexPath.row == lastElement && datamodel.tableData.count < total{

            datamodel.loadmore(completionHandler: {finish in
                if finish {

                    self.tableView.reloadData()

                }})
        }
    }
}

si usa tableView en su viewController set delegate , datasource self en viewDidLoad.

hooma7n
fuente
0

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(...); la salida sería algo 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 límite de datos a 25, eso significa que uitableview tendrá 0-24 (inicialmente).

Si el usuario se desplazó hasta la parte inferior y la última celda es visible dataLimit, se agregarán 25 ...

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

0yeoj
fuente
0
-(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];
            });
        }
    }
}
}
Sahila Mirajkar
fuente
después de mostrar la última fila, inserte filas, es decir, beginUpdates ... y use un poco de retraso para que no se bloquee.
Sahila Mirajkar
0

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

Rápidamente necesitas agregar esto:

  1. Cree una nueva celda de tipo celda Cargando esto mantendrá el indicador. Mira el código a continuación
  2. Mire el número de filas y agregue 1 (esto es para cargar la celda).
  3. debe verificar en rawAtIndex si 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 nota que mi celda de carga se crea a partir de un archivo nib. Estos videos explicarán lo que hice.

Mohammed Ali Khaled
fuente
0
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
        }
    }
  }
Alok SInha
fuente
0

Para Xcode 10.1, Swift 4.2

¡Este video parece un gran tutorial!

Proyecto inicial / completo: https://github.com/RobCanton/Swift-Infinite-Scrolling-Example

import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    var tableView:UITableView!

    var fetchingMore = false
    var items = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        initTableView()
    }

    func initTableView() {
        tableView = UITableView(frame: view.bounds, style: .plain)
        tableView.register(UITableViewCell.self, forCellReuseIdentifier: "tableCell")
        tableView.delegate = self
        tableView.dataSource = self

        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false

        let layoutGuide = view.safeAreaLayoutGuide
        tableView.leadingAnchor.constraint(equalTo: layoutGuide.leadingAnchor).isActive = true
        tableView.topAnchor.constraint(equalTo: layoutGuide.topAnchor).isActive = true
        tableView.trailingAnchor.constraint(equalTo: layoutGuide.trailingAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: layoutGuide.bottomAnchor).isActive = true

        tableView.reloadData()
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

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

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
            cell.textLabel?.text = "Item \(items[indexPath.row])"
            return cell
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offsetY = scrollView.contentOffset.y
        let contentHeight = scrollView.contentSize.height

        if offsetY > contentHeight - scrollView.frame.height * 4 {
            if !fetchingMore {
                beginBatchFetch()
            }
        }
    }

    func beginBatchFetch() {
        fetchingMore = true
        print("Call API here..")
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.50, execute: {
            print("Consider this as API response.")
            let newItems = (self.items.count...self.items.count + 12).map { index in index }
            self.items.append(contentsOf: newItems)
            self.fetchingMore = false
            self.tableView.reloadData()
        })
    }
}
Shuvo Joseph
fuente