UICollectionView índice de celda visible actual

115

Lo estoy usando por UICollectionViewprimera vez en mi aplicación para iPad. Lo configuré de UICollectionViewtal manera que su tamaño y el tamaño de celda sean iguales, lo que significa que solo se muestra una celda a la vez.

Problema: ahora, cuando el usuario se desplaza por UICollectionView, necesito saber qué celda está visible, tengo que actualizar otros elementos de la interfaz de usuario al cambiar. No encontré ningún método de delegado para esto. ¿Cómo puedo conseguir esto?

Código:

[self.mainImageCollection setTag:MAIN_IMAGE_COLLECTION_VIEW];
[self.mainImageCollection registerClass:[InspirationMainImageCollectionCell class] forCellWithReuseIdentifier:@"cellIdentifier"];
[self.mainImageFlowLayout setScrollDirection:UICollectionViewScrollDirectionHorizontal];
[self.mainImageFlowLayout setMinimumInteritemSpacing:0.0f];
[self.mainImageFlowLayout setMinimumLineSpacing:0.0f];
self.mainImageFlowLayout.minimumLineSpacing = 0;
[self.mainImageCollection setPagingEnabled:YES];
[self.mainImageCollection setShowsHorizontalScrollIndicator:NO];
[self.mainImageCollection setCollectionViewLayout:self.mainImageFlowLayout];

Lo que he probado:

Como se UICollectionViewajusta a UIScrollView, obtuve cuando el desplazamiento del usuario termina con el UIScrollViewDelegatemétodo

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Pero dentro de la función anterior, ¿cómo puedo obtener el índice de celda visible actual de UICollectionView???

Irfan DANÉS
fuente
self.collectionViewFloors.indexPathsForVisibleItems
Aznix

Respuestas:

130

El método [collectionView visibleCells] le proporciona todas las matrices de visibleCells que desee. Úsalo cuando quieras

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    for (UICollectionViewCell *cell in [self.mainImageCollection visibleCells]) {
        NSIndexPath *indexPath = [self.mainImageCollection indexPathForCell:cell];
        NSLog(@"%@",indexPath);
    }
}

Actualización a Swift 5:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    for cell in yourCollectionView.visibleCells {
        let indexPath = yourCollectionView.indexPath(for: cell)
        print(indexPath)
    }
}
LE SANG
fuente
¿Puede explicarnos de dónde proviene self.mainImageCollection? Muchas gracias de antemano por sus mayores detalles.
Benjamin McFerren
@BenjaminMcFerren es la vista de colección que usó.
LE SANG
10
El scrollViewDidScroll:método delegado proporciona actualizaciones de la vista de desplazamiento cada vez que finaliza un desplazamiento (y probablemente sea una mejor opción aquí). A diferencia del scrollViewDidEndDecelerating:método delegado que solo se llama cuando la vista de desplazamiento "se detiene [desde un gran desplazamiento] " (consulte el encabezado de UIScrollView).
Sam Spencer
1
IIRC, ..DidScrollse llama muchas veces, incluso durante un desplazamiento corto. Puede ser algo bueno o no, dependiendo de lo que uno quiera hacer.
ToolmakerSteve
4
Desde iOS 10, ahora también es posible usar indexPathsForVisibleItems directamente :)
Ben
174

indexPathsForVisibleItemspodría funcionar para la mayoría de las situaciones, pero a veces devuelve una matriz con más de una ruta de índice y puede ser complicado averiguar cuál desea. En esas situaciones, puede hacer algo como esto:

CGRect visibleRect = (CGRect){.origin = self.collectionView.contentOffset, .size = self.collectionView.bounds.size};
CGPoint visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
NSIndexPath *visibleIndexPath = [self.collectionView indexPathForItemAtPoint:visiblePoint];

Esto funciona especialmente bien cuando cada elemento de la vista de colección ocupa toda la pantalla.

Versión rápida

