Cómo saber cuándo UITableView se desplazó hacia abajo en iPhone

100

Me gustaría saber cuándo se UITableViewdesplazó hacia abajo para cargar y mostrar más contenido, algo como un delegado o algo más para que el controlador sepa cuándo la tabla se desplazó hacia abajo.

¿Alguien sabe esto, por favor ayúdeme, gracias de antemano!

Son Nguyen
fuente

Respuestas:

18

La mejor manera es probar un punto en la parte inferior de la pantalla y usar esta llamada al método siempre que el usuario se desplace ( scrollViewDidScroll):

- (NSIndexPath *)indexPathForRowAtPoint:(CGPoint)point

Pruebe un punto cerca de la parte inferior de la pantalla, y luego, usando indexPath, devuelve, verifique si ese indexPath es la última fila y, si lo es, agregue filas.

Ajay
fuente
Desafortunadamente, mi aplicación actualizará los datos en tiempo real para las filas existentes en esta tabla, por lo que de esta manera puede obtener más contenido cuando la tabla no se desplaza en absoluto.
Son Nguyen
247

en el delegado de tableview, haz algo como esto

ObjC:

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
    CGPoint offset = aScrollView.contentOffset;
    CGRect bounds = aScrollView.bounds;
    CGSize size = aScrollView.contentSize;
    UIEdgeInsets inset = aScrollView.contentInset;
    float y = offset.y + bounds.size.height - inset.bottom;
    float h = size.height;
    // NSLog(@"offset: %f", offset.y);   
    // NSLog(@"content.height: %f", size.height);   
    // NSLog(@"bounds.height: %f", bounds.size.height);   
    // NSLog(@"inset.top: %f", inset.top);   
    // NSLog(@"inset.bottom: %f", inset.bottom);   
    // NSLog(@"pos: %f of %f", y, h);

    float reload_distance = 10;
    if(y > h + reload_distance) {
        NSLog(@"load more rows");
    }
}

Rápido:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let offset = scrollView.contentOffset
    let bounds = scrollView.bounds
    let size = scrollView.contentSize
    let inset = scrollView.contentInset
    let y = offset.y + bounds.size.height - inset.bottom
    let h = size.height
    let reload_distance:CGFloat = 10.0
    if y > (h + reload_distance) {
        print("load more rows")
    }
}
neoneye
fuente
¡Bueno! Simplemente use: y> = h + reload_distance, que en realidad es solo una preocupación cuando reload_distance = 0 (por ejemplo, para rebotes NO).
Chris Prince
4
Este código en "scrollViewDidScroll" se ejecuta todo el tiempo que el usuario mueve su dedo hacia arriba o hacia abajo en la pantalla. Es mejor moverlo debajo de "scrollViewDidEndDecelerating". De esta forma se ejecutará una vez cuando el usuario deje de mover el dedo.
Misha
hmm .. ¿este código todavía funciona? no obteniendo la parte inferior de UITableView para mí. Ojalá supiera el razonamiento detrás de todo el código anterior, así que tal vez pueda solucionarlo
FlowUI. SimpleUITesting.com
offset: 237.000000 content.height: 855.000000 limits.height: 667.000000 inset.top: 64.000000 inset.bottom: 49.000000 pos: 855.000000 of 855.000000 if is get false
Abhishek Thapliyal
62

Neoneyes modificados responden un poco.

Esta respuesta está dirigida a aquellos de ustedes que solo desean que el evento se active una vez por cada vez que suelte el dedo .

Adecuado para cargar más contenido de algún proveedor de contenido (servicio web, datos centrales, etc.). Tenga en cuenta que este enfoque no respeta el tiempo de respuesta de su servicio web.

- (void)scrollViewDidEndDragging:(UIScrollView *)aScrollView
                  willDecelerate:(BOOL)decelerate
{
    CGPoint offset = aScrollView.contentOffset;
    CGRect bounds = aScrollView.bounds;
    CGSize size = aScrollView.contentSize;
    UIEdgeInsets inset = aScrollView.contentInset;
    float y = offset.y + bounds.size.height - inset.bottom;
    float h = size.height;

    float reload_distance = 50;
    if(y > h + reload_distance) {
        NSLog(@"load more rows");
    }
}
Globo ocular
fuente
Esto no funciona. Siempre que el usuario se desplaza hacia abajo, se llama a "cargar más filas". Esto sucede incluso cuando el usuario ya se ha desplazado hasta el final y está esperando que la API devuelva datos.
Dean
2
Dean, la pregunta era sobre saber cuándo la vista de la tabla se desplazaba hacia abajo. Si está obteniendo datos de una API o no, es irrelevante, ¿no?
Eyeball
Pero no hay ningún mensaje que muestre cargar más ... para que el usuario pueda saber que hay más resultados para mostrar.
iPhone 7
Buena respuesta en mi caso funciona si: float reload_distance = 10; if (y> (h - reload_distance)) {NSLog (@ "cargar más filas"); } porque y = 565 y h también es 565
Abhishek Thapliyal
1
Probé la respuesta de Eyeball y @neoneye. Créame o no, se llamó a 'scrollViewDidScroll' varias veces en comparación con 'scrollViewDidEndDragging'. Entonces fue eficiente usar 'scrollViewDidEndDragging' ya que tengo una llamada API.
Vinod Supnekar
39

