Listado series de televisión de la api TVMaze en UIKit
Refactorización de arquitectura VIPER a MVC con DiffableDataSource de las tablas de catalogue y favorites, conectando con la vista original detalle en VIPER, conversión de patrón callback a async await con continuations, test con URLProtocol y Mock de DataBase.
Añadido CatalogueViewController con diffableDataSource en MVC que sustituye a ShowCatalogue con VIPER
lazy var dataSource: UITableViewDiffableDataSource<Int,ShowModel> = {
UITableViewDiffableDataSource(tableView: tableView) { [self] tableView, indexPath, show in
if let cell = tableView.dequeueReusableCell(withIdentifier: "showCell", for: indexPath) as? CatalogueViewCell {
cell.imageLabel.imageFromUrl(urlString: show.image?.original ?? "", force: false, placeholder: nil)
cell.titleLabel.text = show.name
cell.ratingLabel.text = show.rating?.average != nil
? "Rating \(show.rating?.average ?? 0)"
: "No rating".localizable()
cell.genresLabel.text = show.genres?.sorted(by: <).joined(separator: ", ")
return cell
} else {
return UITableViewCell()
}
}
}()
Añadido método que transforma patrón callback en async await con continuations
func getShowsAsync() async throws -> [ShowModel] {
try await withCheckedThrowingContinuation { continuation in
getShows { data in
do {
let decodedData = try JSONDecoder().decode([ShowModel].self, from: data)
continuation.resume(returning: decodedData)
} catch let error {
continuation.resume(throwing: error)
}
} errorAnswer: { msg in
continuation.resume(throwing: Error.self as! Error)
}
}
}
Añadido URLSessionMock para UnitTests
private var session: URLSession {
if let urlProtocol {
let configuration = URLSessionConfiguration.ephemeral
configuration.protocolClasses = [urlProtocol]
return URLSession(configuration: configuration)
} else {
let config = URLSessionConfiguration.default
config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
return URLSession(configuration: config)
}
}
Añadido FavoriteViewController con diffableDataSource en MVC que sustituye a ShowFavorites con VIPER
Añadido DataBaseContainerProtocol para tests
protocol DataBaseContainer {
func fetchFavorites() throws -> [FavoriteModel]
func isFavorite(showId: Int) throws -> Bool
func saveFavorite(show: ShowModel) -> Bool
func deleteShow(id: Int) throws
func getFavoritesShows() throws -> [FavoriteShowModel]
}
final class DataBaseMock: DataBaseContainer {
var favoriteShows = [ShowModel]()
init() {
let url = Bundle(for: DataBaseMock.self).url(forResource: "ShowTests", withExtension: "json")!
do {
let data = try Data(contentsOf: url)
let shows = try JSONDecoder().decode([ShowModel].self, from: data)
guard let first = shows.first else { return }
self.favoriteShows = [first]
} catch {
return
}
}
func fetchFavorites() throws -> [FavoriteModel] {
favoriteShows.compactMap { FavoriteModel(show: $0) }
}
func isFavorite(showId: Int) throws -> Bool {
favoriteShows.contains(where: { $0.id == showId } )
}
func saveFavorite(show: ShowModel) -> Bool {
favoriteShows.append(show)
return true
}
func deleteShow(id: Int) throws {
favoriteShows.removeAll(where: { $0.id == id } )
}
func getFavoritesShows() throws -> [FavoriteShowModel] {
favoriteShows.compactMap { show in
if let id = show.id {
return FavoriteShowModel(id: id, show: show)
}
return nil
}
}
}
Añadido feature con swipe actions para añadir y quitar de favoritos
Añadida conexión a ShowDetailsViewController que permanece en VIPER desde CatalogueViewController y FavoriteViewController
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let navigationController else { return }
let show = modelLogic.getShow(for: indexPath)
let _ = ShowDetailsWireframe(navigationController: navigationController, show: show)
}
TVMaze App Example
Jose iOS Dev
¿Quieres recibir posts, cheatCodes, enlaces y katas en Swift para practicar?
Quincenalmente recibirás en tu correo electrónico la newsletter, solo hace falta tu correo electrónico.