UIKit | How to make modern infinite scroll view like the Unsplash app
In this article I will show you how to implement infinite scroll in UIKit with Combine & DiffableDataSource.
- iOS 13
- Swift 5.x
- Xcode 13.x
- Unsplash API
- Without Storyboard
- Without any open sources(Kingfisher, SDWebImage, RxSwift, SnapKit, Then etc.)
It may seem odd to use Combine with UIKit. As you may know, however, SwiftUI is still incomplete in iOS 13 and seems too early to target iOS 13. Maybe this is why so many people still remain on UIKit.
We will use Combine instead of RxSwift. Combine has advantages that it is native library so does not need to be installed. It’s the reason that if you targeted iOS 13 for your app, you can consider about using Combine with UIKit.
I’m not going to use storyboard in this project. It’s simply the matter of preference so if you want to use storyboard, do so.
How to Properly Remove Main.Storyboard (for iOS 13+)
Whenever you start a new project in Xcode, you need to choose between Storyboards and SwiftUI. Conspicuously missing…
Register your app with the API
You need to register your app in Unsplash to get the client ID used for all requests.
Unsplash API Documentation | Free HD Photo API | Unsplash
This document describes the resources that make up the official Unsplash JSON API. If you have any problems or…
Now we are all ready to dive in.
Make publishers with Combine
Starting with the most important things, network requests.
Publishers help download image and image’s meta data from the network API and process those data asynchronously. We will create a manager class containing static methods to enhance usability.
- To handle network response, I made
ImageMetaData. You can check
ImageMetaData’s full structure in here.
- Key component in here is
dataTaskPublihser. It wraps the well-known native API
dataTaskas a Combine publisher.
You can use those publishers this way:
fetchImageMetaDatawhere you need to get an arrays of meta data. Don’t forget to replace
- You will get a url of an image from the response. To get an image we can use(ex.
UIImage), we should request once again from the url. For this, you can subscribe method
I will show you how to use those later in this article.
Caching with URLCache
Caching large data, such as images, definitely improves application’s performance.
Mostly, the open sources like Kingfisher and SDWebImage does all these things for us so usually we don’t have to care about this irritating jobs.However we are going to do it ourselves in here to understand these things more deeply.
You can skip this part and replace with any open sources if you want.
It’s quite simple. Make a cache storage and check if a request has stored in the cache storage. If it’s not stored, add logic to save the data.
You can use these methods in
Make UI with UIKit
There’s no doubt that you should use
UITableView when displaying repeating cells. You can use either one depending on your preference.
- The view model starts fetching data the moment the instance is created. Fetched image data should be connected data source later.
- It is also responsible for updating data when requested. Update requests are triggered by the view controller based on scroll position. It’s commonly referred to as lazy loading.
- I applied
UICollectionViewFlowLayoutto decide each cell’s height dynamically. It will be implemented through the
UICollectionViewDelegateFlowLayoutwhich I will show you later in this post.
- Also make sure you set the
isPrefetchingEnabledtrue and adopt
UICollectionViewDataSourcePrefetching. It helps avoid choppy scroll when scrolling down the screen quickly.
- As you can see, I didn't adopt
UICollectionViewDataSourcedelegate in this project because instead of that I will use
UICollectionViewDiffableDataSourceas a data source.
- Please don’t forget adopting
UICollectionViewDelegateeven though you are not using it. If this line’s omitted, cells size is set unexpectedly.
Each cell is designed to show an image with author’s name at the bottom left side.
You can check full codes in here.
- A bit tricky thing in here is when a cell is created, we have to make another request to download the image data, unlike the author name.
- There are many ways to pass data to child view controller or view models but I chose to pass the url and handle this information from child view independently. It has the advantage that we can avoid requesting too many data at once.
- This method can have some disadvantages in the aspect of loading speed compared to the method forwarding data directly. It’s because a cell will fetch image at the time it’s shown at the screen.
- It can be avoided to some extent using pre-caching but choose the method according to the part you focus on.
- Cell’s image component(
UIImageView) is connected to view model to be updated right after the image’s downloaded.
- Don’t forget to inject its view model from outside.
Set datasource with UICollectionViewDiffableDataSource
UICollectionViewDiffableDataSource is newly introduced in iOS 13. It has some advantages compared to the original
- Automatic data change animations
- Automatic data synchronization
- Reduced code
- What I did in here is connecting view model’s
- Before making the data source, set
Sectionenum. It’s necessary to make data source.
- To update the data source, use
NSDiffableDataSourceSnapshot. It literally snaps current state of data source and offers the methods, such as
appendItems, which help us change the values.
- If you finished any update logic and want to migrate new data, just
Set flow layout & prefetching
UICollectionViewDelegateFlowLayoutallows you to determine the height of a cell dynamically.
- In the
ImageMetaData, you will remember that we have set a computed variance
imageRatiocalculating aspect ratio of the image.
- Using the
imageRatio, it’s possible to calculate cell’s proper height when width of cell is same with the screen’s.
- To avoid choppy scrolling, prefetching data before reaching the end of page can be a solution. In this project, another page of
ImageMetaDatacan be prefetched when it arrives at the 3rd cell from the bottom. The location of cell that triggers prefetch can be adjusted at will.
- We can also improve load speed by pre-caching the image of the next cell in advance. It’s done by the method
Add drag refresh
It can be implemented by adding
UIRefreshControl. However, if you merely added it to your collection view, you will be facing the animation which never stops even after refresh has already been done.
To stop the animation at the right time, we should keep observing a progress of network request. Fortunately, it becomes really easy with Combine.
- One of the best practices to handle progress of network request is using
enum. You can define all possible cases and update shared variable using the enum.
viewModel.processwill be the case in here. It determines when the animation should start or stop.
- Combine’s method
sinkoffers us a very convenient feature,
receiveCompletion. Using this, we can detect when the request completed or failed with certain error. You can check this in the first code block’s
Now it’s done
It’s been a long way. There will be more to improve this project such as full screen page view like the official Unsplash application. It will probably look like the one below:
Thanks for reading it 😀
Many parts of the codes are not described in this post. I highly recommend you to take a look at the source codes below.