agregue este método en el UITableViewDelegate:

-(void)scrollViewDidScroll:(UIScrollView *)scrollView
{   
    CGFloat height = scrollView.frame.size.height;

    CGFloat contentYoffset = scrollView.contentOffset.y;

    CGFloat distanceFromBottom = scrollView.contentSize.height - contentYoffset;

    if(distanceFromBottom < height) 
    {
        NSLog(@"end of the table");
    }
}
8suhas
fuente
3
Me gusta esta solución excepto por un pequeño detalle. if (distanceFromBottom <= height)
Mark McCorkle
Esto me ayudó a solucionar mi problema, ¡gracias! una barra de pestañas !! era una barra de pestañas estúpida !!
Dmytro
esto funciona como un encanto, y también es simple, pero estoy enfrentando un pequeño problema como si se estuviera ejecutando dos o tres veces en la última fila. ¿Cómo hacer que se ejecute solo una vez cuando llegue a la última fila de la vista de tabla?
R. Mohan
¡¡Muchas gracias!! Puse este código en scrollViewWillBeginDecelerating y se ejecuta solo una vez cuando se desplaza a la última fila.
Manali
20

Ninguna de las respuestas anteriores me ayudó, así que se me ocurrió esto:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)aScrollView
{   
    NSArray *visibleRows = [self.tableView visibleCells];
    UITableViewCell *lastVisibleCell = [visibleRows lastObject];
    NSIndexPath *path = [self.tableView indexPathForCell:lastVisibleCell];
    if(path.section == lastSection && path.row == lastRow)
    {
        // Do something here
    }
}
Iftach Orr
fuente
¿Puedes explicar de dónde viene "lastSection"?
ainesophaur
esto es para la vista de desplazamiento y no para la tabla
iosMentalist
19

Usar – tableView:willDisplayCell:forRowAtIndexPath:( UITableViewDelegatemétodo)

Simplemente compare indexPathcon los elementos de su matriz de datos (o cualquier fuente de datos que utilice para la vista de tabla) para averiguar si se muestra el último elemento.

Documentos: http://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html#//apple_ref/occ/intfm/UITableViewDelegate/tableView:willDisplayCell:forRowAtIndexPath :

Wolfgang Schreurs
fuente
Luego, si vuelve a cargar los datos de tableView, se quedará atascado en un bucle infinito.
Dielson Sales
14

UITableViewes una subclase de UIScrollViewy se UITableViewDelegateajusta a UIScrollViewDelegate. Por lo tanto, el delegado que adjunte a la vista de tabla obtendrá eventos como scrollViewDidScroll:, y puede llamar a métodos como contentOffseten la vista de tabla para encontrar la posición de desplazamiento.

Anomia
fuente
Sí, eso es lo que estoy buscando, implementando ese delegado y verificando si la posición de desplazamiento es UITableViewScrollPositionBottom, sabré cuando la tabla se desplaza hacia abajo, muchas gracias
Son Nguyen
8
NSLog(@"%f / %f",tableView.contentOffset.y, tableView.contentSize.height - tableView.frame.size.height);

if (tableView.contentOffset.y == tableView.contentSize.height - tableView.frame.size.height)
        [self doSomething];

Agradable y simple

user1641587
fuente
8

en Swiftpuedes hacer algo como esto. La siguiente condición será verdadera cada vez que llegue al final de la vista de tabla

func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
        if indexPath.row+1 == postArray.count {
            println("came to last row")
        }
}
Jay Mayu
fuente
1
el problema es cuando tableview tiene solo 3 celdas, por ejemplo, ¡ println("came to last row")este código se ejecuta!
Mc.Lover
@ Mc.Lover ese fue el problema exacto para mí para el cual el uso de CPU iba al 92%. La respuesta de Neoneye funcionó para mí. También agregué una versión rápida si es necesario.
Anuran Barman
7

Sobre la base de la respuesta de @Jay Mayu, que sentí que era una de las mejores soluciones:

C objetivo

// UITableViewDelegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

