Loading a large number of images asynchronously in a scrollable view like UITableView or UICollectionView can be a common task. However, keeping the app responsive in terms of scrolling while images are being downloaded can be a bit of a challenge. In worst cases, we have also experienced app crashes. In light of this scenario, the issue is that the images in these cells are downloading on the UI thread one at a time and they aren’t being cached at all. Hence, the app malfunctions. Therefore, most developers seek the support of third-party libraries such as Alamofire, Kingfisher and SDWebImage to avoid eventual troubles of background image loading and the hassle of cache management.
The focus of this article is to provide an insight on how to download an image from a URL, send it through a memory cache so it can be displayed on image view without the involvement of a library. The trick behind this is to use the same code that is used to ‘save’ for retrieval in order to display it instantly.
There are two approaches to achieve this. One of which is the use of an extension and the other is the use of a subclass of UIImageView. In this article, the major focus will be the use of extensions.
To begin with, we need to create an NSObject class that will allow us to define static methods that will in turn allow us to cache the images.static func setImageViewImage(forImageView: UIImageView, withImageUrl: String){If the user intended to cache the image in a UITableView or UICollectionView, the below method can be used.
(forImageView).imageFromServerURL(urlString: withImageUrl)
}
static func setTableCellImageViewImage(cell: UITableViewCell, imageUrlString: Any,tableViewName:UITableView? = nil, indexpath:IndexPath? = nil){
if let imagePathString = imageUrlString as? String{
(cell.(imageView).imageFromServerURL(urlString: imagePathString, tableView: tableViewName, indexpath: indexpath)
} }
By calling these static methods wherever we intended to cache images, we could accomplish the desired objective. Once these methods are declared, we need to define the NSCache object reference in global scope for the object class we created, as below.
let imageCache = NSCache()
Thereafter, we need to declare the extension that will be used as to achieve our objective. Thus, other than the URL, we can have the other parameters as optional.
extension UIImageView {
public func imageFromServerURL(urlString: String,tableView:UITableView? = nil,indexpath:IndexPath? = nil) {
//the code goes here
}
The data will be downloaded using the URLSessionDataTask class. Hence, the “urlString” parameter will serve as the table view data source. The cache variable will be a reference to the cache dictionary that the app will use to request cached images -if they exist-before downloading them. Therefore, the final code snippet would appear as below.
extension UIImageView {
public func imageFromServerURL(urlString: String,tableView:UITableView? = nil,indexpath:IndexPath? = nil) {
var imageURLString : String?
imageURLString = urlString
if let url = URL(string: urlString) {
image = nil
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = imageFromCache return }
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if error != nil{
print(error as Any)
return
}
DispatchQueue.main.async(execute: {
if let imgaeToCache = UIImage(data: data!){
if imageURLString == urlString {
self.image = imgaeToCache
}
imageCache.setObject(imgaeToCache, forKey: urlString as AnyObject
}
})
}) .resume() }
}
}
The cache object is a collection, like a container, very similar to a NSDictionaryinstance. Here it is used as a collection of UIImage objects, where the key is the row index, which helps to keep track of the right cache image corresponding to each table cell. Therefore, you need to first check whether there is a cached copy of the current image. If a copy already exists, then you have to load it in the cell image view. Assuming the image is downloaded successfully, the code will switch back to the main thread in order to load it to the image view. This is important since all UI tasks should be performed in the main queue and not in a background thread.