Better Programming

Advice for programmers.

Follow publication

Build an Infinite List With SwiftUI and Combine

Mohd Hafiz
Better Programming
Published in
5 min readJun 30, 2021

What we will build. Image by the author.

SwiftUI has become more famous since its introduction at WWDC2019. Over the past two years, Apple has added lots of improvements to this UI framework. Today, developers have started adopting it and slowly migrating their projects from UIKit.

What Will We Build?

In this tutorial, we will make a simple list with endless scrolling and pagination (as shown in the header image). We will use GitHub API to get a list of users. It contains all the GitHub users since it was founded. In our final product, we will be able to create an app with the following features:

  1. Able to fetch a user list based on a given page limit.
  2. Able to endlessly scroll the SwiftUI List with pagination.
  3. Able to handle request errors and reloading.
  4. Uses the Combine framework for API requests and ViewModel.

Let’s Start

1. Create new SwiftUI project

Creating a new project

2. Create decodable user model

Create a new Swift file called User.swift. We only decode the id, name, and avatarUrl from the JSON object. As shown in the result above, in this project, we will only display the user’s name for a quick example. You may enhance it later to display the user avatar and profile details.

3. Add Row view

Since we are using List, we will first create the row view as the list items. Add a new “SwiftUI View” file called UserRow.swift. Then, insert the code below. We will use a simple HStack with Image and Text.

Take note that this UserRow View requires User as the dependency model, which means it cannot be initialized without the model passed to this view.

4. Main view (ContentView)

The ContentView is our default main view. The main view is defined in the InfiniteListSwiftUIApp file (ProjectNameApp file).

Great. Let’s include a NavigationView and a List. Then, insert some static user data.

Now, if we run our project, the app will be similar to the image below:

Users app

5. Create API service

In this step, we are going to create an API service to get the user data from the GitHub API. As shown in the image below, to enable paging, we need to send the since and per_page parameters.

Endpoint details from GitHub API docs
/users endpoint details from GitHub API docs.

Add the following code to a new file called APIService.swift. Since we are using SwiftUI, the project automatically supports iOS13+. We will use the Combine framework in our URLSession.

  1. Create a function that accepts perPage and sinceId and returns an AnyPublisher type with [User].
  2. Construct a URL with specified parameters.
  3. Create a URL request with a timeout of ten seconds.
  4. Send a request with dataTaskPublisher. Then map the result to .data (the result will return Data and Response). Finally, decode the data into [User]. It is simple and clean, right?

6. Create ViewModel

Add a new Swift file called UserViewModel as an ObservableObject and add the code below. By subclassing the ObservableObject, our view model can be used inside SwiftUI views and enable auto-reload when any important changes occur.

  1. Create the users and isRequested variables with the Published wrapper to enable any SwiftUI view receiving the update.
  2. Add a constant pageLimit to specify the number of user objects that will be returned from the API. Then, add currentLastId to keep the last user ID in the users array that we have fetched. It will be used for the pagination parameter request, as stated in a previous step.
  3. Call getUsers() from our service class.
  4. When the request fails, we will mark the variable isRequestFailed to true so that it will trigger the subscriber view and make the proper reload.
  5. If the request is successful, we will get the received value (with type [User]) and append the value to the users variable. Again, when the users variable is undergoing changes, it will immediately trigger the view to reload its content. Then, store the currentLastId for the next fetch.

7. Create LoaderView

Now we want to create a LoaderView that will be placed at the bottom of List. So, whenever it appears, it will call the fetching data function. Even though the user list is empty, the LoaderView will still appear at the top and show the loading text.

8. Update ContentView

This is our last step and it will be quite simple, as we have separated the logic in the ViewModel.

  1. Remove the previous users static variable and use the users data from ViewModel instead. Create a userViewModel instance with ObservedObject as a subscriber to the changes in the UserViewModel. It will invalidate the view when receiving a new update.
  2. Same as before, except now we are using the users array from the ViewModel.
  3. Add the LoaderView that we created before to be part of the list at the bottom position. Also, it has an onAppear() function that will call fetchData. Add onTapGesture() to enable re-fetching data when a request fails.
  4. Call getUsers in ViewModel.
  5. Handle the “Tap to retry” action.

9. Testing failed request

I would recommend using the Network Link Conditioner tool to simulate a failed network request, as shown in the image below. So, when we scroll to the end, we may reach the LoaderView and the request will fail after a few seconds. You may read the details in my previous article.

“Tap to retry” is shown when a network request fails.

Project Completed

Congratulations! We have completed all of the steps and the code works perfectly. When I was implementing the SwiftUI code with the Combine framework, I never expected it to be this clean.

The entire source code is available for download from my GitHub repository. You may try implementing it into your projects and perhaps improving the code logic and the UI. For details on the Combine framework and ObservableObject protocol, you may refer to this WWDC video and this HackingWithSwift article.

Thanks for reading and happy coding!

Mohd Hafiz
Mohd Hafiz

Written by Mohd Hafiz

iOS Developer focusing on Swift — “Read, learn & practice!”.

Write a response

Doesn't it invalidate the whole List when you append more items to it? After you have thousands of items, it becomes extremely laggy when appending more and more

Should you be doing all that work on the main thread? …probably not, do not do networking & processing on the main thread unless you really need to, or have a really good reason to…?