I recently had to implement infinite scrolling in an iOS app. If you’re not familiar with infinite scrolling, it’s an alternative to pagination, where instead of having the user explicitly do something to advance to the next page, new content is lazily loaded as the user nears the end of the currently visible content. This pattern is often used on the web for social media sites, where its main goal is to increase engagement. If you don’t have to click on a “next” button, it’s much easier for you to keep consuming content.
The often slow or unreliable networks that mobile devices operate on make it even more difficult to implement infinite scrolling well. You can’t provide a seamless transition to the next batch of data if the network isn’t available when you need it, or if it takes several seconds to retrieve the data you need. This post will outline a variety of considerations, along with a brief overview of two general implementation approaches.
Keeping It Simple
If you’re contemplating infinite scrolling, make sure that you actually need it, and think carefully about the impacts on user experience. If the data you’re displaying is small enough, there may not be a significant benefit to fetching it incrementally. If you can get everything in a single request, your code will be simpler, and there’s no risk of not having the data you need when the user wants to view it. Remember also that every network request is an opportunity for an error. If you wait to load data on-demand, the user may have lost their network connection, and now what was intended to improve user experience has instead harmed it. You may be able to get the best of both worlds by fetching a small amount of data up front, then proactively fetching the rest of it in the background, so it’ll be there when you need it.
Things to Consider
So you’ve decided that infinite scrolling makes sense for your app. There are a variety of things to consider related to your user experience goals, the kind of data you’re working with, and the API that is providing the data. In addition to the questions below, the Nielsen Norman Group discusses some of the trade-offs of infinite scrolling in their article, Infinite Scrolling Is Not for Every Website. While written in the context of the web, some of the concepts are applicable to mobile apps too.
User Experience
Is position in the data you’ll be displaying meaningful? Depending on the implementation that you choose, it may not be possible to show an accurate scroll indicator. It’s also possible that the total number of results may change, so you’ll have to decide if and how you want to communicate that to the user. What are your primary goals? Are you trying to decrease latency? Are you trying to minimize cellular data use? How common is it for users of your app to have intermittent network connectivity? How important is this feature to your app? The answers to questions like these will help you decide how much complexity is justified and ultimately which implementation to use.
API
The API that provides your app’s data can place strong constraints on your implementation decisions. Do you control the API, or is it provided by a third party? Are there rate limits to worry about? What will you show the user if you exceed a limit? Is the result set that you’re displaying stable, or can it change while you’re viewing it? Are the results explicitly ordered? Will you hide duplicate results if you receive them? Is there any way for you to detect missed results? Does your API use indices that you can calculate, or is it cursor-based, which may force you to serialize your network requests and prohibit cancellation of pending requests?
Blocking vs Non-Blocking
In the iOS apps that I use, I’ve seen two main infinite scrolling variants,
which I’ll be referring to as blocking and non-blocking. I’ll also be discussing
these approaches in the context of a UITableView
, but everything should be
relevant to UICollectionView
and custom UI too.
The blocking version of infinite scrolling allows the user to scroll all the way
to the bottom of the screen. Once at the end of the current data, a loading view
is displayed, usually as either a UITableViewCell
or a table footer. The user
then has to wait while the next batch of data is fetched. Once the new data
arrives, it is added to the table, and the user can continue. While the need to
wait for every screenful of data can be frustrating, this approach is
considerably easier to implement than any of the alternatives.
The non-blocking version does not interrupt scrolling, instead it shows a loading version of cells while their data is retrieved. Once the data arrives, the cells that were in a loading state are updated to display the real data. This approach has the potential to provide a better user experience. However it’s still highly dependent on network performance, and because the UI is never blocked, it may be necessary to implement more complex batching or delays in the app in order to avoid exceeding API rate limits. Building Concurrent User Interfaces on iOS has some great information on loading table view content asynchronously.
Both approaches above can be enhanced by proactively fetching more data before
it’s needed. Common techniques involve running code in scrollViewDidScroll
or
even layoutSubviews
as discussed in Advanced ScrollView Techniques.
If you’re targeting iOS 10 or newer, you can use
UITableViewDataSourcePrefetching
or UICollectionViewDataSourcePrefetching
as
discussed in What’s New in UICollectionView in iOS 10.
Conclusion
Hopefully this post has given you some things to think about. Which of the two approaches above make sense for your app depends a lot on the specifics of the data you’re displaying, as well as how much technical complexity is appropriate for the features you’re implementing. Upcoming posts will provide some example implementations of infinite scrolling and discuss the trade offs involved.