33

I'm implementing a rich UITableView with customly created UITableViewCell, I show these on the screen in one fashion, but once they go off the screen I want to take a note of that, since the second time they come on I would like them to get displayed in a different manner. Think auto "mark as read" when going off the screen.

I've been looking for some way to detect when a cell goes off the screen (get's deallocated or dequeued or equivalent), preferably in the UITableViewController class to make a quick note of the indexPath.row value, but in the UITableViewCell is equally as good.

I haven't been able to do this in any standard way. Counting the times it appeared seems out of the question as I do multiple reloadData calls on the table.

Anyone any ideas? This seems a bit tricky :)

Cristik
  • 30,989
  • 25
  • 91
  • 127

8 Answers8

76

This is an old question, but in case anyone is looking, in iOS6, a new UITableViewDelegate function was introduced that does just this:

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath

It does a great job at telling you whenever a cell is removed, however, it is very thorough and thus if you did a reload cell, even the old cell that's being replaced will trigger this delegate function. In my implementation I simply check to see if the indexPath passed is still within the array tableView.indexPathsForVisibleRows. Something like:

- (void)tableView:(UITableView *)tableView didEndDisplayingCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
    if ([tableView.indexPathsForVisibleRows indexOfObject:indexPath] == NSNotFound)
    {
        // This indeed is an indexPath no longer visible
        // Do something to this non-visible cell...
    }
}
Mr. T
  • 12,795
  • 5
  • 39
  • 47
  • This is the solution I'm looking for, but for iOS 5.0. Any suggestions? – Stian Høiland Jun 05 '13 at 21:08
  • Unfortunately nothing provided by Apple. What I did was use scrollViewDidScroll and do a quick calculation to see if the visibleCells array has changed since the previous "frame" of the scrollview, in which case I do something to the now missing cell. Hope that helps! – Mr. T Jun 05 '13 at 21:23
  • 3
    Sadly tableView.indexPathsForVisibleRows does not get updated until after didEndDisplayingCell getting called so if you add new cells that will become visible, the old cells will be removed from display but will still be within tableView.indexPathsForVisibleRows. (At least for iOS 7). – Olof_t Nov 19 '13 at 09:49
  • 1
    @Olof_t This does seem to be the case, so I just took out my check against tableView.indexPathsForVisibleRows for my ios 7 app. I also added some code in the corresponding willDisplayCell: method to handle some corner cases. – Mr. T Feb 13 '14 at 23:22
  • I used this for stopping a video playing within a cell because as long as the video kept playing, didEndDisplayingCell never got called. – Eran Goldin May 18 '15 at 10:25
6

I think you could use the

- (NSArray *)visibleCells

method for your UITableView. This returns an array of all cells that are visible. You can then mark any data that is "not visible" (i.e. not in this array) in the way you want, such that when you scroll back to it, it has been updated.

Hope that helps

h4xxr
  • 11,385
  • 1
  • 39
  • 36
5

Once UITableViewCell is invisible, it will be removed from UITableView. You may override the method -(void)removeFromSuperView, and do something within the method. At last, do not forget to call [super removeFromSuperView].

Jake1164
  • 12,291
  • 6
  • 47
  • 64
Feng Stone
  • 77
  • 1
  • 3
  • This doesn't seem to be a reliable solution. I've just tested this on iOS 6.0 simulator, the cells seem to be set to hidden instead of being removed. Oddly enough, `setHidden:YES` is not being called either in my implementation! Apparently in the tableView implementation the ivar is set directly, skipping the property setter method. – de. Jul 12 '13 at 07:55
  • removeFromSuperView method doesn't get called after subclassing UITableViewCell – Asif Bilal Nov 07 '17 at 14:49
2

The prepareForReuse method on UITableViewCell that Andrey Tarantsov mentions looks good. Putting a couple of NSLogs in there allows you to print out the values of any variables of the cell. Any thoughts as to how this could be set back to the table view controller?

Adam Swinden
  • 1,917
  • 2
  • 18
  • 24
1

I know this is a REALLY old question, but in case anyone is looking for an answer for Swift 5:

func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    <#code#>
}
ExeRhythm
  • 452
  • 5
  • 13
1

Are you sure a cell going offscreen is exactly what you want to catch? If you want to mark items as read, this does not seem like a proper way to do it. For example, I might scroll though the table really fast, and I would be very surprised if you marked all of the stuff as read.

As for the technical part, simply keep a list of cells that are on screen (cellForRowAtIndexPath should add cells to that list), and in scrollViewDidScroll delegate method check if any of them are no longer visible.

Another possible idea: I remember there is prepareForReuse method on the cell. Not sure when it is called, though.

Andrey Tarantsov
  • 8,965
  • 7
  • 54
  • 58
0

I needed to get some data from the cell as it was scrolled off of the screen. I used @Mr.T's answer however it doesn't state how to get the data.

Say for example the name of the cell class that I'm using is MyCell and it has a data model in it named MyModel with a property of postId. I initially set that info in cellForItem:

var datasource = [MyModel]()

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! MyCell

    cell.myModel = datasource[indexPath.item] // an individual instance of MyModel from the array

    print("cellForItem - indexPath.item: ", indexPath.item) // if the was the very first cell coming on it would print 0
    print("postId: ", cell.myModel.postId) // maybe the postId is qwerty

    return
}

To get some data from the cell as it is scrolled off of the screen:

func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {

    guard let myCell = cell as? MyCell else { return } // You must cast the cell from the method param to your cell type which for me is MyCell

    print("didEndDisplayingCell - indexPath.item: ", indexPath.item) // if this was the very first cell scrolling off it should print 0
    print("postId: ", myCell.myModel.postId) // the postId should be qwerty
}

The best way to test this is to add a small amount of cells to your collectionView, like first 2 cells, then later on 3 cells, then later on 4 cells. Then just scroll off the very first cell and see what is printed out. Do it for each cell. The indexPath.item and postId should both match for cellForItem and didEndDisplaying.

Lance Samaria
  • 17,576
  • 18
  • 108
  • 256
0

I think I would try periodically checking the indexPathsForVisibleRows property of the UITableView. From the largest index path, you can deduce that all previous rows have been scrolled past.

Chris Lundie
  • 6,023
  • 2
  • 27
  • 28
  • Yes, that would be an option, I could place that call in the cellForRowAtIndexPath. However I have variable height rows, thus this would not guarantee that an off screen cell would be marked as offscreen :| –  May 09 '09 at 19:47