Skip to content

Instantly share code, notes, and snippets.

@piyushdec
Last active July 28, 2019 21:49
Show Gist options
  • Save piyushdec/0e18c241d931e56a00d796974ebff211 to your computer and use it in GitHub Desktop.
Save piyushdec/0e18c241d931e56a00d796974ebff211 to your computer and use it in GitHub Desktop.
class MainViewController: UIViewController {
var imageRecords: [ImageRecord] = []
let pendingOperations = PendingOperations()
}
extension MainViewController: UITableViewDataSource {
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath)
let imageDetails = imageRecords[indexPath.row]
cell.textLabel?.text = imageDetails.name
cell.imageView?.image = imageDetails.image
switch imageDetails.state {
case .failed:
cell.textLabel?.text = "Failed to load"
case .new, .downloaded:
//You tell the table view to start operations only if the table view is not scrolling.
//These are actually properties of UIScrollView and, because UITableView is a subclass of UIScrollView,
//table views automatically inherit these properties.
if !tableView.isDragging && !tableView.isDecelerating {
startDownload(for: imageDetails, at: indexPath)
}
}
return cell
}
}
extension MainViewController {
func fetchImageDataSource() {
let request = URLRequest(url: "https://your-server-url-to-fetch-endpoints")
let task = URLSession(configuration: .default).dataTask(with: request) { data, response, error in
self.imageRecords.append(record0...<recordn)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
task.resume()
}
func startDownload(for imageRecord: ImageRecord, at indexPath: IndexPath) {
guard pendingOperations.downloadsInProgress[indexPath] == nil else {
return
}
let downloader = ImageDownloadHelper(imageRecord)
downloader.completionBlock = {
if downloader.isCancelled {
return
}
DispatchQueue.main.async {
self.pendingOperations.downloadsInProgress.removeValue(forKey: indexPath)
self.tableView.reloadRows(at: [indexPath], with: .auto)
}
}
pendingOperations.downloadsInProgress[indexPath] = downloader
pendingOperations.downloadQueue.addOperation(downloader)
}
}
extension MainViewController {
override func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
suspendAllOperations()
}
override func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
loadImagesForVisibleCells()
resumeAllOperations()
}
}
override func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
loadImagesForVisibleCells()
resumeAllOperations()
}
}
extension MainViewController {
func suspendAllOperations() {
pendingOperations.downloadQueue.isSuspended = true
}
func resumeAllOperations() {
pendingOperations.downloadQueue.isSuspended = false
}
func loadImagesForVisibleCells() {
if let pathsArray = tableView.indexPathsForVisibleRows {
var allPendingOperations = Set(pendingOperations.downloadsInProgress.keys)
//get cancellable indexpaths by deleting all visible indexpaths
var toBeCancelledIndexPaths = allPendingOperations
let visibleIndexPaths = Set(pathsArray)
toBeCancelledIndexPaths.subtract(visibleIndexPaths)
//get resumable indexpaths by deleting all other indexpaths
var toBeStartedIbdexPaths = visibleIndexPaths
toBeStartedIbdexPaths.subtract(allPendingOperations)
//iterate all cancellable indexpaths and cancel them
for indexPath in toBeCancelledIndexPaths {
if let pendingDownload = pendingOperations.downloadsInProgress[indexPath] {
pendingDownload.cancel()
}
pendingOperations.downloadsInProgress.removeValue(forKey: indexPath)
}
//iterate all resumable indexpaths and start them again
for indexPath in toBeStartedIndexPaths {
let recordToProcess = imageRecords[indexPath.row]
startDownload(for: recordToProcess, at: indexPath)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment