Desplazamiento automático de UICollectionView a la celda en IndexPath

101

Antes de cargar la vista de colección, el usuario establece el número de imágenes en la matriz de vista de colección. Todas las celdas no caben en la pantalla. Tengo 30 celdas y solo 6 en la pantalla.

La pregunta: ¿Cómo desplazarse a la celda con la imagen deseada automáticamente al cargar UICollectionView?

AlexanderZ
fuente

Respuestas:

90

Descubrí que el desplazamiento viewWillAppearpuede no funcionar de manera confiable porque la vista de colección aún no ha terminado su diseño; puede desplazarse al elemento incorrecto.

También descubrí que el desplazamiento hacia adentro viewDidAppearhará que se vea un destello momentáneo de la vista sin desplazamiento.

Y, si se desplaza cada vez viewDidLayoutSubviews, el usuario no podrá desplazarse manualmente porque algunos diseños de colección provocan un diseño de subvista cada vez que se desplaza.

Esto es lo que encontré que funciona de manera confiable:

C objetivo :

- (void)viewDidLayoutSubviews {
    [super viewDidLayoutSubviews];

    // If we haven't done the initial scroll, do it once.
    if (!self.initialScrollDone) {
        self.initialScrollDone = YES;

        [self.collectionView scrollToItemAtIndexPath:self.myInitialIndexPath
                                    atScrollPosition:UICollectionViewScrollPositionRight animated:NO];
    }
}

Rápido:

 override func viewWillLayoutSubviews() {

    super.viewWillLayoutSubviews()

      if (!self.initialScrollDone) {

         self.initialScrollDone = true
         self.testNameCollectionView.scrollToItem(at:selectedIndexPath, at: .centeredHorizontally, animated: true)
    }

}
LenK
fuente
Buen consejo. Enfatizaré el uso de una initialScrollDonebandera como lo ha hecho LenK, ya que este método se llamará más de una vez y si llama a scrollToItemAtIndexPath: más de una vez parece hacer que collectionView no se pueda desplazar (simulador de iOS iPhone 5 / iOS 8)
maz
1
Funciona muy bien en iOS8. Debería ser la respuesta aceptada.
Nick
5
Esto no funcionó para mí en iOS 8.3. Tuve que llamar [self.collectionView layoutIfNeeded];antes, lo que también funciona cuando haces esto dentro viewDidLoad.
Brent Dillingham
1
Esta definitivamente debería ser la respuesta aceptada. La respuesta anterior me dio un destello cuando se mostró la primera celda y luego se actualizó con mi nueva celda, este método cambia sin problemas.
simon_smiley
esto también funciona en iOS 9 y tampoco estropea mi diseño automático.
Mr. Zystem
168

Respuesta nueva y editada:

Agregar esto en viewDidLayoutSubviews

RÁPIDO

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let indexPath = IndexPath(item: 12, section: 0)
    self.collectionView.scrollToItem(at: indexPath, at: [.centeredVertically, .centeredHorizontally], animated: true)
}

Normalmente, .centerVerticallyes el caso

ObjC

-(void)viewDidLayoutSubviews {
   [super viewDidLayoutSubviews];
    NSIndexPath *indexPath = [NSIndexPath indexPathForItem:12 inSection:0];
   [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically | UICollectionViewScrollPositionCenteredHorizontally animated:NO];
}

La respuesta anterior funciona para iOS anterior

Agregue esto en viewWillAppear:

[self.view layoutIfNeeded];
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];
Boweidmann
fuente
4
Eso es exactamente lo que estoy tratando de usar. Tengo el número del objeto en la matriz y sé el nombre de ese objeto. Pero no sé cómo poner indexPath correcto ... ¿Alguna idea?
AlexanderZ
4
Puede crear fácilmente indexPath:[NSIndexPath indexPathForItem:numberOfTheObject inSection:sectionNumber]
boweidmann
3
Tuve algunos errores. Pero luego puse todo el código mencionado dentro (void) viewDidLayoutSubviews {} y ahora está funcionando bien. Gracias Bogdan.
AlexanderZ
1
¿Cómo puedo hacer zoom en la celda y mostrarla ampliada?
fatuhoku
9
Esto no funciona de manera confiable en iOS 7.1 y puede hacer que la vista de colección aparezca como una pantalla negra. Tampoco quería mantener el estado como la solución en viewDidLayoutSubviews a continuación. Lo que hice fue simplemente realizar un [self.view layoutIfNeeded] justo antes de la llamada a scrollToItemAtIndexPath
Daniel Galasko
15