// Need to call the service & update the array
if(indexPath.row + 1 == self.sourceArray.count) {
    DLog(@"Displayed the last row!");
  }
}

Swift 2.x

// UITableViewDelegate
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
    if (indexPath.row + 1) == sourceArray.count {
        print("Displayed the last row!")
    }
}
footyapps27
fuente
5

Generalmente uso esto para cargar más datos, cuando la última celda comienza a mostrarse

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
   if (indexPath.row ==  myDataArray.count-1) {
        NSLog(@"load more");
    }
}
Shaik Riyaz
fuente
1
esto está mal. Cada vez que llame a reloadData se realizará una solicitud. Y a veces solo necesitas recargar la mesa.
Patrick Bassut
Querías decir, si estás en la última celda y luego realizaste una recarga de datos, ¿esto sucederá?
Shaik Riyaz
5

Tomando excelentes respuestas de neoneye pero rápidamente, y cambiando el nombre de las variables.

Básicamente, sabemos que hemos llegado al final de la vista de la tabla si yOffsetAtBottomestá más allá de la altura del contenido de la tabla.

func isTableViewScrolledToBottom() -> Bool {
    let tableHeight = tableView.bounds.size.height
    let contentHeight = tableView.contentSize.height
    let insetHeight = tableView.contentInset.bottom

    let yOffset = tableView.contentOffset.y
    let yOffsetAtBottom = yOffset + tableHeight - insetHeight

    return yOffsetAtBottom > contentHeight
}
samwize
fuente
5

Aquí está el código de la versión rápida 3.0.

   func scrollViewDidScroll(_ scrollView: UIScrollView) {

        let offset = scrollView.contentOffset
        let bounds = scrollView.bounds
        let size = scrollView.contentSize
        let inset = scrollView.contentInset
        let y: Float = Float(offset.y) + Float(bounds.size.height) + Float(inset.bottom)
        let height: Float = Float(size.height)
        let distance: Float = 10

        if y > height + distance {
            // load more data
        }
    }
Morshed Alam
fuente
3

Mi solución es agregar celdas antes de que la vista de tabla se desacelere en el desplazamiento estimado. Es predecible por velocidad.

- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)offset {

    NSLog(@"offset: %f", offset->y+scrollView.frame.size.height);
    NSLog(@"Scroll view content size: %f", scrollView.contentSize.height);

    if (offset->y+scrollView.frame.size.height > scrollView.contentSize.height - 300) {
        NSLog(@"Load new rows when reaches 300px before end of content");
        [[DataManager shared] fetchRecordsNextPage];
    }
}
abeto
fuente
3

Actualización para Swift 3

La respuesta de Neoneye funcionó mejor para mí en Objective C, este es el equivalente a la respuesta en Swift 3:

func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
    let offset: CGPoint = scrollView.contentOffset
    let bounds: CGRect = scrollView.bounds
    let size: CGSize = scrollView.contentSize
    let inset: UIEdgeInsets = scrollView.contentInset
    let y: CGFloat = offset.y + bounds.size.height - inset.bottom
    let h: CGFloat = size.height
//        print("offset: %f", offset.y)
//        print("content.height: %f", size.height)
//        print("bounds.height: %f", bounds.size.height)
//        print("inset.top: %f", inset.top)
//        print("inset.bottom: %f", inset.bottom)
//        print("position: %f of %f", y, h)

    let reloadDistance: CGFloat = 10

    if (y > h + reloadDistance) {
            print("load more rows")
    }
}
Ibrahim
fuente
2

Quiero realizar alguna acción en mi 1 celda completa de Tableview.

Entonces el código es vincular el:

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    NSArray* cells = self.tableView.visibleCells;
    NSIndexPath *indexPath = nil ;

    for (int aIntCount = 0; aIntCount < [cells count]; aIntCount++)
    {

        UITableViewCell *cell = [cells objectAtIndex:aIntCount];
CGRect cellRect = [self.tableView convertRect:cell.frame toView:self.tableView.superview];

        if (CGRectContainsRect(self.tableView.frame, cellRect))
        {
            indexPath = [self.tableView indexPathForCell:cell];

    //  remain logic
        }
     }
}

Que esto sea de ayuda para alguien.

Mayuri R Talaviya
fuente
0

La respuesta de @neoneye funcionó para mí. Aquí está la versión Swift 4

    let offset = scrollView.contentOffset
    let bounds = scrollView.bounds
    let size = scrollView.contentSize
    let insets = scrollView.contentInset
    let y = offset.y + bounds.size.height - insets.bottom
    let h = size.height
    let reloadDistance = CGFloat(10)
    if y > h + reloadDistance {
        //load more rows
    }
Barman Anuran
fuente