UICollectionView rolar automaticamente para a célula em IndexPath


101

Antes de carregar a visualização da coleção, o usuário define o número da imagem na matriz da visualização da coleção. Todas as células não cabem na tela. Tenho 30 células e apenas 6 na tela.

A questão: como rolar para a célula com a imagem desejada automaticamente no carregamento do UICollectionView?

Respostas:


90

Descobri que a rolagem viewWillAppearpode não funcionar de maneira confiável porque a visualização da coleção ainda não terminou seu layout; você pode rolar para o item errado.

Também descobri que rolar para dentro viewDidAppearfará com que um flash momentâneo da exibição não rolada fique visível.

E, se você rolar toda vez viewDidLayoutSubviews, o usuário não conseguirá rolar manualmente porque alguns layouts de coleção causam um layout de subvisualização toda vez que você rola.

Aqui está o que eu descobri que funciona de maneira confiável:

Objetivo C:

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

}

Boa dica. Vou enfatizar o uso de um initialScrollDonesinalizador como LenK fez, uma vez que este método será chamado mais de uma vez e se você chamar scrollToItemAtIndexPath: mais de uma vez, parece tornar a collectionView não rolável (simulador iOS iPhone 5 / iOS 8)
maz

1
Funciona muito bem no iOS8. Deve ser a resposta aceita.
Nick

5
Isso não funcionou para mim no iOS 8.3. Tive que ligar [self.collectionView layoutIfNeeded];antes, o que também funciona quando você faz isso por dentro viewDidLoad.
Brent Dillingham

1
Esta definitivamente deveria ser a resposta aceita. A resposta acima me deu um flash quando a primeira célula foi mostrada e depois atualizada com a minha nova célula, este método muda perfeitamente.
simon_smiley de

isso também funciona no iOS 9 e também não atrapalha meu layout automático.
Sr. Zystem

168

Resposta nova e editada:

Adicione isto em 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, .centerVerticallyé o caso

ObjC

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

Resposta antiga funcionando para iOS mais antigo

Adicione isto em viewWillAppear:

[self.view layoutIfNeeded];
[self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredVertically animated:NO];

4
É exatamente isso que estou tentando usar. Eu tenho o número do objeto na matriz e sei o nome desse objeto. Mas não sei como colocar indexPath correto ... Alguma idéia?
AlexanderZ

4
Você pode criar facilmente indexPath:[NSIndexPath indexPathForItem:numberOfTheObject inSection:sectionNumber]
boweidmann

3
Teve alguns erros. Mas então coloquei todo o código mencionado dentro de (void) viewDidLayoutSubviews {} e agora está funcionando bem. Thanx Bogdan.
AlexanderZ

1
Como posso 'ampliar' a célula e mostrá-la ampliada?
fatuhoku

9
Isso não funciona de maneira confiável no iOS 7.1 e pode fazer com que a exibição da coleção apareça como uma tela preta. Eu também não queria manter o estado como a solução em viewDidLayoutSubviews abaixo. O que eu fiz foi simplesmente executar um [self.view layoutIfNeeded] logo antes da chamada para scrollToItemAtIndexPath
Daniel Galasko

15

Eu concordo totalmente com a resposta acima. A única coisa é que para mim a solução foi definir o código em viewDidAppear

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

Quando usei o viewWillAppear a rolagem não funcionou.


2
viewDidAppear tem a desvantagem de você ver a visão da coleção movendo / piscando, a visão já apareceu (como o nome indica) ... Eu realmente me pergunto por que a Apple não projetou este caso não tão incomum de uma forma mais agradável, a forma viewDidLayoutSubviews é um pouco estranho
TheEye

12

isso pareceu funcionar para mim, é semelhante a outra resposta, mas tem algumas diferenças distintas:

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

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)

Posição de rolagem:

UICollectionViewScrollPosition.CenteredHorizontally / UICollectionViewScrollPosition.CenteredVertically

9

Você pode usar o GCD para despachar a rolagem para a próxima iteração do loop de execução principal em viewDidLoad para atingir esse comportamento. A rolagem será realizada antes que a visualização da coleção seja mostrada na tela, portanto, não haverá piscar.

- (void)viewDidLoad {
    dispatch_async (dispatch_get_main_queue (), ^{
        NSIndexPath *indexPath = YOUR_DESIRED_INDEXPATH;
        [self.collectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
    });
}

como obter indexpath quando a posição é 3?
kemdo

5

Eu recomendaria fazer isso em collectionView: willDisplay:Então, você pode ter certeza de que o delegado de exibição de coleção oferece algo. Aqui meu exemplo:

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

1
Descobri que usar didEndDisplaying, em vez de willDisplay, era a maneira de fazer isso funcionar corretamente. willDisplay funcionou, desde que o celular estivesse na tela; no entanto, se a célula ainda não estava na tela, esse método não me rolou até ela. didEndDisplaying funcionou perfeitamente para esse caso de uso. Agradeço sua resposta me colocando no caminho certo!
C6Silver

2

Como alternativa ao mencionado acima. Chamada após o carregamento de dados:

Rápido

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

1

Todas as respostas aqui - hacks. Eu encontrei uma maneira melhor de rolar a exibição de coleção em algum lugar após relaodData:
Layout de exibição de coleção de subclasse qualquer que seja o layout que você usar, sobrescrever o método prepareLayout, depois de super chamar definir o deslocamento de que você precisa.
por exemplo: https://stackoverflow.com/a/34192787/1400119

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.