检查UITableViewCell是否完全可见的最佳方法

105

我有一个UITableView,其中包含高度不同的单元格,并且我需要知道它们何时完全可见。

目前,每次视图滚动时,我都会循环遍历可见单元格列表中的每个单元格,以检查它是否完全可见。这是最佳方法吗?

这是我的代码:

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

编辑:

请注意,* - (NSArray )visibleCells 返回的是完全可见和部分可见的可见单元格。

编辑2:

在lnafziger和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];
}

3
作为一则旁注,您应该去查看之前所有的问题,并接受那些帮助过您的答案。 - Baub
4
谢谢告诉我!我已经给他们点了+1,但是忘记了设置采纳答案的功能。 - RohinNZ
你的代码看起来对我来说是正确的,虽然它很复杂,但它能够工作。不要修复没有问题的东西,好吗? - CodaFi
这是来自@matt的一篇非常棒的答案,还附带了一个动画 https://stackoverflow.com/a/68109661/4833705 - Lance Samaria
12个回答

139

你可以使用 rectForRowAtIndexPath: 方法获取单元格的矩形区域,然后使用 CGRectContainsRect 函数与表视图的边界矩形进行比较。

请注意,如果单元格不可见,则不会实例化它,因此速度相当快。

Swift

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

Obj-C

的翻译是:

Obj-C

CGRect cellRect = [tableView rectForRowAtIndexPath:indexPath];
BOOL completelyVisible = CGRectContainsRect(tableView.bounds, cellRect);
当然,这不会考虑到表格视图被父视图裁剪或被其他视图遮挡的情况。

11
再仔细想想,它可能只是 CGRectContainsRect(tableView.bounds, [tableView rectForRowAtIndexPath:indexPath]) - Vadim Yelagin
1
为什么我的cellRect的origin.x = 0,origin.y = 0,width = 0,height = 0?虽然在UI上它们不全是0,但我正在使用自动布局,有什么想法吗? - RainCast
7
Swift 3:let completelyVisible = tableView.bounds.contains(tableView.rectForRow(at: indexPath)) - cbartel
我们如何处理UICollectionView的类似功能? - Satyam
有关collectionView的任何解决方案吗?谢谢。 - famfamfam
显示剩余2条评论

65

我会像这样进行更改:

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

在 if 语句中,< 和 > 应该改为 <= 或 >=,我会在答案中进行更正... - Aardvark

15

你可以尝试像这样来查看可见百分比:

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

如果表视图能够在iPad上显示2个带有视频的单元格,那么以上代码会同时播放这两个视频吗? - Jerome
@Catalin,我尝试了你的解决方案,但我的表格视图变得很慢。有什么方法可以提高滚动性能吗? - Rahul Vyas
@RahulVyas 很抱歉,不行 :( 请检查您的内部元素,也许布局可以优化一下,因为涉及到图层/子图层。 - Catalin

7
如果您还想考虑contentInset,并且不想依赖于父视图(表格视图在父视图中的位置可能与0,0不同),那么这是我的解决方案:
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)
    }
}

5

文档中提到:

visibleCells 返回接收器中可见的表格单元格。

- (NSArray *)visibleCells

返回值是一个数组,包含 UITableViewCell 对象,每个对象代表接收者表视图中的一个可见单元格。

可用性:iOS 2.0 及以上版本。

另请参阅 - indexPathsForVisibleRows


4
抱歉,也许我没有表达清楚。我只对完全可见的单元格感兴趣。-(NSArray *)visibleCells和indexPathsForVisibleRows都返回完全可见和部分可见的单元格。正如您在我的代码中所看到的,我已经使用-(NSArray *)visibleCells来查找完全可见的单元格。不管怎样,还是谢谢。 - RohinNZ

1

Swift 5+

我们可以使用

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
       ...
    }

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    ...
}


let cellRect = tableView.rectForRow(at: indexPath)
let completelyVisible = tableView.bounds.contains(cellRect)

在表视图中,didEndDisplaying可能会在单元格变得可见之前被调用。 - Ivan Tkachenko

1
以下代码将让您通过集合视图的布局属性检查集合视图单元格是否完全可见。
保留html标签:

以下代码将让您通过集合视图的布局属性检查集合视图单元格是否完全可见。

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


0
也许针对这个问题,最好使用UITableViewDelegate的下一个函数。
func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath)

这样做不起作用。从文档中,“告诉委托人指定的单元格已从表格中删除。” - anatoliy_v

0
即使您说您想每次滚动时都检查它,您也可以使用


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

}

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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接