UICollectionView índice de célula visível atual

115

Estou usando UICollectionViewpela primeira vez em meu aplicativo para iPad. Eu defini de UICollectionViewforma que seu tamanho e o tamanho da célula sejam iguais, significa que apenas uma vez a célula é exibida por vez.

Problema: agora, quando o usuário rola UICollectionView, preciso saber qual célula está visível, preciso atualizar outros elementos da IU na mudança. Não encontrei nenhum método delegado para isso. Como posso conseguir isso?

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

O que eu tentei:

Em UICollectionViewconformidade com UIScrollView, eu descobri quando a rolagem do usuário termina com o UIScrollViewDelegatemétodo

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView

Mas dentro da função acima, como posso obter o índice de célula visível atual de UICollectionView???

Irfan DINAMARCA
fonte
self.collectionViewFloors.indexPathsForVisibleItems
Aznix

Respostas:

130

O método [collectionView visibleCells] fornece todos os array visibleCells que você deseja. Use-o quando quiser

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

Atualização para Swift 5:

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
    for cell in yourCollectionView.visibleCells {
        let indexPath = yourCollectionView.indexPath(for: cell)
        print(indexPath)
    }
}
LE SANG
fonte
Você pode explicar de onde vem self.mainImageCollection? Muito obrigado antecipadamente por seus maiores detalhes.
Benjamin McFerren 01 de
@BenjaminMcFerren é a visão da coleção que você usou.
LE SANG
10
O scrollViewDidScroll:método delegate fornece atualizações de visualização de rolagem sempre que qualquer rolagem termina (e provavelmente é uma escolha melhor aqui). Ao contrário do scrollViewDidEndDecelerating:método delegado, que só é chamado quando a visualização de rolagem " é interrompida [a partir de uma grande rolagem] " (consulte o cabeçalho UIScrollView).
Sam Spencer
1
IIRC, ..DidScrollé chamado muitas vezes, mesmo durante uma curta rolagem. Pode ou não ser uma coisa boa, dependendo do que se queira fazer.
Toolmaker Steve
4
Desde o iOS 10 agora também é possível usar indexPathsForVisibleItems diretamente :)
Ben
174

indexPathsForVisibleItemspode funcionar para a maioria das situações, mas às vezes retorna uma matriz com mais de um caminho de índice e pode ser complicado descobrir o que você deseja. Nessas situações, você pode fazer algo assim:

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

Isso funciona especialmente bem quando cada item em sua exibição de coleção ocupa a tela inteira.

Versão 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
fonte
9
Posso concordar, às vezes indexPathsForVisibleItems retorna mais células, mesmo que pensemos que não deveria haver tal caso. Sua solução funciona muito bem.
MP23
2
Eu estava totalmente nesse tipo de situação, apenas uma correção que funcionou para mim (mas meu caso foi com tableview), precisei trocar o CGPointMake (CGRectGetMidX (visibleRect), CGRectGetMidY (visibleRect)); para CGPointMake (CGRectGetMidX (visibleRect), CGRectGetMinY (visibleRect));
JRafaelM
1
Excelente, mas se as células tiverem um espaço entre elas, visibleIndexPathàs vezes será nulo, Entãoif (visibleIndexPath) == nil { let cells = collectionView.visibleCells() let visibleIndexPath = collectionView.indexPathForCell(cells[0] as! UICollectionViewCell)! as NSIndexPath }
Husam
A única solução que funcionou para minhas células de tela inteira. Adicionada a versão Swift como uma resposta abaixo.
Sam Bing
1
Eu gostaria de poder votar mais a favor disso. Esse problema tem me deixado louco e essa solução salvou o dia.
SeanT
77

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

Trabalhando as respostas combinadas em 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
fonte
Obtendo indexpath duas vezes, preciso obter apenas uma vez
Arshad Shaik
18

Para completar, esse é o método que acabou funcionando para mim. Foi uma combinação dos métodos de @Anthony e @ 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
fonte
11

Provavelmente será melhor usar os 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
fonte
3
A respeito didEndDisplaying, dos documentos: Diz ao delegado que a célula especificada foi removida da visualização da coleção. Use este método para detectar quando uma célula é removida de uma visualização de coleção, em vez de monitorar a visualização em si para ver quando ela desaparece. Portanto, não acho que didEndDisplayingseja chamado quando o celular for exibido.
Jonny
9

Só quero adicionar para outros: por algum motivo, não recebi a célula que estava visível para o usuário quando estava rolando para a célula anterior em collectionView com pagingEnabled.

Então, eu insiro o código dentro do dispatch_async para dar um pouco de "ar" e isso funciona para mim.

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


            [visibleCell doSomthing];
        });
}
user1105951
fonte
Isso realmente ajudou! Não percebi que posso usar o bloco dispatch_async para torná-lo absolutamente perfeito! Esta é a melhor resposta!
kalafun de
1
Para Swift 4: DispatchQueue.main.async {quando a chamada é iniciada de algo comofunc tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
Jonny
Alguns esclarecimentos: isso também me ajudou, e eu tive o mesmo / semelhante problema, ao rolar de volta para uitableviewcell contendo um uicollectionview. A chamada de atualização foi iniciada willDisplay cellconforme mencionado acima. Eu acho que o problema, em algum lugar no UIKit, é realmente o mesmo no meu caso que esta resposta.
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
fonte
6

Swift 3.0

A solução mais simples que fornecerá indexPath para células visíveis.

yourCollectionView.indexPathsForVisibleItems 

retornará a matriz de indexpath.

Apenas pegue o primeiro objeto do array assim.

yourCollectionView.indexPathsForVisibleItems.first

Eu acho que deve funcionar bem com Objective-C também.

Anil Kukadeja
fonte
6

UICollectionView índice de célula visível atual: Swift 3

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

     return nil
}
Mihail Salari
fonte
Melhor resposta. Limpo e algumas linhas.
garanda 01 de
1
Resposta perfeita. Obrigado
Hardik Thakkar
3

converter a resposta de @ Anthony para Swift 3.0 funcionou perfeitamente para mim:

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)!")
}

fonte
2

Você pode usar scrollViewDidEndDecelerating: para isso

//@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
fonte
0

Essa é uma questão antiga, mas no meu 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
fonte
0

Verifique também este snippet

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

Neste tópico, existem tantas soluções que funcionam bem se a célula ficar em tela cheia, mas elas usam limites de visualização de coleção e pontos médios de retângulo visível. No entanto, há uma solução simples para este problema

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

com isso, você pode obter indexPath da célula visível. Eu adicionei DispatchQueue porque quando você desliza mais rápido e se por um breve momento a próxima célula é mostrada, então sem dispactchQueue você obterá indexPath da célula mostrada brevemente, não da célula que está sendo exibida na tela.

kuldip
fonte
-1

tente fazer isso, funciona. (no exemplo abaixo eu tenho 3 células, por exemplo).

    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
fonte
-2

Swift 3 e 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])
}

Se você quiser mostrar o número real, pode adicionar +1

Jamil Hasnine Tamim
fonte