Melhor maneira de verificar se UITableViewCell está completamente visível

100

Tenho um UITableView com células de alturas diferentes e preciso saber quando elas estão totalmente visíveis ou não.

No momento, estou percorrendo cada célula na lista de células visíveis para verificar se ela está completamente visível toda vez que a exibição é rolada. Esta é a melhor abordagem?

Este é o meu código:

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {

    CGPoint offset = aScrollView.contentOffset;
    CGRect bounds = aScrollView.bounds;    
    NSArray* cells = myTableView.visibleCells;

    for (MyCustomUITableViewCell* cell in cells) {

        if (cell.frame.origin.y > offset.y &&
            cell.frame.origin.y + cell.frame.size.height < offset.y + bounds.size.height) {

            [cell notifyCompletelyVisible];
        }
        else {

            [cell notifyNotCompletelyVisible];
        }
    }
}

Editar:

Observe que * - (NSArray ) visibleCells retorna células visíveis que são completamente visíveis e parcialmente visíveis.

Editar 2:

Este é o código revisado após combinar as soluções de ambos lnafziger e Vadim Yelagin :

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView {
    NSArray* cells = myTableView.visibleCells;
    NSArray* indexPaths = myTableView.indexPathsForVisibleRows;

    NSUInteger cellCount = [cells count];

    if (cellCount == 0) return;

    // Check the visibility of the first cell
    [self checkVisibilityOfCell:[cells objectAtIndex:0] forIndexPath:[indexPaths objectAtIndex:0]];

    if (cellCount == 1) return;

    // Check the visibility of the last cell
    [self checkVisibilityOfCell:[cells lastObject] forIndexPath:[indexPaths lastObject]];

    if (cellCount == 2) return;

    // All of the rest of the cells are visible: Loop through the 2nd through n-1 cells
    for (NSUInteger i = 1; i < cellCount - 1; i++)
        [[cells objectAtIndex:i] notifyCellVisibleWithIsCompletelyVisible:YES];
}

