UITableView Infinite Scrolling Tutorial

In this tutorial you will learn how to implement an Infinite Scrolling UITableView in your iOS app using a paginated REST API. By Lorenzo Boaro.

4.2 (29) · 2 Reviews

Download materials
Save for later
Share
Note: This tutorial works with both Xcode 9 and Xcode 10, iOS 11 and iOS 12.

Infinite scrolling allows users to load content continuously, eliminating the need for pagination. The app loads some initial data and then adds the rest of the information when the user reaches the bottom of the visible content.

Social media companies like Twitter and Facebook have made this technique popular over the years. If you look at their mobile applications, you can see infinite scrolling in action.

In this tutorial, you’ll learn how to add infinite scrolling to an iOS app that fetches data from a REST API. In particular, you’ll integrate the Stack Exchange REST API to display the list of moderators for a specific site, like Stack Overflow or Mathematics.

To improve the app experience, you’ll use the Prefetching API introduced by Apple in iOS 10 for both UITableView and UICollectionView. This is an adaptive technology that performs optimizations targeted to improve scrolling performances. Data source prefetching provides a mechanism to prepare data before you need to display it. For large data sources where fetching the information takes time, implementing this technology can have a dramatic impact on user experience.

Getting Started

For this tutorial, you’ll use ModeratorsExplorer, an iOS app that uses the Stack Exchange REST API to display the moderators for a specific site.

Start by downloading the starter project using the Download Materials link at the top or bottom of this tutorial. Once downloaded, open ModeratorsExplorer.xcodeproj in Xcode.

To keep you focused, the starter project has everything unrelated to infinite scrolling already set up for you.

In Views, open Main.storyboard and look at the view controllers contained within:

storyboard

The view controller on the left is the root navigation controller of the app. Then you have:

  1. ModeratorsSearchViewController: This contains a text field so you can search for a site. It also contains a button which takes you to the next view.
  2. ModeratorsListViewController: This includes a table which lists the moderators for a given site. Each table cell, of type ModeratorTableViewCell, includes two labels: one to display the name of the moderator and one for the reputation. There’s also a busy indicator that spins when new content is requested.

Build and run the app, and you’ll see the initial screen:

startscreen

At the moment, tapping on Find Moderators! will show a spinner that animates indefinitely. Later in this tutorial, you’ll hide that spinner once the initial content gets loaded.

Get Acquainted with Stack Exchange API

The Stack Exchange API provides a mechanism to query items from the Stack Exchange network.

For this tutorial, you’re going to use the /users/moderators API. As the name implies, it returns the list of moderators for a specific site.

The API response is paginated; the first time you request the list of moderators, you won’t receive the whole list. Instead, you’ll get a list with a limited number of the moderators (a page) and a number indicating the total number of moderators in their system.

Pagination is a common technique for many public APIs. Instead of sending you all the data they have, they send a limited amount; when you need more, you make another request. This saves server resources and provides a faster response.

Here’s the JSON response (for clarity, it only shows the fields related to pagination):

{

  "has_more": true,
  "page": 1,
  "total": 84,
  "items": [
 
    ...
    ...
  ]
}

The response includes the total number of moderators in their system (84) and the requested page (1). With this information, and the list of moderators received, you can determine the number of items and pages you need to request to show the complete list.

If you want to learn more about this specific API, visit Usage of /users/moderators.

Show Me The Moderators

Note: This tutorial uses URLSession to implement the network client. If you’re not familiar with it, you can learn about it in URLSession Tutorial: Getting Started or in our course Networking with URLSession.

Start by loading the first page of moderators from the API.

In Networking, open StackExchangeClient.swift and find fetchModerators(with:page:completion:). Replace the method with this:

