Implementing an infinite scrolling list and pagination with SwiftUI

Implementing an infinite scrolling list and pagination with SwiftUI

Recently, I was working on a feature that needed infinite scrolling and pagination. After searching for a few articles online, I realized that there is little to no help on how to achieve infinite scrolling on SwiftUI views.

So I ventured a little, did some research, and came up with my own solution on how to implement pagination and infinite scrolling with SwiftUI views. It is very simple and can be applied to any use case without performance overhead.

You can easily customize it with your own network layer, triggering the condition of loading the next set of data and the terminal condition for when to stop the loading.

Let's start building an infinite loading screen step by step. At each step, we will build an individual component, and at the end, we will tie them all together and build our screen.

  1. Building a model object

Before we start loading our data in the infinite list with pagination, we first need to create a model object. This is our data source which will get attached to a view and its content will be shown to users.

We will start with a simple Employee struct with just two properties id and name. I have intentionally decided now to pollute it with extra properties to keep things simple.

struct Employee: Identifiable, Equatable {

    var id: String {

    let name: String
Please note the conformance to Identifiable and Equatable protocols. The conformance is needed to uniquely identify each instance and compare two Employee instances

2. Building a View Model

The next part is building a view model which is a logical brain of our view. The view model is responsible for fetching data and keeping track of states. Our view will change in response to changes in state properties on the view model.

At the basic level, we have listItems array which contains the objects of type Employee and isLoading boolean flag which indicates if we have any more records to load or not. Once we reach the end of the list, this flag is switched off.

Both these properties are marked with Published annotation and the class conforms to ObservableObject protocol so that we can observe any changes to this property and update the view accordingly.

In addition, we have two private properties for internal calculation on the view model. iteration is used to keep track of the number of records loaded. maximumNumberOfRecordsToFetch is used to keep track of the maximum records to fetch before we stop the loading operation.

class ViewModel: ObservableObject {

    private let maximumNumberOfRecordsToFetch = 40

    @Published var listItems: [Employee] = []
    @Published var isLoading = true
    private var iteration = 0

    init() {

    func loadItems() {

        if iteration >= maximumNumberOfRecordsToFetch {
            self.isLoading = false

        // Artificial delay to give illusion of network service loading data
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
            let employees = (self.iteration..<self.iteration + 10).map { Employee(name: String($0)) }
            self.iteration += 10
            self.listItems.append(contentsOf: employees)

As soon as the view model is initialized, we call loadItems method to load the initial batch of Employee objects. Subsequent calls to loadItems are triggered from the SwiftUI view.

3. Building a SwiftUI view and integrating it with a view model

In the third and final part, we will build a SwiftUI view and integrate it with a view model. Our view is just a list with a Text which displays the name of each employee. We also have a loading indicator at the end to show the loading indicator.

struct InfiniteLoadingScreen: View {

    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        List {
            ForEach(viewModel.listItems) { listItem in

                if listItem == viewModel.listItems.last {
                    if viewModel.isLoading {
                        ProgressView().onAppear {

When the view loads for the first time, it already has the initial list of items. When we reach to the end of the list, if there are still any items to load, we will add a progress indicator view which will load the next batch as soon as it appears on the screen.

Once the view model has completed loading all the necessary items, it sets isLoading flag to false and the progress indicator is no longer displayed.

The full source code for this demo is available to reference on GitHub in this GitHub gist.


So this was all about implementing infinite scrolling with pagination on the SwiftUI platform using Swift and iOS. Hope this post was useful to you. Infinite loading data is a frequent use case in iOS apps and is intended to provide a seamless experience to end users without extra action.

One caveat though - infinite loading consumes a lot of network bandwidth and if it involves media download, it can easily strain the network resources. Before you enable automatic infinite loading, make sure to let users know its pros and cons and provide an option to let them turn it off. Even better, you can also disable it by default when you know the user is on the slower or the LTE network to consume precious bandwidth.

As mentioned above, the full source code is available on GitHub for reference and extension. I welcome all and any feedback and comments on this article and the source code.

Support and Feedback

If you have any comments or questions, please feel free to reach out to me on LinkedIn.

If you like my blog content and wish to keep me going, please consider donating on Buy Me a Coffee or Patreon. Help, in any form or amount, is highly appreciated and it's a big motivation to keep me writing more articles like this.

Consulting Services

I also provide a few consulting services on, and you can reach out to me there too. These services include,

  1. Let's Connect
  2. Resume Review
  3. 1:1 Mentorship
  4. Interview Preparation & Tips
  5. Conference Speaking
  6. Take-home Exercise Help (iOS)
  7. Career Guidance
  8. Mock Interview