5

Given a UITableView with a single visible cell at any given time, how can I determine which cell is most in view while the table view is being scrolled?

I know I can get an array of visible cells by doing this:

NSArray *paths = [tableView indexPathsForVisibleRows];

And then get the last cell (or first, or whatever) by doing:

UITableViewCell* cell = (UITableViewCell*)[tableView cellForRowAtIndexPath:[paths lastObject]];

But how to I compare all the visible cells and determine which of them is most in view?

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
brandonscript
  • 68,675
  • 32
  • 163
  • 220

6 Answers6

5

The following logic would get you the most visible cell at the end of the scroll:

-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {

    CGRect visibleRect = (CGRect){.origin = self.tableView.contentOffset, .size = self.tableView.bounds.size};
    CGPoint visiblePoint = CGPointMake(CGRectGetMidX(visibleRect), CGRectGetMidY(visibleRect));
    NSIndexPath *visibleIndexPath = [self.tableView indexPathForRowAtPoint:visiblePoint];

}
Sebyddd
  • 4,305
  • 2
  • 39
  • 43
3

The algorithm is different depending on how many paths you get back:

  • If there is only one path, that's the most visible cell right there
  • If there are three or more paths, any of the cells in the middle (i.e. all cells except the first and the last ones) are equally visible
  • If there are exactly two cells, find the position of the line that separates the two in their parent view*, and compute two distances - top-to-middle and middle-to-bottom. If top-to-middle is greater, then the top cell is most visible. If middle-to-bottom is greater, then the second cell is more visible. Otherwise, the two cells are equally visible.

* Midpoint position is the bottom of the second cell. Top and bottom positions are the top and bottom of the table view.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • This might actually make the most sense, since if only one cell can be visible when touch is released, maximum of two should ever be visible when scrolling. – brandonscript Dec 11 '14 at 04:00
2

Swift solution based on @Sebyddd's answer:

func scrollViewDidEndDecelerating(scrollView: UIScrollView) {
  scrollToMostVisibleCell()
}

func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) {
  if !decelerate{
    scrollToMostVisibleCell()
  }
}

func scrollToMostVisibleCell(){
  let visibleRect = CGRect(origin: tableView.contentOffset, size: tableView.bounds.size)
  let visiblePoint = CGPoint(x: CGRectGetMidX(visibleRect), y: CGRectGetMidY(visibleRect))
  let visibleIndexPath: NSIndexPath = tableView.indexPathForRowAtPoint(visiblePoint)!

  tableView.scrollToRowAtIndexPath(visibleIndexPath, atScrollPosition: .Top, animated: true)
}
budiDino
  • 13,044
  • 8
  • 95
  • 91
1

You can use the table view's rectForRowAtIndexPath: to get the frame of each visible cell, then offset them (with CGRectOffset) by -contentOffset.y to account for scrolling, then intersect them with the table view's bounds to find out how much each cell is visible inside the table view.

Khanh Nguyen
  • 11,112
  • 10
  • 52
  • 65
0

The below logic will give you the UITableViewCell which is most visible or closet to center in UITableView every time as soon as user stops scrolling. Hope this logic would help somebody.

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
{
    if (!decelerate)
    {
        if (isScrollingStart)
        {
            isScrollingStart=NO;
            isScrollingEnd=YES;
            [self scrollingStopped];
        }
    }
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{

    if (isScrollingStart)
    {
        isScrollingStart=NO;
        isScrollingEnd=YES;
        [self scrollingStopped];
    }
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView
{
    isScrollingStart=YES;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView
{
    isScrollingStart=YES;
}
-(void)scrollingStopped
{
    NSMutableArray* arrVideoCells=[NSMutableArray array];
    NSLog(@"Scrolling stopped");
    NSArray* arrVisibleCells=[self.tableTimeline visibleCells];
    for (TimeLineCell* cell in arrVisibleCells)
    {
        if ([cell isKindOfClass:[TimeLineCellMediaVideo class]])
        {
            [arrVideoCells addObject:cell];
        }
    }

    TimeLineCellMediaVideo* videoCell=[self getCellNearCenterOfScreen:arrVideoCells];

}
-(TimeLineCellMediaVideo*)getCellNearCenterOfScreen:(NSMutableArray*)arrCells
{
    TimeLineCellMediaVideo* closetCellToCenter;
    CGRect filterCGRect;
    for (TimeLineCellMediaVideo* videoCell in arrCells)
    {
        if (arrCells.count==1)
            closetCellToCenter= videoCell;


        NSIndexPath* cellIndexPath=[self.tableTimeline indexPathForCell:videoCell];
        CGRect rect = [self.tableTimeline convertRect:[self.tableTimeline rectForRowAtIndexPath:cellIndexPath] toView:[self.tableTimeline superview]];
        if (closetCellToCenter)
        {

            CGRect intersect = CGRectIntersection(self.tableTimeline.frame, filterCGRect);
            float visibleHeightFilterCell = CGRectGetHeight(intersect);

            intersect = CGRectIntersection(self.tableTimeline.frame, rect);
            float visibleHeightCurrentCell = CGRectGetHeight(intersect);
            if (visibleHeightCurrentCell>visibleHeightFilterCell)
            {
                filterCGRect=rect;
                closetCellToCenter= videoCell;
            }

        }
        else
        {
            closetCellToCenter=videoCell;
            filterCGRect=rect;

        }
    }
    return closetCellToCenter;
}
Pandey_Laxman
  • 3,889
  • 2
  • 21
  • 39
0

I did the following to find indexPath for most visible cell and it is working correctly.

func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
            guard let tableView = scrollView as? UITableView else {
                return
            }
            let visibleHeights = tableView.visibleCells.compactMap { cell -> (indexPath: IndexPath, visibleHeight: CGFloat)? in
                guard let indexPath = tableView.indexPath(for: cell) else {
                    return nil
                }
                let cellRect = tableView.rectForRow(at: indexPath)
                let superView = tableView.superview
                let convertedRect = tableView.convert(cellRect, to: superView)
                let intersection = tableView.frame.intersection(convertedRect)
                let visibleHeight = intersection.height
                return (indexPath, visibleHeight)
            }

            guard let maxVisibleIndexPath = visibleHeights.max(by: { $0.visibleHeight < $1.visibleHeight })?.indexPath else {
                return
            }
            print("maxVisibleIndexPath: \(maxVisibleIndexPath)")

        }
Harish saini
  • 125
  • 1
  • 4