UITableView
iOS development
scroll detection
Swift programming
iPhone app development

How to know when UITableView did scroll to bottom in iPhone?

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Detecting when a UITableView reaches the bottom is a common requirement for infinite scrolling, analytics, and lazy loading. The usual solution is to watch the table view's scroll position and compare the visible bottom edge to the total content height.

The detail that trips people up is that "bottom" is rarely an exact equality check. Content insets, safe area adjustments, bouncing, and dynamic cell heights mean you should trigger with a threshold instead of waiting for a mathematically perfect last pixel.

Detect Bottom in scrollViewDidScroll

Because UITableView is a subclass of UIScrollView, bottom detection is usually implemented in the scroll view delegate.

swift
1import UIKit
2
3final class FeedViewController: UIViewController, UITableViewDelegate {
4    @IBOutlet private weak var tableView: UITableView!
5
6    private var isLoadingNextPage = false
7
8    func scrollViewDidScroll(_ scrollView: UIScrollView) {
9        guard scrollView === tableView else { return }
10
11        let offsetY = scrollView.contentOffset.y
12        let visibleHeight = scrollView.bounds.height
13            - scrollView.adjustedContentInset.top
14            - scrollView.adjustedContentInset.bottom
15        let contentHeight = scrollView.contentSize.height
16        let threshold: CGFloat = 120
17
18        let distanceFromBottom = contentHeight - (offsetY + visibleHeight)
19        if distanceFromBottom <= threshold && !isLoadingNextPage {
20            loadNextPage()
21        }
22    }
23
24    private func loadNextPage() {
25        isLoadingNextPage = true
26        print("Load more rows")
27    }
28}

The threshold lets you start loading before the user hits the absolute end. That usually feels better than waiting until the table is fully exhausted.

Reset Loading State Correctly

The main logic above prevents duplicate requests, but it only works if the loading flag is reset in both success and failure cases.

swift
1func didFinishLoading(newRows: [String]) {
2    // Update your data source first.
3    // rows.append(contentsOf: newRows)
4
5    tableView.reloadData()
6    isLoadingNextPage = false
7}
8
9func didFailLoading(error: Error) {
10    print(error.localizedDescription)
11    isLoadingNextPage = false
12}

If you reset the flag only after successful loads, one failed request can leave pagination permanently disabled.

Alternative: Detect the Last Cell in willDisplay

If your goal is specifically "the user is about to see the last row," another clean pattern is tableView(_:willDisplay:forRowAt:).

swift
1func tableView(_ tableView: UITableView,
2               willDisplay cell: UITableViewCell,
3               forRowAt indexPath: IndexPath) {
4    let lastSection = tableView.numberOfSections - 1
5    guard lastSection >= 0 else { return }
6
7    let lastRow = tableView.numberOfRows(inSection: lastSection) - 1
8    let isLastCell = indexPath.section == lastSection && indexPath.row == lastRow
9
10    if isLastCell && !isLoadingNextPage {
11        loadNextPage()
12    }
13}

This is often easier to reason about when your table has discrete pages of rows and you care more about cell display than raw scroll offset.

Handle Short Lists and Empty Tables

Bottom detection behaves differently when the content is shorter than the viewport. In that case, contentSize.height may already be less than the visible height, which means a naive formula will say you are at the bottom as soon as the screen appears.

If that is not what you want, add a guard:

swift
1let contentFitsOnScreen = contentHeight <= visibleHeight
2if contentFitsOnScreen {
3    return
4}

This prevents an immediate extra fetch when the first page contains only a few rows. In other designs, that immediate fetch is desirable, so treat it as a product decision rather than a universal rule.

Consider Insets and Bounce Behavior

Do not base the calculation only on frame.height and contentOffset.y. Modern iPhones often have adjusted content insets, especially with large titles, safe areas, and embedded controllers.

Using adjustedContentInset makes the math line up with what the user can actually see. It also reduces false triggers when the table bounces at the top or bottom.

Common Pitfalls

  • Using exact equality such as offsetY + height == contentHeight, which often fails due to rounding and layout changes.
  • Forgetting adjustedContentInset, which can trigger too early or too late on real devices.
  • Firing pagination from scrollViewDidScroll without an isLoading guard, causing duplicate network requests.
  • Resetting the loading flag only on success, leaving the table stuck after one failed request.
  • Assuming short content should always trigger more loading immediately, even when the product does not want that behavior.

Summary

  • Use scrollViewDidScroll when you want continuous bottom detection for pagination.
  • Compare visible bottom position to content height with a threshold, not exact equality.
  • Use adjustedContentInset so the calculation matches the real visible area.
  • Guard duplicate requests with a loading flag and reset it on success and failure.
  • Consider willDisplay for a simpler last-cell trigger when that better matches the feature.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.