Episode #309

Automatic UITableView Paging

15 minutes
Published on November 2, 2017
UITableView can support scrolling through many rows of data, however fetching large amounts of remote data can slow down your app, use up too much memory, and bog down your web server. This is all wasteful if users aren‘t ever going to scroll down that far. In this episode you‘ll learn how to perform automatic UITableView pagination using an easy technique.

This episode is a refresh of Episode 8.

Setting Up

    private var beers: [Beer] = []
    private var currentPage = 1
    private var shouldShowLoadingCell = false
override func viewDidLoad() {
    // ...

    refreshControl = UIRefreshControl()
    refreshControl?.addTarget(self, action: #selector(refreshBeers), for: .valueChanged)

    refreshControl?.beginRefreshing()
    loadBeers()
}

Loading the Data

    private func loadBeers(refresh: Bool = false) {
        print("Fetching page \(currentPage)")
        breweryDBClient.fetchBeers(page: currentPage, styleId: 3) { page in
            DispatchQueue.main.async {

                if refresh {
                    self.beers = page.data
                } else {
                    for beer in page.data {
                        if !self.beers.contains(beer) {
                            self.beers.append(beer)
                        }
                    }   
                }

                self.shouldShowLoadingCell = page.currentPage < page.numberOfPages

                self.refreshControl?.endRefreshing()
                self.tableView.reloadData()
            }
        }
    }

Fetching the next page

private func fetchNextPage() {
    currentPage += 1
    loadBeers()
}

Showing the Loading Cell

If we need to show a loading cell we need to add 1 to the cell count:

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let count = beers.count
        return shouldShowLoadingCell ? count + 1 : count
    }

Then return a cell for the loading index path:

    private func isLoadingIndexPath(_ indexPath: IndexPath) -> Bool {
        guard shouldShowLoadingCell else { return false }
        return indexPath.row == self.beers.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        if isLoadingIndexPath(indexPath) {
            return LoadingCell(style: .default, reuseIdentifier: "loading")
        } else {
            //...
        }
    }

Then we need to trigger the next page load when the cell is displayed...

    override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        guard isLoadingIndexPath(indexPath) else { return }
        fetchNextPage()
    }

Refreshing the Data

@objc
private func refreshBeers() {
    currentPage = 1
    loadBeers(refresh: true)
}

This episode uses Xcode 9.1-beta1, Swift 4.

Want more? Subscribers can view all 587 episodes. New episodes are released regularly.

Subscribe to get access →

Source Code

View on GitHub Download Source