UICollectionView DataSourcePrefetching Example and Explanation

    Klevis Davidhi
    Klevis Davidhi
    Share

    UICollectionView DataSourcePrefetching

    Recently, Apple announced a new UICollectionViewDataSource protocol extension called UICollectionViewDataSourcePrefetching. This extension makes it possible to implement two new functions that deliver a completely smooth scrolling performance. This tutorial will show you how to use it.

    You can find the source code on GitHub.

    Setting up the Xcode project

    Open Xcode and create a new project based on the Single View Application template. Before you do anything else, in order to improve the appearance of our application ,select the view and choose Editor -> Embed In -> Navigation Controller. Now drag a Collection View from the Object Library onto the view canvas and adjust its size .

    Notice that into the Collection View is one reusable cell , so click under the Attributes Inspector and give this cell an Identifier, let’s call it foodCell. Drag an UIImageView onto it and adjust its size to fit the content.

    Cell Identifier

    Create a new file, of type Cocoa Touch Class , subclass of UICollectionViewCell, name it collectionViewCell and assign it to the cell.

    collectionViewCell Class

    Connect the UIImageView to the collectionViewCell.swift code and name it ‘foodImage‘. Also , connect the collectionView to ViewController.swift.

    ImageView connection

    Conforming to protocols and writing some lines

    Now , it’s time to write some code in viewController.swift.
    The first thing to do is to conform the class to UICollectionViewDelegate,UICollectionViewDataSource, and UICollectionViewDataSourcePrefetching protocols.

    Declaring protocols

    You should probably see some errors showing up in your sidebar, due to required methods not being implemented yet. We’ll cover that soon, but let’s finish a little setup first.

    We’ll need a data source for this UICollectionView, so let’s just create an array called imageArray, which for illustrative purposes will store 30 images. Outside of any method, create the array like so:

    var imageArray = [UIImage?](repeating: nil, count: 30)

    and a variable to store the base url of a picture like so :

    var baseUrl = URL(string: "https://placehold.it")!

    Same as above create another array of type URLSessionDataTask :

    var tasks = [URLSessionDataTask?](repeating: nil, count: 30)

    The next step is to create two separated functions in order to generate images from dynamically generated urls. The first function requires a parameter which in our case will be the index of each cell and will return an url.

     func urlComponents(index: Int) -> URL {
    
            var baseUrlComponents = URLComponents(url: baseUrl, resolvingAgainstBaseURL: true)
            baseUrlComponents?.path = "/\(screenSize.width)x\(screenSize.height * 0.3)"
            baseUrlComponents?.query = "text=food \(index)"
            return (baseUrlComponents?.url)!
        }

    The second one is where the downloading process will be executed and it requires the indexPath of each cell as a parameter. The return type of this function will be URLSessionDataTask.

      func getTask(forIndex: IndexPath) -> URLSessionDataTask {
            let imgURL = urlComponents(index: forIndex.row)
            return URLSession.shared.dataTask(with: imgURL) { data, response, error in
                guard let data = data, error == nil else { return }
    
                DispatchQueue.main.async() {
                    let image = UIImage(data: data)!
                    self.imageArray[forIndex.row] = image
                    self.collectionView.reloadItems(at: [forIndex])
                }
            }
        }

    Make sure you have added these lines in viewDidLoad() method:

    collectionView.dataSource = self
    collectionView.delegate = self
    collectionView.prefetchDataSource = self

    Implementing required methods

    Now , we must implement 3 required methods :

    • collectionView(_:numberOfItemsInSection:)
    • collectionView(_:cellForItemAt:)
    • collectionView(_:prefetchItemsAt:)

    UICollectionView must know how many rows are going to be inside the section, so we’ll return the count of the elements in our array here:

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return imageArray.count
    }

    Before coming to the most important methods, there is only one more thing left to do. Let’s create a method where we can supervise the downloading process.
    Once again the parameter required is the indexPath of the cell.

     func requestImage(forIndex: IndexPath) {
            var task: URLSessionDataTask
    
            if imageArray[forIndex.row] != nil {
                // Image is already loaded
                return
            }
    
            if tasks[forIndex.row] != nil
                && tasks[forIndex.row]!.state == URLSessionTask.State.running {
                // Wait for task to finish
                return
            }
    
            task = getTask(forIndex: forIndex)
            tasks[forIndex.row] = task
            task.resume()
        }
    }

    Finally, is time to build and return each cell. So in this method :

        func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    
            let foodCell = collectionView.dequeueReusableCell(withReuseIdentifier: "foodCell", for: indexPath) as! collectionViewCell
    
            if let img = imageArray[indexPath.row] {
                foodCell.foodImage.image = img
            }
            else {
                requestImage(forIndex: indexPath)
            }
    
            return foodCell
        }

    As we see the requestImage(forIndex: IndexPath) method helps us to observe if an image is already downloaded,is in process or is not downloaded at all.

    Prefetching

    In order for your app to have “buttery smooth” performance, you must strive for app animation that performs at 60 frames per second. This means that a given frame of the user interface must be displayed in less than 16.67ms in order for the animation to appear “smooth.” Otherwise, when the frame rate drops lower than that, it’s apparent to the user in the form of a choppy animation.

    Prefetching is a mechanism by which you are notified early before the collection view will need the cells to be displayed, thus providing an opportunity to prepare the cell’s data source in advance of actually creating the cell. Prefetching is provided by the UICollectionViewDataSourcePrefetching protocol and contains two instance functions:

    public protocol UICollectionViewDataSourcePrefetching : NSObjectProtocol {
    
    // indexPaths are ordered ascending by geometric distance from the collection view
    
    @available(iOS 10.0, *)
    public func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath])
    
    
    // indexPaths that previously were considered as candidates for pre-fetching, but were not actually used; may be a subset of the previous call to -collectionView:prefetchItemsAtIndexPaths:
    
    @available(iOS 10.0, *)
    optional public func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath])
    }
    

    Implementing DataSource Prefetching

    The first and required method is called when the Collection View is ready to start building out cells even before they are on the screen. An array of NSIndexPath objects is passed to the function and is used to prepare the data source. In our code we would write something like this:

        func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
    
            for indexPath in indexPaths{
    
                requestImage(forIndex: indexPath)
            }
    
        }

    Implementing Prefetch Cancellation

    As mentioned above, the second method, collectionView(_:cancelPrefetchingForItemsAt:), is an optional feature that allows you to revert or clean up your data source when prefetching for an array of cells has been cancelled by the Collection View. This can happen when scrolling changes direction or becomes too rapid for prefetching to be implemented effectively. So let’s implement the last function in our code:

    
        func collectionView(_ collectionView: UICollectionView, cancelPrefetchingForItemsAt indexPaths: [IndexPath]) {
            for indexPath in indexPaths{
                if let task = tasks[indexPath.row] {
                    if task.state != URLSessionTask.State.canceling {
                        task.cancel()
                    }
                }
            }
        }

    There may be times when you want to disable collection view prefetching. This can be done by setting the UICollectionView isPrefetchingEnabled property to false.

    These new functions can also be used in UITableView as well just by implementing UITableViewDataSourcePrefetching protocol.

    Conclusion

    Using this protocol and its functions is a good choice . There’s no more need to worry about the performance of cells as they are ready to come to the screen before being seen and with a smooth scrolling performance, our application feels better.

    Frequently Asked Questions about UICollectionViewDataSourcePrefetching

    What is UICollectionViewDataSourcePrefetching and how does it work?

    UICollectionViewDataSourcePrefetching is a protocol in iOS that allows you to prefetch data for a UICollectionView. This means that the data is loaded before it is actually needed, which can significantly improve the user experience by reducing lag and loading times. The protocol works by calling the prefetchItemsAt method, which tells the UICollectionView which items to prefetch. This method is called well in advance of when the data will be displayed, allowing the UICollectionView to load the data in the background.

    How can I implement UICollectionViewDataSourcePrefetching in my app?

    To implement UICollectionViewDataSourcePrefetching in your app, you first need to conform to the UICollectionViewDataSourcePrefetching protocol. This can be done by adding UICollectionViewDataSourcePrefetching to the list of protocols your class conforms to. Then, you need to implement the required methods, which are prefetchItemsAt and cancelPrefetchingForItemsAt. The prefetchItemsAt method is where you specify which items to prefetch, while the cancelPrefetchingForItemsAt method is used to cancel any prefetching that is no longer needed.

    What are the benefits of using UICollectionViewDataSourcePrefetching?

    UICollectionViewDataSourcePrefetching can significantly improve the user experience of your app by reducing lag and loading times. By prefetching data, you can ensure that the data is ready to be displayed as soon as it is needed, rather than having to wait for it to load. This can make your app feel much more responsive and smooth, particularly when dealing with large amounts of data or complex layouts.

    Are there any downsides to using UICollectionViewDataSourcePrefetching?

    While UICollectionViewDataSourcePrefetching can greatly improve the user experience, it does come with some potential downsides. One of these is increased memory usage, as the prefetched data needs to be stored somewhere. This can be a problem if you’re prefetching a large amount of data or if your app is running on a device with limited memory. Additionally, if the prefetched data is not used, it can lead to wasted resources.

    How can I manage memory usage when using UICollectionViewDataSourcePrefetching?

    To manage memory usage when using UICollectionViewDataSourcePrefetching, you can implement the cancelPrefetchingForItemsAt method. This method is called when the UICollectionView determines that the prefetched data is no longer needed, allowing you to free up the memory that was used to store it. Additionally, you can be selective about what data you prefetch, focusing on the data that is most likely to be needed soon.

    Can UICollectionViewDataSourcePrefetching be used with other data sources, like CoreData or a remote server?

    Yes, UICollectionViewDataSourcePrefetching can be used with any data source, including CoreData and remote servers. The prefetchItemsAt method is where you specify which items to prefetch, and this can be done regardless of where the data is coming from. However, when dealing with remote data, you’ll need to consider the potential for network latency and handle it appropriately.

    How does UICollectionViewDataSourcePrefetching handle errors?

    UICollectionViewDataSourcePrefetching does not provide any built-in error handling. If an error occurs while prefetching data, it’s up to you to handle it. This can be done in the prefetchItemsAt method, where you can add error handling code to deal with any issues that might arise.

    Can UICollectionViewDataSourcePrefetching be used with UICollectionViewDiffableDataSource?

    Yes, UICollectionViewDataSourcePrefetching can be used with UICollectionViewDiffableDataSource. The two protocols can work together to provide a smooth and efficient user experience. UICollectionViewDiffableDataSource handles the data management, while UICollectionViewDataSourcePrefetching handles the prefetching of data.

    How does UICollectionViewDataSourcePrefetching compare to other prefetching techniques?

    UICollectionViewDataSourcePrefetching is a powerful and flexible prefetching technique that can greatly improve the user experience of your app. It allows you to prefetch data for a UICollectionView, reducing lag and loading times. While there are other prefetching techniques available, UICollectionViewDataSourcePrefetching is built into UIKit and is designed to work seamlessly with UICollectionView.

    Can UICollectionViewDataSourcePrefetching be used with UITableView?

    Yes, UICollectionViewDataSourcePrefetching can be used with UITableView. The protocol works in the same way, allowing you to prefetch data for a UITableView. This can significantly improve the user experience by reducing lag and loading times, particularly when dealing with large amounts of data or complex layouts.