Last active
February 19, 2020 04:55
-
-
Save hectorddmx/8fb7b3d484ac63a42a590d8b2d3108c0 to your computer and use it in GitHub Desktop.
TableView example with canceled data task
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TVShowsViewController.swift | |
// TVShows | |
// | |
// Created by Hector de Diego on 2/22/19. | |
// Copyright © 2019 hector.dd. All rights reserved. | |
// | |
import UIKit | |
class TVShowsViewController: BaseViewController { | |
let networkingService = NetworkingService() | |
// MARK: - Outlets | |
@IBOutlet weak var showsTableView: UITableView! | |
// MARK: - Properties | |
enum ScreenState { | |
case empty | |
case loading | |
case error(Error) | |
case populated([TVShow]) | |
} | |
var currentTVShows: [TVShow] = [] | |
var tvShow: TVShow? | |
public var screenState: ScreenState = ScreenState.empty { | |
didSet { | |
switch screenState { | |
case .populated(let tvShows): | |
currentTVShows = tvShows | |
case .error(let error): | |
handleErrorMessage(vc: self, error: error) { | |
[weak self] _ in | |
guard let self = self else { return } | |
self.loadShows() | |
} | |
fallthrough | |
default: | |
currentTVShows = [] | |
} | |
showsTableView.reloadData() | |
} | |
} | |
func loadShows() { | |
networkingService.fetchTVShows() { [weak self] response in | |
guard let self = self else { return } | |
self.update(response: response) | |
} | |
} | |
func update(response: TVShowsModel) { | |
if let error = response.error { | |
screenState = .error(error) | |
return | |
} | |
guard let tvShows = response.tvShows, !tvShows.isEmpty else { | |
screenState = .empty | |
return | |
} | |
screenState = .populated(tvShows) | |
} | |
// MARK: - Configuration | |
override func prepareNavigationBar() { | |
super.prepareNavigationBar() | |
navigationItem.title = "TV Shows" | |
} | |
private func prepareShowsTableView() { | |
showsTableView.delegate = self | |
showsTableView.dataSource = self | |
let nib = UINib(nibName: TVShowTableViewCell.nibName, bundle: .main) | |
showsTableView.register(nib, forCellReuseIdentifier: TVShowTableViewCell.reuseIdentifier) | |
showsTableView.register( | |
nib, | |
forCellReuseIdentifier: TVShowTableViewCell.reuseIdentifier | |
) | |
} | |
// MARK: - Lifecycle | |
override func viewDidLoad() { | |
super.viewDidLoad() | |
prepareNavigationBar() | |
prepareShowsTableView() | |
loadShows() | |
} | |
// MARK: - Navigation | |
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { | |
if | |
segue.identifier == Segues.tvShowDetailSegue.rawValue, | |
let destination = segue.destination as? TVShowDetailViewController { | |
destination.tvShow = tvShow | |
} | |
} | |
} | |
// MARK: - Delegates | |
extension TVShowsViewController: UITableViewDelegate { | |
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { | |
tvShow = currentTVShows[indexPath.row] | |
performSegue(withIdentifier: Segues.tvShowDetailSegue.rawValue, sender: self) | |
} | |
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { | |
} | |
fileprivate func buildUnfavoriteAction() -> [UITableViewRowAction]? { | |
let unfavoriteAction = UITableViewRowAction( | |
style: .normal, title: "Delete") { [weak self] (rowAction, indexPath) in | |
guard let self = self else { return } | |
self.showActionAlert(vc: self) { [weak self] _ in | |
guard let self = self else { return } | |
self.currentTVShows[indexPath.row].setFavoriteStatus(favorite: false) | |
} | |
} | |
unfavoriteAction.backgroundColor = UIColor(rgb: Colors.unfavorite.rawValue) | |
return [unfavoriteAction] | |
} | |
fileprivate func buildFavoriteAction() -> [UITableViewRowAction]? { | |
let favoriteAction = UITableViewRowAction( | |
style: .normal, title: "Favorite") { [weak self] (rowAction, indexPath) in | |
guard let self = self else { return } | |
self.currentTVShows[indexPath.row].setFavoriteStatus(favorite: true) | |
} | |
favoriteAction.backgroundColor = UIColor(rgb: Colors.favorite.rawValue) | |
return [favoriteAction] | |
} | |
func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? { | |
if currentTVShows[indexPath.row].isFavorite { | |
return buildUnfavoriteAction() | |
} else { | |
return buildFavoriteAction() | |
} | |
} | |
} | |
extension TVShowsViewController: UITableViewDataSource { | |
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { | |
return currentTVShows.count | |
} | |
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | |
guard let showCell: TVShowTableViewCell = tableView.dequeueReusableCell( | |
withIdentifier: TVShowTableViewCell.reuseIdentifier, | |
for: indexPath) as? TVShowTableViewCell | |
else { return UITableViewCell() } | |
showCell.load(tvShow: currentTVShows[indexPath.row]) | |
return showCell | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TVShowTableViewCell.swift | |
// TVShows | |
// | |
// Created by Hector de Diego on 2/22/19. | |
// Copyright © 2019 hector.dd. All rights reserved. | |
// | |
import UIKit | |
class TVShowTableViewCell: UITableViewCell { | |
var task: URLSessionTask? // 1. The task that needs to be canceled when reusing the cell | |
static let reuseIdentifier = String(describing: TVShowTableViewCell.self) | |
static let nibName = String(describing: TVShowTableViewCell.self) | |
@IBOutlet weak var showThumbImageView: UIImageView! | |
@IBOutlet weak var showTitleLabel: UILabel! | |
override func awakeFromNib() { | |
super.awakeFromNib() | |
selectionStyle = .none | |
} | |
override func setSelected(_ selected: Bool, animated: Bool) { | |
super.setSelected(selected, animated: animated) | |
} | |
override func prepareForReuse() { | |
showTitleLabel.text = "" | |
showThumbImageView.image = UIImage() | |
} | |
func load(tvShow: TVShow) { | |
self.task?.cancel() // 2. The task should be canceled before anything else is configured in my cell | |
// Canceling the task will avoid the image from continue downloading and being loaded some seconds later.... | |
if let name = tvShow.name { | |
showTitleLabel.text = name | |
} | |
if let image = tvShow.image, | |
let mediumImage = image.medium { | |
loadImage(fromURL: mediumImage) // 3. The image will be downloaded if we have a URL string... | |
} | |
} | |
func load(tvShowStore: TVShowStore) { | |
self.task?.cancel() | |
showTitleLabel.text = "" | |
showThumbImageView.image = UIImage() | |
if !tvShowStore.name.isEmpty { | |
showTitleLabel.text = tvShowStore.name | |
} | |
if !tvShowStore.imageMedium.isEmpty { | |
loadImage(fromURL: tvShowStore.imageMedium) | |
} | |
} | |
// The function that loads my image in my imageView | |
// If there is a cached version, it will load that | |
// If not, it will download that and cache it. | |
public func loadImage(fromURL url: String) { | |
guard let imageURL: URL = URL(string: url) else { | |
return | |
} | |
let cache: URLCache = URLCache.shared | |
let request = URLRequest(url: imageURL) | |
DispatchQueue.global(qos: .userInitiated).async { | |
if | |
let data: Data = cache.cachedResponse(for: request)?.data, | |
let image: UIImage = UIImage(data: data) { | |
print("Getting cached image: \(url)") | |
DispatchQueue.main.async { | |
self.showThumbImageView.transition(toImage: image) | |
} | |
} else { | |
print("Requesting image: \(url)") | |
// 4. We need to set the task to be the current download, we do this by assigning the dataTask to this variable | |
self.task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in | |
if let data = data, | |
let response: URLResponse = response, | |
((response as? HTTPURLResponse)?.statusCode ?? 500) < 300, | |
let image: UIImage = UIImage(data: data) { | |
let cachedData: CachedURLResponse = CachedURLResponse( response: response, data: data) | |
cache.storeCachedResponse(cachedData, for: request) | |
DispatchQueue.main.async { | |
self.showThumbImageView.transition(toImage: image) | |
} | |
} | |
}) // 5. Instead of calling here resume() directly, we have to do that now in the variable | |
self.task?.resume() // 6. We call resume() so the download is sent. | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment