51

Is the following post still the accepted way of detecting when an instance of UITableView has scrolled to the bottom [in Swift], or has it been altered (as in: improved) since?

Problem detecting if UITableView has scrolled to the bottom

Thank you.

Community
  • 1
  • 1
Joseph Beuys' Mum
  • 2,395
  • 2
  • 24
  • 50

6 Answers6

109

Try this:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let height = scrollView.frame.size.height
    let contentYOffset = scrollView.contentOffset.y
    let distanceFromBottom = scrollView.contentSize.height - contentYOffset

    if distanceFromBottom < height {
        print("You reached end of the table")
    }
}

or you can try this way:

if tableView.contentOffset.y >= (tableView.contentSize.height - tableView.frame.size.height) {    
    /// you reached the end of the table
}
aheze
  • 24,434
  • 8
  • 68
  • 125
Anbu.Karthik
  • 82,064
  • 23
  • 174
  • 143
  • 10
    Thanks @Anbu.Karthik. I may well do. I am *appalled* that such primitive methods still have to be used in 2016, but so be it. What's preventing the engineers from introducing a "func scrollViewDidScrollToBottom" function? – Joseph Beuys' Mum Aug 18 '16 at 10:43
  • @TheMotherofJosephBeuys - I am not get your point , is there any method available in iOS `func scrollViewDidScrollToBottom" ` – Anbu.Karthik Aug 18 '16 at 10:46
  • 3
    Don't worry @Anbu.Karthik, I'm just moaning. – Joseph Beuys' Mum Aug 18 '16 at 10:56
  • @DmitryKanunnikoff - may I know the reason for thanks – Anbu.Karthik Sep 15 '17 at 04:17
  • 1
    @Anbu.Karthik You answer helped me to determine table's scroll (end of table). It is the reason, I think. – DmitryKanunnikoff Sep 15 '17 at 19:51
  • This is a good solution but ... bare in mind it will probably fire before the tableview is loaded specially if the loading process is async ... just control it ... – Marcos Sep 05 '18 at 18:29
  • 1
    This solution doesn't work if you have a header in the table. – drewster Feb 06 '19 at 18:10
  • 3
    I had to change the if condition to `if distanceFromBottom <= height` to get this to work nicely. – shocking Oct 02 '19 at 05:23
  • UPD: if you don't want to catch `true` when your scroll/collectoin does initialise first time, add this check to not emit incorrect result `guard scrollView.contentSize.height > 0 else { return }` as first line of the function for the `func scrollViewDidScroll(_ scrollView: UIScrollView)` – hamsternik Jan 21 '21 at 19:33
  • This "you reached end of the table" gets printed multiple times. How to fix this – Human Jun 24 '21 at 14:46
  • lol @JosephBeuys'Mum. Your comment seems funny now in 2021. 2016 seems so ancient. Like the stone ages of computing :-) – clearlight May 07 '22 at 01:17
  • worked for me with tableview and collection view combination – KrishnDip Oct 19 '22 at 10:58
  • This very old answer is wrong. See 2023 answers. – Fattie Feb 01 '23 at 18:38
61

Swift 3

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if indexPath.row + 1 == yourArray.count {
        print("do something")
    }
}
Giang
  • 3,553
  • 30
  • 28
  • 14
    This solution is only valid when there is 1 section. When there are more sections, then the rows in the left hand side need to be accumulated. – kjoelbro Jul 13 '17 at 19:01
  • 2
    This seems like the best answer to me – Dew Time Aug 15 '17 at 00:48
  • 1
    @kjoelbro You got the entire `indexPath` handy. You can easily determine where is the end of your tableview – Fangming Sep 25 '17 at 13:15
  • 4
    `if indexPath.section == COUNT_OF_SECTIONS - 1 { if indexPath.row + 1 == LAST_DATA_SET.count { print("do something")` – Tim Nuwin Sep 18 '18 at 21:53
  • It works, but it is kind of ugly.. or it doesn't look right. We would need something like `didDisplay cell`, but unfortunately there is nothing like that – DeepBlue May 23 '19 at 16:26
  • For me this is called when the tableview is loaded, even without any scrolling... – RJB Jun 11 '20 at 16:43
  • 2
    If you have few cells then the print statement is called when the table is initially loading the data. Definitely not recommended – Kaunteya Jul 22 '20 at 09:01
  • How on earth this is an acceptable answer? – Ankur Lahiry Jun 18 '22 at 18:27
12

In Swift 4

func scrollViewDidScroll(_ scrollView: UIScrollView) {
  let isReachingEnd = scrollView.contentOffset.y >= 0
      && scrollView.contentOffset.y >= (scrollView.contentSize.height - scrollView.frame.size.height)
}

If you implement expandable UITableView/UICollectionView, you may need to check scrollView.contentSize.height >= scrollView.frame.size.height

onmyway133
  • 45,645
  • 31
  • 257
  • 263
  • This is the correct answer.`willDisplay cell` method is triggered before than scrollView, indeed if you scroll to top your tableView, meanwhile this method is called and you could be wrong results if you implement multi page scroll to the end (scroll to top but willDisplay is called before and load more pages..) – Alessandro Ornano Oct 06 '22 at 05:49
10

We can avoid using scrollViewDidScroll and use tableView:willDisplayCell

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if indexPath.section == tableView.numberOfSections - 1 &&
        indexPath.row == tableView.numberOfRows(inSection: indexPath.section) - 1 {
        // Notify interested parties that end has been reached
    }
}

This should work for any number of sections.

JPetric
  • 3,838
  • 28
  • 26
  • 11
    I believe the tableView calls this method for every cell it loads initially. E.g. if there are 26 cells and it first loads 13, it will call this 13 times upon the first reloadData() call. Thus, if you have 13 cells and it loads 13, it will call willDisplay and you will get a false positive saying that the user reached the bottom. I believe... someone confirm please – Joshua Wolff Jul 24 '19 at 01:05
1

The actual formula:

let maxContent =
  table.contentSize.height - table.bounds.height + table.contentInset.bottom

and then ...

if table.contentOffset.y < -maxContent {
   // the table is now "pulling" (bouncing) as the user
   // moves their finger upwards more than is possible
}

Note however:

https://stackoverflow.com/a/71350599/294884

For very dynamic tables contentSize can be tricky and realtime.

Fattie
  • 27,874
  • 70
  • 431
  • 719
0

You can use the indexPathsForVisibleRows or indexPathForRow(at: CGPoint), but I prefer indexPathsForRows(in: CGRect) because it lets you pretty cleanly incorporate the content insets (in case the tableView is partially covered).

var data: [String] = ... // Your data displayed in tableView
...

guard !data.isEmpty else {
    return
}
let lastIndexPath = IndexPath(row: data.count - 1, section: 0)
let topInset = tableView.contentInset.top
let bottomInset = tableView.contentInset.bottom
let visibleRect = tableView.bounds
    .inset(by: .init(top: topInset, left: 0, bottom: bottomInset, right: 0))
let visibilePaths = tableView.indexPathsForRows(in: visibleRect) ?? []
if visiblePaths.contains(lastIndexPath) {
    // isScrolledToBottom
 }

The topInset is not needed, I just added it for sake of completion so the visibleArea is actually the visible area. If you have more than 1 section, you'll have to calculate the lastIndexPath appropriately.

MrKew
  • 333
  • 1
  • 14