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

我有一个 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 )可见细胞返回完全可见和部分可见的可见单元格。

编辑2:

这是合并了 Lnafziger瓦迪姆 · 叶拉金的解决方案后修订的代码:

- (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];
}
61435 次浏览

From the docs:

visibleCells Returns the table cells that are visible in the receiver.

- (NSArray *)visibleCells

Return Value An array containing UITableViewCell objects, each representing a visible cell in the receiving table view.

Availability Available in iOS 2.0 and later.

See Also – indexPathsForVisibleRows

I would change it like this:

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

You can get the rect of a cell with rectForRowAtIndexPath: method and compare it with tableview's bounds rect using CGRectContainsRect function.

Note that this will not instantiate the cell if it is not visible, and thus will be rather fast.

Swift

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

Obj-C

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

Of course this will not regard the table view being clipped by a superview or obscured by another view.

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
}

You can try something like this to see how much percentage is 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];
}
}
}
}
- (BOOL)checkVisibilityOfCell{
if (tableView.contentSize.height <= tableView.frame.size.height) {
return YES;
} else{
return NO;
}
}

Even if you said you want to check it every time you scrolled, you can also use

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


}

Maybe for this issue better used next function from UITableViewDelegate

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

If you also want to take the contentInset into account, and don't want to rely on a superview (the table view frame in superview could be something else than 0,0), here's my 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)
}
}

The code below will let you check if a collection view cell is completely visible through the layout attributes of the collection view.

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

Swift 5+

we can use

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)