let visibleRect = CGRect(origin: collectionView.contentOffset, size: collectionView.bounds.size)
let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
let visibleIndexPath = collectionView.indexPathForItem(at: visiblePoint)
lobianco
fuente
9
Estoy de acuerdo, a veces indexPathsForVisibleItems devuelve más celdas, incluso si pensamos que no debería haber tal caso. Tu solución funciona muy bien.
MP23
2
Estaba totalmente en este tipo de situación, solo una solución que funcionó para mí (pero mi caso fue con tableview), necesitaba cambiar CGPointMake (CGRectGetMidX (visibleRect), CGRectGetMidY (visibleRect)); para CGPointMake (CGRectGetMidX (visibleRect), CGRectGetMinY (visibleRect));
JRafaelM
1
Excelente, pero si las celdas tienen un espacio entre ellas, a visibleIndexPathveces será nulo, así queif (visibleIndexPath) == nil { let cells = collectionView.visibleCells() let visibleIndexPath = collectionView.indexPathForCell(cells[0] as! UICollectionViewCell)! as NSIndexPath }
Husam
La única solución que funcionó para mis celdas de tamaño de pantalla completa. Se agregó la versión Swift como respuesta a continuación.
Sam Bing
1
Ojalá pudiera votar más a favor de esto. Este problema me ha estado volviendo loco y esta solución me salvó el día.
SeanT
77

Rápido 5 :

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    var visibleRect = CGRect()

    visibleRect.origin = collectionView.contentOffset
    visibleRect.size = collectionView.bounds.size

    let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)

    guard let indexPath = collectionView.indexPathForItem(at: visiblePoint) else { return } 

    print(indexPath)
}

Respuestas de trabajo combinadas en Swift 2.2:

 func scrollViewDidEndDecelerating(scrollView: UIScrollView) {

        var visibleRect = CGRect()

        visibleRect.origin = self.collectionView.contentOffset
        visibleRect.size = self.collectionView.bounds.size

        let visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect))

        let visibleIndexPath: NSIndexPath = self.collectionView.indexPathForItemAtPoint(visiblePoint)

        guard let indexPath = visibleIndexPath else { return } 
        print(indexPath)

    }
Sam Bing
fuente
Obteniendo indexpath dos veces, necesito obtener solo una vez
Arshad Shaik
18

En aras de la integridad, este es el método que terminó funcionando para mí. Fue una combinación de los métodos de @Anthony & @ iAn.

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
      CGRect visibleRect = (CGRect){.origin = self.collectionView.contentOffset, .size = self.collectionView.bounds.size};
      CGPoint visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
      NSIndexPath *visibleIndexPath = [self.collectionView indexPathForItemAtPoint:visiblePoint];
      NSLog(@"%@",visibleIndexPath);
}
Vincil Bishop
fuente
11

Probablemente sea mejor usar los métodos UICollectionViewDelegate: (Swift 3)

// Called before the cell is displayed    
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    print(indexPath.row)
}

// Called when the cell is displayed
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    print(indexPath.row)
}
Sr. Stanev
fuente
3
En cuanto didEndDisplayinga los documentos: le dice al delegado que la celda especificada se eliminó de la vista de colección. Utilice este método para detectar cuándo se elimina una celda de una vista de colección, en lugar de monitorear la vista en sí para ver cuándo desaparece. Entonces no creo que didEndDisplayingse llame cuando se muestra la celda.
Jonny
9

Solo quiero agregar para otros: por alguna razón, no obtuve la celda que era visible para el usuario cuando me desplazaba a la celda anterior en collectionView con pagingEnabled.

Entonces inserto el código dentro de dispatch_async para darle un poco de "aire" y esto funciona para mí.

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
    dispatch_async(dispatch_get_main_queue(), ^{
            UICollectionViewCell * visibleCell= [[self.collectionView visibleCells] objectAtIndex:0];


            [visibleCell doSomthing];
        });
}
usuario1105951
fuente
¡Esto realmente ayudó! ¡No me di cuenta de que puedo usar el bloque dispatch_async para hacerlo absolutamente perfecto! ¡Esta es la mejor respuesta!
kalafun
1
Para Swift 4: DispatchQueue.main.async {cuando la llamada se inicia desde algo comofunc tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
Jonny
Alguna aclaración: esto también me ayudó, y tuve el mismo problema / similar, al desplazarme hacia uitableviewcell que contiene una uicollectionview. La llamada de actualización se inició willDisplay cellcomo se mencionó anteriormente. Creo que el problema, en algún lugar de UIKit, es realmente el mismo en mi caso que esta respuesta.
Jonny
7

Para Swift 3.0

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let visibleRect = CGRect(origin: colView.contentOffset, size: colView.bounds.size)
    let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)
    let indexPath = colView.indexPathForItem(at: visiblePoint)
}
Hiren Panchal
fuente
6