func fetchModerators(with request: ModeratorRequest, page: Int, 
     completion: @escaping (Result<PagedModeratorResponse, DataResponseError>) -> Void) {
  // 1
  let urlRequest = URLRequest(url: baseURL.appendingPathComponent(request.path))
  // 2
  let parameters = ["page": "\(page)"].merging(request.parameters, uniquingKeysWith: +)
  // 3
  let encodedURLRequest = urlRequest.encode(with: parameters)
  
  session.dataTask(with: encodedURLRequest, completionHandler: { data, response, error in
    // 4
    guard 
      let httpResponse = response as? HTTPURLResponse,
      httpResponse.hasSuccessStatusCode,
      let data = data 
    else {
        completion(Result.failure(DataResponseError.network))
        return
    }
    
    // 5
    guard let decodedResponse = try? JSONDecoder().decode(PagedModeratorResponse.self, from: data) else {
      completion(Result.failure(DataResponseError.decoding))
      return
    }
    
    // 6
    completion(Result.success(decodedResponse))
  }).resume()
}

Here’s the breakdown:

  1. Build a request using URLRequest initializer. Prepend the base URL to the path required to get the moderators. After its resolution, the path will look like this:
    http://api.stackexchange.com/2.2/users/moderators.
  2. Create a query parameter for the desired page number and merge it with the default parameters defined in the ModeratorRequest instance — except for the page and the site; the former is calculated automatically each time you perform a request, and the latter is read from the UITextField in ModeratorsSearchViewController.
  3. Encode the URL with the parameters created in the previous step. Once done, the final URL for a request should look like this: http://api.stackexchange.com/2.2/users/moderators?site=stackoverflow&page=1&filter=!-*jbN0CeyJHb&sort=reputation&order=desc. Create a URLSessionDataTask with that request.
  4. Validate the response returned by the URLSession data task. If it’s not valid, invoke the completion handler and return a network error result.
  5. If the response is valid, decode the JSON into a PagedModeratorResponse object using the Swift Codable API. If it finds any errors, call the completion handler with a decoding error result.
  6. Finally, if everything is OK, call the completion handler to inform the UI that new content is available.

Now it’s time to work on the moderators list. In ViewModels, open ModeratorsViewModel.swift, and replace the existing definition of fetchModerators with this one:

func fetchModerators() {
  // 1
  guard !isFetchInProgress else {
    return
  }
  
  // 2
  isFetchInProgress = true
  
  client.fetchModerators(with: request, page: currentPage) { result in
    switch result {
    // 3
    case .failure(let error):
      DispatchQueue.main.async {
        self.isFetchInProgress = false
        self.delegate?.onFetchFailed(with: error.reason)
      }
    // 4
    case .success(let response):
      DispatchQueue.main.async {
        self.isFetchInProgress = false
        self.moderators.append(contentsOf: response.moderators)          
        self.delegate?.onFetchCompleted(with: .none)
      }
    }
  }
}

Here’s what’s happening with the code you just added:

  1. Bail out, if a fetch request is already in progress. This prevents multiple requests happening. More on that later.
  2. If a fetch request is not in progress, set isFetchInProgress to true and send the request.
  3. If the request fails, inform the delegate of the reason for that failure and show the user a specific alert.
  4. If it’s successful, append the new items to the moderators list and inform the delegate that there’s data available.

Note: In both the success and failure cases, you need to tell the delegate to perform its work on the main thread: DispatchQueue.main. This is necessary since the request happens on a background thread and you’re going to manipulate UI elements.

Build and run the app. Type stackoverflow in the text field and tap on Find Moderators. You’ll see a list like this:

Moderators list

Hang on! Where’s the rest of the data? If you scroll to the end of the table, you’ll notice it’s not there.

By default, the API request returns only 30 items for each page, therefore, the app shows the first page with the first 30 items. But, how do you present all of the moderators?

You need to modify the app so it can request the rest of the moderators. When you receive them, you need to add those new items to the list. You incrementally build the full list with each request, and you show them in the table view as soon as they’re ready.

You also need to modify the user interface so it can react when the user scrolls down the list. When they get near the end of the list of loaded moderators, you need to request a new page.

Because network requests can take a long time, you need to improve the user experience by displaying a spinning indicator view if the moderator information is not yet available.

Time to get to work!