Meilleur moyen de vérifier si UITableViewCell est complètement visible

100

J'ai un UITableView avec des cellules de différentes hauteurs et j'ai besoin de savoir quand elles sont complètement visibles ou non.

En ce moment, je parcourt chaque cellule de la liste des cellules visibles pour vérifier si elle est complètement visible à chaque fois que la vue défile. Est-ce la meilleure approche?

Voici mon code:

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

Éditer:

Veuillez noter que * - (NSArray ) visibleCells renvoie des cellules visibles qui sont à la fois complètement visibles et partiellement visibles.

Modifier 2:

Voici le code révisé après avoir combiné les solutions de lnafziger et de 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
la source
3
En guise de remarque, vous devriez passer à toutes vos questions précédentes et accepter les réponses de ceux qui vous ont aidé.
Baub
4
Merci de me le dire! Je leur avais déjà donné +1 mais j'avais oublié la fonction de réponse acceptée définie.
RohinNZ
Votre code me semble correct, et bien qu'il soit compliqué, cela fonctionne. Ne répare pas ce qui n'est pas cassé, hein?
CodaFi

Réponses:

125

Vous pouvez obtenir le rect d'une cellule avec rectForRowAtIndexPath:method et le comparer avec les limites rect de tableview en utilisant CGRectContainsRectfunction.

Notez que cela n'instanciera pas la cellule si elle n'est pas visible, et donc sera plutôt rapide.

Rapide

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

Obj-C

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

Bien sûr, cela ne considérera pas que la vue de la table soit coupée par une vue supervisée ou obscurcie par une autre vue.

Vadim Yelagin
la source
Je vous remercie! J'ai combiné ce code avec la solution de lnafziger.
RohinNZ
11
À la seconde pensée, cela peut être justeCGRectContainsRect(tableView.bounds, [tableView rectForRowAtIndexPath:indexPath])
Vadim Yelagin
1
pourquoi est-ce que l'origine.x = 0 de mon CellRect, origin.y = 0, largeur = 0, hauteur = 0? bien que sur l'interface utilisateur ils ne soient pas tous des 0, j'utilise la mise en page automatique, des idées?
RainCast
7
Swift 3:let completelyVisible = tableView.bounds.contains(tableView.rectForRow(at: indexPath))
cbartel
Comment pouvons-nous gérer des fonctionnalités similaires pour UICollectionView?
Satyam
64

Je le changerais comme ceci:

- (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
la source
Le <et> dans l'instruction if doit être <= ou> =, je vais corriger cela dans la réponse ...
Aardvark
12

Vous pouvez essayer quelque chose comme ceci pour voir combien de pourcentage est 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
la source
Si la vue du tableau est capable d'afficher 2 cellules avec des vidéos (sur iPad par exemple), les deux vidéos seront lues avec le code ci-dessus?
Jerome
@Catalin J'ai essayé votre solution mais ma tableview ralentit. Un moyen d'améliorer les performances de défilement?
Rahul Vyas
@RahulVyas désolé mais non: (vérifiez vos éléments intérieurs peut-être que la mise en page peut être un peu optimisée en ce qui concerne les couches / sous
Catalin
5

À partir de la documentation:

visibleCells Renvoie les cellules du tableau visibles dans le récepteur.

- (NSArray *)visibleCells

Valeur de retour Un tableau contenant des objets UITableViewCell, chacun représentant une cellule visible dans la vue de table de réception.

Disponibilité Disponible dans iOS 2.0 et versions ultérieures.

Voir aussi - indexPathsForVisibleRows

CodaFi
la source
4
Désolé peut-être que je n'ai pas été assez clair. Je ne suis intéressé que par les cellules qui sont complètement visibles. - (NSArray *) visibleCells et indexPathsForVisibleRows renvoient tous les deux les cellules qui sont complètement visibles et partiellement visibles. Comme vous pouvez le voir dans mon code, j'utilise déjà - (NSArray *) visibleCells pour trouver celles qui sont complètement visibles. Merci quand même.
RohinNZ
4

Si vous souhaitez également prendre en compte contentInset, et que vous ne voulez pas vous fier à un superview (le cadre de vue table dans superview peut être autre chose que 0,0), voici ma solution:

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

Même si vous avez dit vouloir le vérifier à chaque fois que vous faites défiler, vous pouvez également utiliser

-(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
la source
0

Peut-être pour ce problème, mieux utiliser la fonction suivante de UITableViewDelegate

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)
Artyom
la source
Cela ne fonctionnera pas. From a doctells the delegate that the specified cell was removed from the table.
anatoliy_v
0

Le code ci-dessous vous permettra de vérifier si une cellule de vue de collection est complètement visible à travers les attributs de mise en page de la vue de collection.

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

fahlout
la source