Swift 3.0

La solución más simple que le dará indexPath para celdas visibles.

yourCollectionView.indexPathsForVisibleItems 

devolverá la matriz de indexpath.

Simplemente tome el primer objeto de la matriz como esta.

yourCollectionView.indexPathsForVisibleItems.first

Supongo que también debería funcionar bien con Objective - C.

Anil Kukadeja
fuente
6

UICollectionView índice de celda visible actual: Swift 3

var visibleCurrentCellIndexPath: IndexPath? {
    for cell in self.collectionView.visibleCells {
        let indexPath = self.collectionView.indexPath(for: cell)
        return indexPath
     }

     return nil
}
Mihail Salari
fuente
La mejor respuesta. Limpio y unas líneas.
garanda
1
Respuesta perfecta .. Gracias
Hardik Thakkar
3

convertir la respuesta de @ Anthony a Swift 3.0 funcionó perfectamente para mí:

func scrollViewDidScroll(_ scrollView: UIScrollView) {

    var visibleRect = CGRect()
    visibleRect.origin = yourCollectionView.contentOffset
    visibleRect.size = yourCollectionView.bounds.size
    let visiblePoint = CGPoint(x: CGFloat(visibleRect.midX), y: CGFloat(visibleRect.midY))
    let visibleIndexPath: IndexPath? = yourCollectionView.indexPathForItem(at: visiblePoint)
    print("Visible cell's index is : \(visibleIndexPath?.row)!")
}

fuente
2

Puedes usar scrollViewDidEndDecelerating: para esto

//@property (strong, nonatomic) IBOutlet UICollectionView *collectionView;

   - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{

        for (UICollectionViewCell *cell in [self.collectionView visibleCells]) {
            NSIndexPath *indexPath = [self.collectionView indexPathForCell:cell];
            NSUInteger lastIndex = [indexPath indexAtPosition:[indexPath length] - 1];
            NSLog(@"visible cell value %d",lastIndex);
        }

    }
raaz
fuente
0

Esta es una vieja pregunta pero en mi caso ...

- (void) scrollViewWillBeginDragging:(UIScrollView *)scrollView {

    _m_offsetIdx = [m_cv indexPathForCell:m_cv.visibleCells.firstObject].row;
}

- (void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    _m_offsetIdx = [m_cv indexPathForCell:m_cv.visibleCells.lastObject].row;
}
Hwangho Kim
fuente
0

También revisa este fragmento

let isCellVisible = collectionView.visibleCells.map { collectionView.indexPath(for: $0) }.contains(inspectingIndexPath)
Nik Kov
fuente
0

En este hilo, hay tantas soluciones que funcionan bien si la celda toma pantalla completa pero usan límites de vista de colección y puntos medios de Visible rect.Sin embargo, hay una solución simple para este problema.

    DispatchQueue.main.async {
        let visibleCell = self.collImages.visibleCells.first
        print(self.collImages.indexPath(for: visibleCell))
    }

con esto, puede obtener indexPath de la celda visible. He agregado DispatchQueue porque cuando desliza el dedo más rápido y si por un breve momento se muestra la siguiente celda, sin dispactchQueue obtendrá indexPath de la celda que se muestra brevemente, no la celda que se muestra en la pantalla.

kuldip
fuente
-1

prueba esto, funciona. (en el siguiente ejemplo, tengo 3 celdas, por ejemplo).

    func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
    let visibleRect = CGRect(origin: self.collectionView.contentOffset, size: self.collectionView.bounds.size)
    let visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect))
    let visibleIndexPath = self.collectionView.indexPathForItemAtPoint(visiblePoint)
    if let v = visibleIndexPath {
        switch v.item {
        case 0: setImageDescription()
            break
        case 1: setImageConditions()
            break
        case 2: setImageResults()
            break
        default: break
        }
    }
Arash Jamshidi
fuente
-2

Swift 3 y Swift 4:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
   var visibleRect = CGRect()

   visibleRect.origin = collectionView.contentOffset
   visibleRect.size = collectionView.bounds.size

   let visiblePoint = CGPoint(x: visibleRect.midX, y: visibleRect.midY)

   guard let indexPath = collectionView.indexPathForItem(at: visiblePoint) else { return } 

   print(indexPath[1])
}

Si desea mostrar el número real, puede agregar +1

Jamil Hasnine Tamim
fuente