I have a list of 5000 lines that stores url of images. When the list is loaded, links are stored inside dataItem.logo as Strings.
What is the best approach? Should images be downloaded and cached one by one when the list is loaded or to do this when a cell is displayed in UICollectionView?
I'm thinking that more than half of the images will never be displayed anyway and Im trying the second way.
I have this code from UIKit:
class ChannelCellComposer {
// MARK: Properties
/// Cache used to store processed images, keyed on `DataItem` identifiers.
static private var processedImageCache = NSCache<NSString, UIImage>()
/**
A dictionary of `NSOperationQueue`s for `DataItemCollectionViewCell`s. The
queues contain operations that process images for `DataItem`s before updating
the cell's `UIImageView`.
*/
private var operationQueues = [ChannelCollectionViewCell: OperationQueue]()
// MARK: Implementation
func compose(_ cell: ChannelCollectionViewCell, withDataItem dataItem: Song) {
// Cancel any queued operations to process images for the cell.
let queue = operationQueue(forCell: cell)
queue.cancelAllOperations()
// Set the cell's properties.
cell.representedDataItem = dataItem
cell.label.text = dataItem.title
cell.imageView.alpha = 1.0
cell.imageView.image = ChannelCellComposer.processedImageCache.object(forKey: dataItem.logo as NSString)
print(dataItem.logo)
// No further work is necessary if the cell's image view has an image.
guard cell.imageView.image == nil else { return }
/*
Initial rendering of a jpeg image can be expensive. To avoid stalling
the main thread, we create an operation to process the `DataItem`'s
image before updating the cell's image view.
The execution block is added after the operation is created to allow
the block to check if the operation has been cancelled.
*/
let processImageOperation = BlockOperation()
processImageOperation.addExecutionBlock { [unowned processImageOperation] in
// Ensure the operation has not been cancelled.
guard !processImageOperation.isCancelled else { return }
// Load and process the image.
guard let image = self.processImage(named: dataItem.logo) else { return }
// Store the processed image in the cache.
ChannelCellComposer.processedImageCache.setObject(image, forKey: dataItem.logo as NSString)
OperationQueue.main.addOperation {
// Check that the cell is still showing the same `DataItem`.
guard dataItem == cell.representedDataItem else { return }
// Update the cell's `UIImageView` and then fade it into view.
cell.imageView.alpha = 0.0
cell.imageView.image = image
UIView.animate(withDuration: 0.25) {
cell.imageView.alpha = 1.0
}
}
}
queue.addOperation(processImageOperation)
}
// MARK: Convenience
/**
Returns the `NSOperationQueue` for a given cell. Creates and stores a new
queue if one doesn't already exist.
*/
private func operationQueue(forCell cell: ChannelCollectionViewCell) -> OperationQueue {
if let queue = operationQueues[cell] {
return queue
}
print("operationQueue")
let queue = OperationQueue()
operationQueues[cell] = queue
return queue
}
/**
Loads a UIImage for a given name and returns a version that has been drawn
into a `CGBitmapContext`.
*/
private func processImage(named imageName: String) -> UIImage? {
// Load the image.
guard let image = UIImage(named: imageName) else { return nil }
print("process Imaage")
/*
We only need to process jpeg images. Do no processing if the image
name doesn't have a jpg suffix.
*/
// guard imageName.hasSuffix(".jpg") else { return image }
// Create a `CGColorSpace` and `CGBitmapInfo` value that is appropriate for the device.
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
// Create a bitmap context of the same size as the image.
let imageWidth = Int(Float(image.size.width))
let imageHeight = Int(Float(image.size.height))
let bitmapContext = CGContext(data: nil, width: imageWidth, height: imageHeight, bitsPerComponent: 8, bytesPerRow: imageWidth * 4, space: colorSpace, bitmapInfo: bitmapInfo)
// Draw the image into the graphics context.
guard let imageRef = image.cgImage else { fatalError("Unable to get a CGImage from a UIImage.") }
bitmapContext?.draw(imageRef, in: CGRect(origin: CGPoint.zero, size: image.size))
// Create a new `CGImage` from the contents of the graphics context.
guard let newImageRef = bitmapContext?.makeImage() else { return image }
// Return a new `UIImage` created from the `CGImage`.
return UIImage(cgImage: newImageRef)
}
}
What I want to do for now is to download the image from url and then replace the string from dataItem.logo with the image object. I think downloading will be done by using try? Data(contentsOf: dataItem.logo)
but how can I replace the dataItem.logo (String) with dataItem.logo (image)