Using lists is very common in an iOS app. UITableViews are great and have been there from the very beginning, so you can find tons of articles about infinite scrolling. But what about SwiftUI?
You don’t have UITableView, but you can use List. Although you can achieve a similar result you don’t have the concept of data source and delegate.
You may wonder how do you know when the user reached the end of the list, so it is time to fetch some more data to display?
I’ll show you a simple technique to achieve your goal with the first version of SwiftUI, hopefully future evolutions will help us having more control on List.
The example
I have a project on GitHub called StargazersSwiftUI that I used for my article about loading remote images in SwiftUI
I didn’t implement infinite scrolling back then, but I did now. You may want to read that article as well to know how to display images while you fetch data from the network.
A quick introduction to the project: I use an API of GitHub to get the list of stargazers (users that starred a particular project).
The API allow you to specify a page when you make a call and that’s perfect for our purpose as we want to start with a list of users and then fetch more of them as we scroll to the bottom of the list. In the example you can see both the infinite scrolling and the simpler implementation with a button you can tap to load more data, see here
My approach is a view with a List and a view model that provides an array of users to show.
class StargazersViewModel:ObservableObject {
@Published var stargazers:[User] = []
func getNextStargazers(completion: @escaping(Bool) ->Void) {
dataSource.getStargazers(owner:owner, repo:repository, page:currentPage) { stargazers,_ in
DispatchQueue.main.async {
if let stargazers = stargazers {
self.currentPage += 1
self.stargazers.append(contentsOf: stargazers)
completion(true)
}
else {
completion(false)
}
}
}
}
func isLastStargazer(_ stargazer:User) -> Bool {
if let last = self.stargazers.last {
return last == stargazer
}
return false
}
}
I’m pasting only the most significant parts. We have a function to get the next page of users, and another function to find out if a user is the last one of the list.
Infinite scrolling
Now the important part: how do we know when is time to fetch more users?
Our view model has a function that let us now if a particular user is the last one, so once we reach the last user we know it is time to ask for more data.
As I said, there isn’t a delegate method to know when we reach the end of the list, so we’ll use onAppear on each view of the List
struct StargazersViewInfiniteScroll: View {
@ObservedObject var viewModel:StargazersViewModel
var body: some View {
List(viewModel.stargazers) { stargazer in
StargazerView(stargazer: stargazer)
.onAppear {
self.elementOnAppear(stargazer)
}
}
}
private func elementOnAppear(_ stargazer:User) {
if self.viewModel.isLastStargazer(stargazer) {
self.viewModel.getNextStargazers { success in
print("next data received")
}
}
}
}
onAppear on the view is called when a view appears on screen. Our function elementOnAppear asks the view model if the element is the last one. If so, we ask the view model to fetch new data. There is a completion, but it isn’t really necessary as the view model update its stargazers var that is @Published, so our List updates itself. The callback can be used for error handling, or to display an indicator that there is no more data to fetch.
Conclusion
That’s it, using onAppear is a simple technique I used while waiting for enhancements of SwiftUI. I’m sure we’ll have more control over List in the future and there will be way more APIs, but in the meantime I hope this article helped you implement infinite scrolling on your SwiftUI app. Happy coding 🙂