La mejor manera de verificar si UITableViewCell es completamente visible

100

Tengo un UITableView con celdas de diferentes alturas y necesito saber cuándo son completamente visibles o no.

En este momento, estoy recorriendo cada celda en la lista de celdas visibles para verificar si es completamente visible cada vez que se desplaza la vista. ¿Es este el mejor enfoque?

Aquí está mi 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:

Tenga en cuenta que * - (NSArray ) visibleCells devuelve celdas visibles que son tanto completamente visibles como parcialmente visibles.

Edición 2:

Este es el código revisado después de combinar soluciones de lnafziger y 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
fuente
3
Solo como nota al margen, debe ir a todas sus preguntas anteriores y aceptar las respuestas de quienes lo ayudaron.
Baub
4
¡Gracias por decirme! Ya les había dado +1 pero me había olvidado de la función de respuesta aceptada establecida.
RohinNZ
Tu código me parece correcto y, aunque es complicado, funciona. No arregles lo que no está roto, ¿eh?
CodaFi

Respuestas:

125

Puede obtener el rect de una celda con el rectForRowAtIndexPath:método y compararlo con los límites de tableview rect usando la CGRectContainsRectfunción.

Tenga en cuenta que esto no creará una instancia de la celda si no es visible y, por lo tanto, 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);

Por supuesto, esto no considerará que la vista de tabla esté recortada por una supervista u oscurecida por otra vista.

Vadim Yelagin
fuente
¡Gracias! Combiné este código con la solución de lnafziger.
RohinNZ
11
Pensándolo bien, puede ser soloCGRectContainsRect(tableView.bounds, [tableView rectForRowAtIndexPath:indexPath])
Vadim Yelagin
1
¿Por qué el origen de mi cellRect.x = 0, origen.y = 0, ancho = 0, alto = 0? aunque en la interfaz de usuario no son todos 0, estoy usando el diseño automático, ¿alguna idea?
RainCast
7
Swift 3:let completelyVisible = tableView.bounds.contains(tableView.rectForRow(at: indexPath))
cbartel
¿Cómo podemos manejar una funcionalidad similar para UICollectionView?
Satyam
64

Lo cambiaría así:

- (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
fuente
El <y> en la declaración if debe ser <= o> =, corregiré esto en la respuesta ...
Aardvark
12

Puede probar algo como esto para ver cuánto porcentaje es visible:

-(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
fuente
Si la vista de tabla puede mostrar 2 celdas con videos (en iPad, por ejemplo), ¿ambos videos se reproducirán con el código anterior?
Jerome
@Catalin He probado tu solución pero mi vista de tabla se ralentiza. ¿Alguna forma de mejorar el rendimiento del desplazamiento?
Rahul Vyas
@RahulVyas lo siento, pero no :( verifique sus elementos internos, tal vez el diseño se pueda optimizar un poco en lo que respecta a capas / subcapas
Catalin
5

De los documentos:

visibleCells Devuelve las celdas de la tabla que son visibles en el receptor.

- (NSArray *)visibleCells

Valor de retorno Una matriz que contiene objetos UITableViewCell, cada uno de los cuales representa una celda visible en la vista de la tabla receptora.

Disponibilidad Disponible en iOS 2.0 y posterior.

Vea también: indexPathsForVisibleRows

CodaFi
fuente
4
Lo siento, tal vez no fui lo suficientemente claro. Solo me interesan las celdas que son completamente visibles. - (NSArray *) visibleCells e indexPathsForVisibleRows devuelven las celdas que son completamente visibles y parcialmente visibles. Como puede ver en mi código, ya estoy usando - (NSArray *) visibleCells para encontrar las que son completamente visibles. Gracias de todos modos.
RohinNZ
4

Si también desea tener en cuenta el contentInset y no desea depender de una supervista (el marco de la vista de tabla en la supervista podría ser algo más que 0,0), aquí está mi solución:

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
fuente
1
- (BOOL)checkVisibilityOfCell{
    if (tableView.contentSize.height <= tableView.frame.size.height) {
        return YES;
    } else{
        return NO;
    }
}
Naresh Jain
fuente
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
fuente
0

Incluso si dijo que desea comprobarlo cada vez que se desplaza, también puede utilizar

-(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
fuente
0

Tal vez para este problema use mejor la siguiente función de UITableViewDelegate

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
Artyom
fuente
Esto no funcionará. De un documentotells the delegate that the specified cell was removed from the table.
anatoliy_v
0

El siguiente código le permitirá verificar si una celda de vista de colección es completamente visible a través de los atributos de diseño de la vista de colección.

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

fahlout
fuente