60

Is there any way to know if a tableview cell is currently visible? I have a tableview whose first cell(0) is a uisearchbar. If a search is not active, then hide cell 0 via an offset. When the table only has a few rows, the row 0 is visible. How to determine if row 0 is visible or is the top row?

Jim B
  • 2,267
  • 6
  • 24
  • 26

11 Answers11

104

UITableView has an instance method called indexPathsForVisibleRows that will return an NSArray of NSIndexPath objects for each row in the table which are currently visible. You could check this method with whatever frequency you need to and check for the proper row. For instance, if tableView is a reference to your table, the following method would tell you whether or not row 0 is on screen:

-(BOOL)isRowZeroVisible {
  NSArray *indexes = [tableView indexPathsForVisibleRows];
  for (NSIndexPath *index in indexes) {
    if (index.row == 0) {
      return YES;
    }
  }

  return NO;
}

Because the UITableView method returns the NSIndexPath, you can just as easily extend this to look for sections, or row/section combinations.

This is more useful to you than the visibleCells method, which returns an array of table cell objects. Table cell objects get recycled, so in large tables they will ultimately have no simple correlation back to your data source.

MarmiK
  • 5,639
  • 6
  • 40
  • 49
devunwired
  • 62,780
  • 12
  • 127
  • 139
  • thank you! it was very helpful, but it should be "indexPathsForVisibleRows" not "indexPathsForvisibleRows" – mklfarha Mar 18 '11 at 00:10
  • Thanks for this answer.Star is missing. "for (NSIndexPath *index in indexes) {" – S.P. Jun 27 '11 at 14:55
  • 5
    @Devunwired I have a followup question. What if I want to check if a row is **fully** visible. Your code is fine except it also return YES if the row is only partially visible. Thanks! – pixelfreak Oct 02 '11 at 00:20
  • 8
    Note that instead of using a `for` statement to loop thru all the indexes in the array, if you are looking for the row of index 0 of section 0, it is probably easier to directly write `BOOL isRowZeroVisible = [[tableView indexPathsForVisibleRows] containsObject:[NSIndexPath indexPathWithRow:0 inSection:0]];` – AliSoftware Oct 02 '11 at 21:15
  • 1
    in reference to @AliSoftware's comment, it should now say indexPath*For*Row – mkc842 Feb 06 '14 at 22:45
  • 1
    @mkc842 so what is the correct way to check if cell is completely visible? – Deepak Khiwani Aug 26 '14 at 07:33
  • https://developer.apple.com/reference/uikit/uitableview/1614885-indexpathsforvisiblerows – Subin K Kuriakose Dec 27 '16 at 11:12
  • Note that in viewWillAppear, you might get false positives from `indexPathsForVisibleRows` as layout is in progress – xaphod Mar 31 '17 at 01:15
  • if anyone wants to check the cell type instead of the row, use `cell isKindOfClass:[YourClassCell class]` – Jason Sep 19 '19 at 00:43
51

To checking tableview cell is visible or not use this code of line

    if(![tableView.indexPathsForVisibleRows containsObject:newIndexPath])
    {
       // your code 
    }

here newIndexPath is IndexPath of checking cell.....

Swift 3.0

if !(tableView.indexPathsForVisibleRows?.contains(newIndexPath)) {
  // Your code here

}
Antony Raphel
  • 2,036
  • 2
  • 25
  • 45
Anand Mishra
  • 1,093
  • 12
  • 15
17

I use this in Swift 3.0

extension UITableView {

    /// Check if cell at the specific section and row is visible
    /// - Parameters:
    /// - section: an Int reprenseting a UITableView section
    /// - row: and Int representing a UITableView row
    /// - Returns: True if cell at section and row is visible, False otherwise
    func isCellVisible(section:Int, row: Int) -> Bool {
        guard let indexes = self.indexPathsForVisibleRows else {
            return false
        }
        return indexes.contains {$0.section == section && $0.row == row }
    }  }
Luke Rogers
  • 2,369
  • 21
  • 28
Emil
  • 205
  • 2
  • 7
6

Swift Version:

if let indices = tableView.indexPathsForVisibleRows {
    for index in indices {
        if index.row == 0 {
            return true
        }
    }
}
return false
ems305
  • 1,030
  • 11
  • 7
4

Another solution (which can also be used to check if a row is fully visible) would be to check if the frame for the row is inside the visible rect of the tableview.

In the following code, ip represents the NSIndexPath:

CGRect cellFrame = [tableView rectForRowAtIndexPath:ip];
if (cellFrame.origin.y<tableView.contentOffset.y) { // the row is above visible rect
  [tableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionTop animated:NO];
}
else if(cellFrame.origin.y+cellFrame.size.height>tableView.contentOffset.y+tableView.frame.size.height-tableView.contentInset.top-tableView.contentInset.bottom){ // the row is below visible rect
  [tableView scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionBottom animated:NO];
}

Also using cellForRowAtIndexPath: should work, since it returns a nil object if the row is not visible:

if([tableView cellForRowAtIndexPath:ip]==nil){
  // row is not visible
}
alex-i
  • 5,406
  • 2
  • 36
  • 56
3

Swift:

Improved @Emil answer

extension UITableView {
    
    func isCellVisible(indexPath: IndexPath) -> Bool {
        guard let indexes = self.indexPathsForVisibleRows else {
            return false
        }
        return indexes.contains(indexPath)
    }
}
Juan Boero
  • 6,281
  • 1
  • 44
  • 62
2

Note that "Somewhat visible" is "visible". Also, in viewWillAppear, you might get false positives from indexPathsForVisibleRows as layout is in progress, and if you're looking at the last row, even calling layoutIfNeeded() won't help you for tables. You'll want to check things in/after viewDidAppear.

This swift 3.1 code will disable scroll if the last row is fully visible. Call it in/after viewDidAppear.

    let numRows = tableView.numberOfRows(inSection: 0) // this can't be in viewWillAppear because the table's frame isn't set to proper height even after layoutIfNeeded()
    let lastRowRect = tableView.rectForRow(at: IndexPath.init(row: numRows-1, section: 0))
    if lastRowRect.origin.y + lastRowRect.size.height > tableView.frame.size.height {
        tableView.isScrollEnabled = true
    } else {
        tableView.isScrollEnabled = false
    }
xaphod
  • 6,392
  • 2
  • 37
  • 45
1

IOS 4:

NSArray *cellsVisible = [tableView indexPathsForVisibleRows];
NSUInteger idx = NSNotFound;
idx = [cellsVisible indexOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop)
{
    return ([(NSIndexPath *)obj compare:indexPath] == NSOrderedSame);
}];

if (idx == NSNotFound)
{
sth
  • 222,467
  • 53
  • 283
  • 367
CSmith
  • 13,318
  • 3
  • 39
  • 42
0

Best to check by Frame

let rectOfCellInSuperview = yourTblView.convert(yourCell.frame, to: yourTblView.superview!) //make sure your tableview has Superview
if !yourTblView.frame.contains(rectOfCellInSuperview) {
       //Your cell is not visible
}
Zaporozhchenko Oleksandr
  • 4,660
  • 3
  • 26
  • 48
iOS Lifee
  • 2,091
  • 23
  • 32
0

Swift 2023 simple solution, for a given index path:

tableView.indexPathsForVisibleItems.contains(cellIndexPath)
Xys
  • 8,486
  • 2
  • 38
  • 56
0

For me the problem was that indexPathsForVisibleRows doesn't take tableView insets into account. So for example if you change insets on keyboard appearance, rows covered by keyboard still are considered visible. So I had to do this:

func isRowVisible(at indexPath: IndexPath, isVisibleFully: Bool = false) -> Bool {
    let cellRect = tableView.rectForRow(at: indexPath)
    let cellY = isVisibleFully ? cellRect.maxY : cellRect.minY

    return cellY > tableView.minVisibleY && cellY < tableView.maxVisibleY
}

And this is UITableView extension:

extension UITableView {

    var minVisibleY: CGFloat {
        contentOffset.y + contentInset.top
    }

    var maxVisibleY: CGFloat {
        contentOffset.y + bounds.height - contentInset.bottom
    }
}
Shalugin
  • 1,092
  • 2
  • 10
  • 15