TV Maze App Example (Fork)

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.