Infinite Scrolling Using SwiftUI and View Model (MVVM)
In the context of software, pagination is the process of separating digital content into discrete (separate) pages. This is done so that large amounts of data can be retrieved in parts, rather than all at once, which can become very difficult and inefficient as the amount of data increases.
In web applications, pagination is typically implemented by having numbers at the bottom of each page, usually requiring users to click on “next” and “previous” buttons to retrieve the next/previous page of data. In mobile applications, making a user click on “next” and “previous” buttons at the bottom of a list is not intuitive. So how do we support pagination in mobile apps? The answer is, infinite scrolling!
Infinite scrolling is a design technique whereby users scroll down a list, and content automatically, and continuously loads until there is no more content left to load. This way the user can keep scrolling until they reach the last item on the list, without ever having to manually load more items.
With our understanding of infinite scrolling, let's dive in and implement the example above!
Below is a high level diagram of the components required to implement infinite scrolling. The arrows indicate the direction data flows.
From the diagram we can see that the view model plays a very important role in implementing infinite scrolling. It is in charge of requesting subsequent pages when appropriate, and it does this by analyzing data provided by the backend API as well as the user's scroll position as reported by the view.
The next component is the backend API where we get our data from. For the sake of simplicity, I will assume that we already have a backend API which supports pagination. We simply need to be able to send a request specifying the number of the page we need.
Lastly, the view component looks very small, and that's because it is! SwiftUI really makes it easy to implement infinite scrolling as we will see next.
The primary thing our view code should be able to do is display the list of items and provide our view model with the current scroll position. With SwiftUI, this is surprisingly simple to do, below is the view code.
The first thing the view does is request the first page of data (1) when it is initialized by calling the requestInitialSetOfItems method in the view model. We will look at the view model methods more closely later.
This view code converts the list of items to an enumerated collection (2) which is then iterated by the List view. As the individual individual rows appear, the onAppear modifier is used to pass their index to the view model (3), which will use this information to determine whether it needs to request a subsequent page of data or not.
I decided it was a good idea to give users some indication that more data is loading (4). So I created a simple LoadingView for this purpose. All it does is display a spinner with some text.
As mentioned earlier, the view model performs the bulk of the infinite scrolling logic. To do that it will need to know what the users current scroll position is, how many items are currently loaded onto the device, and how many total items are available according the the backend API.
Before diving into how the view model will perform its logic, let's see what properties it will need to support infinite scrolling. Below are the properties necessary in our view model.
itemsFromThreshold is a threshold value that determines how far from the end of the list we should request more items. The larger the value, the sooner subsequent pages will be requested. The smaller the value, the later subsequent requests will be made since you need to scroll closer to the end.
These properties store pagination related information retrieved from the API. totalItemsAvailable is the total amount of items available according to the API. page is used to keep track of which page we are requesting next. itemsLoadedCount simply keeps tally of how many items we have successfully loaded onto the device.
networkService provides networking APIs capable of taking pagination parameters and returning decoded response objects.
These are the outputs of our view model that the view code observes, hence why they are decorated with @Published . items is simply the list of items that our view displays. dataIsLoading is a boolean used by the view to determine when it should overlay a loading view to let the user know more items are loading.
Now that we have gone over the properties required by the view model, let's dive into how they are used to implement infinite scrolling.
Below are the methods contained in the view model.
This method simply requests the first page of data.
This method is called by the view code every time a new row appears. It requests the next page of data only when two conditions are meet: the threshold for requesting more data is reached, and there is more data available. Notice that before it requests the next page, it first increases the page property by one.
Here is where we make the actual network request. Before making the request we set dataIsLoading to true so the view displays a spinner. Once we successfully receive our response, we update our pagination properties and our list of items. Finally, we hide the spinner.
Using itemsLoadedCount and the current scroll position ( index ), this method determines whether we have meet the threshold for requesting more data. For example, if itemsLoadedCount = 50 and index = 40 , this would mean we are 10 items from the end of the list. If itemsFromEndThreshold were set to 10, this would indicate that our threshold for requesting the next page, if any, is meet.
As the name implies, this method returns a boolean value indicating whether there are any more items in the backend to request or not. If the number of items loaded onto the device is less than to total number of items available, then we have more items to request, so true is returned. Otherwise we cannot request any more items and false is returned.
In this post I demonstrated how easy SwiftUI makes it to implement infinite scrolling. We also saw how virtually all the logic can be contained within the view model, further simplifying our view code and enforcing separation of concerns. All of this is helpful because it makes the code maintainable and scalable.
I hope you enjoyed this post, if you would like to see more content like this please make sure to subscribe!