UITableView infinite scrolling a lot simpler than you knew: UITableViewDataSourcePrefetching
Imagine the following scenario: You have a UITableView that you want to present lots of data that were fetched from the WEB via some API. It might be thousands of different objects to be scrolled in a single tableView, but the thing is, you may not even need to check all of them, what would make this task totally unnecessary and spend too much memory. In this case, wouldn't that be much easier to load only the items you are actually about to see as you scroll in the table and have some way of preventing what you are about to display?
Well it becomes a lot easier with a new protocol that was introduced in iOS 10.0, which is the UITableViewDataSourcePrefetching. Basically this protocol consist of two methods:
Basically, they are triggered when you are about to scroll your tableView up or down saying to you that some rows are about to be shown(scrolling down) or if they are not about to be displayed anymore(scrolling was cancelled). The data to be passed by the indexPaths array varies depending on which position of the table you are and which speed you are scrolling. It really prevents which shall be displayed in your screen.
1) prefetchRowsAt
This method is called when you are scrolling a tableView down and then the UITableView API may predict which indexes are about to show and then you can make some appropriate handling, like fetching asynchronous data to fill that cell. Of course, the faster you scroll down, more cells will be prefetched.
2) cancelPrefetchingRowsAt
Now, if you are scrolling down and have some cells that are about to be displayed, and then you suddenly stop scrolling or maybe just decrease the scroll's speed, some cells will be tagged as not about to show anymore. This way, you may cancel any asynchronous operation for then since it will not be necessary anymore.
Demo: Number API
Now that you don't need to fetch all the data at once, just when necessary, let's illustrate this phenomenon with a NumberAPI fetching demo. If you don't know, the Number API receives a number within a URL and fetches some curious fact about that number. What we are about to do is to list all facts for each number in an increasing order one by one, but we shall fetch then only if necessary. First things first, let's setup our project:
- Create a new Xcode project with the iOS template and in your main storyboard, drag and drop a UITableView, attaching its constraints straight to the borders. Don't forget to drag an IBOutlet to your ViewController swift file:
2. Create a model for the numbers we are fetching. This is the shape of the data we are fetching from the NumberAPI:
struct NumberModel: Decodable {let text: String?let number: Int?let found: Bool?let type: String?}
3. Declare this two variables in your ViewController class. One of them represents the numbers array content that your tableView shall present. The second is the total, the count of rows your tableView should display:
But wait, Why aren't we declaring the count of rows as the numbers array itself? Well, that's because the actual count isn't the number of numbers, but we need to have more cells in order to detect when we should prefetch more items. We will check it further.
4. Create a custom UITableViewCell called TableViewCell with a default identifier string called by its own name and also a Xib file dragging a single label:
5. Make a setup to your tableView in your ViewController:
As you may have seen, like the other protocols, the tableView should assign its prefetchDataSource property to the ViewController in order to implement the prefetching protocol. We will implement the initialPrefetch method soon
6. Implement the fetching logic:
7. Implement the TableView protocols:
Pagination logic
Now that we have implemented the fetching task, let's understand how will be our logic to iterate through each number and when is time to fetch new data. As we saw before, the two methods from UITableViewDataSourcePrefetching are responsible respectively to tell us which cells are about to be displayed depending on the scrolling speed and which cells are not about to be displayed anymore if we decrease our speed or stop scrolling.
Firstly, we will make an initial prefetch operation that will retrieve the first ten cells, that's why I initialized our total with ten before.
When we are scrolling down. We first need to check if the cells to be displayed have already their content about the respective number. If they are new, we start fetching it:
Also, for each time we are about to display new content, we increment our total in order to have more cells that our logic shall detect. Imagine that like we were adding yeast to a bread in order to improve its growing process.
For each cell that doesn't seem to be displayed anymore, we just check if it's being fetched and just cancel the task and remove from the tasks array:
Don't forget to also decrement our totalNumbers variable as it's one cell that will not show for now.
Now we are fetching only the cells that we actually need to display, and increase our list interface size just when we are about to display new data in order to allow the user to always keep scrolling:
If you wanna check the Github repository for this project, click right here
Conclusion
In this article we showed a new way of always keeping track of which cells of a tableView are going to display and then handling a respective remote task before it has actually showed up, or maybe removing the task if the cell is not about to display. With new cells being incremented, we can always allow the user to keep an infinite scroll and even more important, we don't waste memory as our tableView grows dynamically, I hope you enjoyed ;)