- (void)checkVisibilityOfCell:(MultiQuestionTableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath {
    CGRect cellRect = [myTableView rectForRowAtIndexPath:indexPath];
    cellRect = [myTableView convertRect:cellRect toView:myTableView.superview];
    BOOL completelyVisible = CGRectContainsRect(myTableView.frame, cellRect);

    [cell notifyCellVisibleWithIsCompletelyVisible:completelyVisible];
}
RohinNZ
fonte
3
Apenas como uma observação lateral, você deve ir a todas as suas perguntas anteriores e aceitar as respostas daqueles que o ajudaram.
Baub
4
Obrigado por me contar! Eu já tinha dado a eles +1, mas tinha esquecido o recurso definir resposta aceita.
RohinNZ
Seu código parece correto para mim e, embora seja complicado, ele funciona. Não conserte o que não está quebrado, hein?
CodaFi

Respostas:

125

Você pode obter o reto de uma célula com o rectForRowAtIndexPath:método e compará-lo com o ret de limites de tableview usando a CGRectContainsRectfunção.

Observe que isso não irá instanciar a célula se ela não estiver visível e, portanto, será bastante rápido.

Rápido

let cellRect = tableView.rectForRowAtIndexPath(indexPath)
let completelyVisible = tableView.bounds.contains(cellRect)

Obj-C

CGRect cellRect = [tableView rectForRowAtIndexPath:indexPath];
BOOL completelyVisible = CGRectContainsRect(tableView.bounds, cellRect);

É claro que isso não considerará a visualização da tabela sendo cortada por uma supervisão ou obscurecida por outra visualização.

Vadim Yelagin
fonte
Obrigado! Combinei esse código com a solução de lnafziger.
RohinNZ
11
Pensando bem, pode ser apenasCGRectContainsRect(tableView.bounds, [tableView rectForRowAtIndexPath:indexPath])
Vadim Yelagin
1
por que origin.x = 0, origin.y = 0, width = 0, height = 0 do meu cellRect? embora na IU nem todos sejam 0s, estou usando o layout automático, alguma ideia?
RainCast de
7
Swift 3:let completelyVisible = tableView.bounds.contains(tableView.rectForRow(at: indexPath))
cbartel
Como podemos lidar com uma funcionalidade semelhante para UICollectionView?
Satyam de
64

Eu mudaria assim:

- (void)checkVisibilityOfCell:(MyCustomUITableViewCell *)cell inScrollView:(UIScrollView *)aScrollView {
    CGRect cellRect = [aScrollView convertRect:cell.frame toView:aScrollView.superview];

    if (CGRectContainsRect(aScrollView.frame, cellRect))
        [cell notifyCompletelyVisible];
    else
        [cell notifyNotCompletelyVisible];
}

- (void)scrollViewDidScroll:(UIScrollView *)aScrollView { 
    NSArray* cells = myTableView.visibleCells;

    NSUInteger cellCount = [cells count];
    if (cellCount == 0)
        return;

    // Check the visibility of the first cell
    [self checkVisibilityOfCell:[cells firstObject] inScrollView:aScrollView];
    if (cellCount == 1)
        return;

    // Check the visibility of the last cell
    [self checkVisibilityOfCell:[cells lastObject] inScrollView:aScrollView];
    if (cellCount == 2)
        return;

    // All of the rest of the cells are visible: Loop through the 2nd through n-1 cells
    for (NSUInteger i = 1; i < cellCount - 1; i++)
        [[cells objectAtIndex:i] notifyCompletelyVisible];
}
lnafziger
fonte
O <e> na instrução if deve ser <= ou> =, corrigirei isso na resposta ...
Aardvark
12

Você pode tentar algo assim para ver quanta porcentagem está visível:

-(void)scrollViewDidScroll:(UIScrollView *)sender
{
    [self checkWhichVideoToEnable];
}

-(void)checkWhichVideoToEnable
{
    for(UITableViewCell *cell in [tblMessages visibleCells])
    {
        if([cell isKindOfClass:[VideoMessageCell class]])
        {
            NSIndexPath *indexPath = [tblMessages indexPathForCell:cell];
            CGRect cellRect = [tblMessages rectForRowAtIndexPath:indexPath];
            UIView *superview = tblMessages.superview;

            CGRect convertedRect=[tblMessages convertRect:cellRect toView:superview];
            CGRect intersect = CGRectIntersection(tblMessages.frame, convertedRect);
            float visibleHeight = CGRectGetHeight(intersect);

            if(visibleHeight>VIDEO_CELL_SIZE*0.6) // only if 60% of the cell is visible
            {
                // unmute the video if we can see at least half of the cell
                [((VideoMessageCell*)cell) muteVideo:!btnMuteVideos.selected];
            }
            else
            {
                // mute the other video cells that are not visible
                [((VideoMessageCell*)cell) muteVideo:YES];
            }
        }
    }
}
Catalin
fonte
Se a table view é capaz de exibir 2 células com vídeos (no iPad, por exemplo), ambos os vídeos serão reproduzidos com o código acima?
Jerome
@Catalin Tentei sua solução, mas meu tableview fica lento. Há alguma maneira de melhorar o desempenho da rolagem?
Rahul Vyas
@RahulVyas, desculpe, mas não :( verifique seus elementos internos talvez o layout possa ser otimizado um pouco no que diz respeito a camadas / subcamadas
Catalin
5

Dos documentos:

visibleCells Retorna as células da tabela que são visíveis no receptor.

- (NSArray *)visibleCells

Valor de retorno Uma matriz contendo objetos UITableViewCell, cada um representando uma célula visível na exibição da tabela de recebimento.

Disponibilidade Disponível no iOS 2.0 e posterior.

Consulte também - indexPathsForVisibleRows

CodaFi
fonte
4
Desculpe, talvez eu não tenha sido claro o suficiente. Estou interessado apenas nas células que são completamente visíveis. - (NSArray *) visibleCells e indexPathsForVisibleRows retornam as células que são completamente visíveis e parcialmente visíveis. Como você pode ver no meu código, eu já estou usando - (NSArray *) visibleCells para encontrar aqueles que estão completamente visíveis. Obrigado de qualquer maneira.
RohinNZ
4

Se você também quiser levar o contentInset em conta e não quiser depender de uma supervisão (o quadro de visão da tabela em superview pode ser diferente de 0,0), aqui está minha solução:

extension UITableView {

    public var boundsWithoutInset: CGRect {
        var boundsWithoutInset = bounds
        boundsWithoutInset.origin.y += contentInset.top
        boundsWithoutInset.size.height -= contentInset.top + contentInset.bottom
        return boundsWithoutInset
    }

    public func isRowCompletelyVisible(at indexPath: IndexPath) -> Bool {
        let rect = rectForRow(at: indexPath)
        return boundsWithoutInset.contains(rect)
    }
}
Kukosk
fonte
1
- (BOOL)checkVisibilityOfCell{
    if (tableView.contentSize.height <= tableView.frame.size.height) {
        return YES;
    } else{
        return NO;
    }
}
Naresh Jain
fonte
0
UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath];
CGRect frame = cell.frame;
if (CGRectContainsRect(CGRectOffset(self.collectionView.frame, self.collectionView.contentOffset.x, self.collectionView.contentOffset.y), frame))
{
    // is on screen
}
Andy Poes
fonte
0

Mesmo se você disser que deseja verificá-lo sempre que rolar, você também pode usar

-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
       CGRect cellRect = [tableView convertRect:cell.frame toView:tableView.superview];
       if (CGRectContainsRect(tableView.frame, cellRect)){
            //Do things in case cell is fully displayed
        }

}
iRestMyCaseYourHonor
fonte
0

Talvez para este problema seja melhor usada a próxima função de UITableViewDelegate

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
Artyom
fonte
Isso não vai funcionar. De um doctells the delegate that the specified cell was removed from the table.
anatoliy_v
0

O código a seguir permitirá que você verifique se uma célula de visualização de coleção está completamente visível através dos atributos de layout da visualização de coleção.

guard let cellRect = collectionView.layoutAttributesForItem(at: indexPath)?.frame else { return } let isCellCompletelyVisible = collectionView.bounds.contains(cellRect)

Fahlout
fonte