Estoy totalmente de acuerdo con la respuesta anterior. Lo único es que para mí la solución fue configurar el código en viewDidAppear

viewDidAppear 
{
    [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:YES];
}

Cuando usé viewWillAppear, el desplazamiento no funcionó.

Raistlin
fuente
2
viewDidAppear tiene la desventaja de que ves la vista de colección moviéndose / parpadeando, la vista ya apareció (como su nombre lo indica) ... Realmente me pregunto por qué Apple no ha diseñado este caso no tan inusual de una manera más agradable, la forma viewDidLayoutSubviews es un poco incómodo
TheEye
12

esto pareció funcionar para mí, es similar a otra respuesta pero tiene algunas diferencias claras:

- (void)viewDidLayoutSubviews
{     
   [self.collectionView layoutIfNeeded];
   NSArray *visibleItems = [self.collectionView indexPathsForVisibleItems];
   NSIndexPath *currentItem = [visibleItems objectAtIndex:0];
   NSIndexPath *nextItem = [NSIndexPath indexPathForItem:someInt inSection:currentItem.section];

   [self.collectionView scrollToItemAtIndexPath:nextItem atScrollPosition:UICollectionViewScrollPositionNone animated:YES];
}
invernadero
fuente
12

Rápido:

your_CollectionView.scrollToItemAtIndexPath(indexPath, atScrollPosition: UICollectionViewScrollPosition.CenteredHorizontally, animated: true)

Swift 3

let indexPath = IndexPath(row: itemIndex, section: sectionIndex)

collectionView.scrollToItem(at: indexPath, at: UICollectionViewScrollPosition.right, animated: true)

Posición de desplazamiento:

UICollectionViewScrollPosition.CenteredHorizontally / UICollectionViewScrollPosition.CenteredVertically
Rajesh Loganathan
fuente
9

Puede usar GCD para enviar el desplazamiento a la siguiente iteración del ciclo de ejecución principal en viewDidLoad para lograr este comportamiento. El desplazamiento se realizará antes de que se muestre la vista de colección en la pantalla, por lo que no habrá parpadeo.

- (void)viewDidLoad {
    dispatch_async (dispatch_get_main_queue (), ^{
        NSIndexPath *indexPath = YOUR_DESIRED_INDEXPATH;
        [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
    });
}
Tianyu Wang
fuente
¿Cómo obtener indexpath cuando la posición es 3?
kemdo
5

Recomendaría hacerlo en collectionView: willDisplay:Entonces, puede estar seguro de que el delegado de vista de colección ofrece algo. Aquí mi ejemplo:

func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    /// this is done to set the index on start to 1 to have at index 0 the last image to have a infinity loop
    if !didScrollToSecondIndex {
        self.scrollToItem(at: IndexPath(row: 1, section: 0), at: .left, animated: false)
        didScrollToSecondIndex = true
    }
}
Eike
fuente
1
Descubrí que usar didEndDisplaying, en lugar de willDisplay, era la forma de hacer que esto funcionara correctamente. willDisplay estuvo bien mientras la celda estuviera en la pantalla; sin embargo, si la celda aún no estaba en la pantalla, este método no me desplazaba hasta ella. didEndDisplaying aunque funcionó perfectamente para ese caso de uso. ¡Aprecio tu respuesta poniéndome en el camino correcto!
C6Silver
2

Como alternativa a lo mencionado anteriormente. Llamar después de la carga de datos:

Rápido

collectionView.reloadData()
collectionView.layoutIfNeeded()
collectionView.selectItem(at: indexPath, animated: true, scrollPosition: .right)
nCod3d
fuente
1

Todas las respuestas aquí: trucos. Encontré una mejor manera de desplazar la vista de colección en algún lugar después de relaodData:
diseño de vista de colección de subclase cualquiera que sea el diseño que use, método de anulación prepareLayout, después de la super llamada establezca el desplazamiento que necesite.
ej .: https://stackoverflow.com/a/34192787/1400119

tramposo77777
fuente