I download an image per each cell I need to show in an UITableView
by calling an async network task. This is in the UIViewController
class of the table:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard results.count > 0 else {
return UITableViewCell()
}
let myCell = tableView.dequeueReusableCell(withIdentifier: CustomCell.cellIdentifier, for: indexPath) as! CustomCell
let model = results[indexPath.row]
myCell.model = model
return myCell
}
And this is CustomCell
:
class CustomCell: UITableViewCell {
// Several IBOutlets
static let cellIdentifier = "myCell"
let imageProvider = ImageProvider()
var model: MyModel? {
willSet {
activityIndicator.startAnimating()
configureImage(showImage: false, showActivity: true)
}
didSet {
guard let modelUrlStr = model?.imageUrlStr, let imageUrl = URL(string: modelUrlStr) else {
activityIndicator.stopAnimating()
configureImage(showImage: false, showActivity: false)
return
}
imageProvider.getImage(imageUrl: imageUrl, completion: {[weak self] (image, error) in
DispatchQueue.main.async {
guard error == nil else {
self?.activityIndicator.stopAnimating()
self?.configureImage(showImage: false, showActivity: false)
return
}
self?.imageView.image = image
self?.activityIndicator.stopAnimating()
self?.configureImage(showCoverImage: true, showActivity: false)
}
})
}
}
override func awakeFromNib() {
super.awakeFromNib()
configureImage(showCoverImage: false, showActivity: false)
}
override func prepareForReuse() {
super.prepareForReuse()
model = nil
}
private func configureImage(showImage: Bool, showActivity: Bool) {
// Update image view
}
}
And ImageProvider
:
class ImageProvider {
var imageTask: URLSessionDownloadTask?
func getImage(imageUrl: URL, completion: @escaping DownloadResult) {
imageTask?.cancel()
imageTask = NetworkManager.sharedInstance.getImageInBackground(imageUrl: imageUrl, completion: { (image, error) -> Void in
if let error = error {
completion(nil, error)
} else if let image = image {
completion(image, nil)
} else {
completion(nil, nil)
}
})
}
}
Since cells can be dynamically dequeued and reused and the download is async and then an image could be reused on each cell while scrolling, am I this way ensuring that each cell is always showing its corresponding image?
EDIT: Different approach
Is it appropriate to keep a reference to the model in the cell? Thinking about a correct MVC
architecture. Who should be the responsible for downloading the images? The cell (passing to it only the URL instead of the complete model object), or the table view's view controller (updating the image in the cell in its tableView:cellForRowAt:
method)?
ImageProvider
that would allow you to cancel the underlying URLSessionDownloadTask
. Call this method when the cell is about to be reused. For that, override prepareForReuse()
in your cell subclass.DispatchQueue.main.async
block is still enqueued and will be fired after cell reuse has happened. To mitigate this, you need to check the URL that the of the finished task against the URL that is stored in your model.