Repository: thiagolioy/marvelapp Branch: master Commit: ad1af1f5cb77 Files: 67 Total size: 261.0 KB Directory structure: gitextract_9es8icdz/ ├── .gitignore ├── .ruby-version ├── .travis.yml ├── Dangerfile ├── Gemfile ├── LICENSE ├── Marvel/ │ ├── AppDelegate/ │ │ └── AppDelegate.swift │ ├── ApperanceProxyHelper.swift │ ├── Cells/ │ │ ├── CharacterCollectionCell.swift │ │ ├── CharacterTableCell.swift │ │ └── Xibs/ │ │ ├── CharacterCollectionCell.xib │ │ └── CharacterTableCell.xib │ ├── Controllers/ │ │ ├── CharacterViewController.swift │ │ └── CharactersViewController.swift │ ├── Datasources/ │ │ ├── CharactersCollectionDatasource.swift │ │ ├── CharactersDatasource.swift │ │ ├── ItemsCollectionViewDatasource.swift │ │ └── ItemsTableViewDatasource.swift │ ├── Models/ │ │ ├── Character.swift │ │ └── ThumbImage.swift │ ├── Network/ │ │ ├── MarvelAPI.swift │ │ └── MarvelAPIManager.swift │ ├── Resources/ │ │ ├── Assets.xcassets/ │ │ │ ├── AppIcon.appiconset/ │ │ │ │ └── Contents.json │ │ │ ├── Contents.json │ │ │ ├── Grid Icon.imageset/ │ │ │ │ └── Contents.json │ │ │ └── List Icon.imageset/ │ │ │ └── Contents.json │ │ ├── ColorPalette.swift │ │ ├── Info.plist │ │ └── Storyboard.swift │ ├── Storyboards/ │ │ └── Base.lproj/ │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ └── UIImageView+Kingfisher.swift ├── Marvel.xcodeproj/ │ ├── project.pbxproj │ ├── project.xcworkspace/ │ │ └── contents.xcworkspacedata │ └── xcshareddata/ │ └── xcschemes/ │ └── Marvel.xcscheme ├── MarvelTests/ │ ├── CharacterSpec.swift │ ├── CharacterViewControllerSpec.swift │ ├── CharactersCollectionDatasourceSpec.swift │ ├── CharactersCollectionDelegateSpec.swift │ ├── CharactersDatasourceSpec.swift │ ├── CharactersDelegateSpec.swift │ ├── CharactersViewControllerSpec.swift │ ├── Info.plist │ ├── MockLoader.swift │ ├── ThumbImageSpec.swift │ ├── character.json │ └── characters_response.json ├── Podfile ├── README.md ├── coverage/ │ ├── AppDelegate.swift.html │ ├── Character.swift.html │ ├── CharacterCollectionCell.swift.html │ ├── CharacterTableCell.swift.html │ ├── CharacterViewController.swift.html │ ├── CharactersCollectionDatasource.swift.html │ ├── CharactersDatasource.swift.html │ ├── CharactersViewController.swift.html │ ├── ItemsCollectionViewDatasource.swift.html │ ├── ItemsTableViewDatasource.swift.html │ ├── ThumbImage.swift.html │ ├── UIImageView+Kingfisher.swift.html │ ├── highlight.pack.js │ ├── index.html │ └── slather.css └── fastlane/ ├── Appfile ├── Fastfile └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Xcode # # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore ## Build generated build/ DerivedData/ ## Various settings *.pbxuser !default.pbxuser *.mode1v3 !default.mode1v3 *.mode2v3 !default.mode2v3 *.perspectivev3 !default.perspectivev3 xcuserdata/ ## Other *.moved-aside *.xcuserstate ## Obj-C/Swift specific *.hmap *.ipa *.dSYM.zip *.dSYM ## Playgrounds timeline.xctimeline playground.xcworkspace # Swift Package Manager # # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. # Packages/ .build/ # CocoaPods # # We recommend against adding the Pods directory to your .gitignore. However # you should judge for yourself, the pros and cons are mentioned at: # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control # Pods/ **/Marvel.xcworkspace/ # Carthage # # Add this line if you want to avoid checking in source code from Carthage dependencies. # Carthage/Checkouts Carthage/Build # fastlane # # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the # screenshots whenever they are needed. # For more information about the recommended setup visit: # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md **/fastlane/report.xml **/fastlane/Preview.html **/fastlane/screenshots **/fastlane/test_output ================================================ FILE: .ruby-version ================================================ 2.3.0 ================================================ FILE: .travis.yml ================================================ language: objective-c osx_image: xcode8.1 cache: - bundler - cocoapods before_install: - bundle install - bundle exec pod keys set MarvelApiKey $MARVEL_API_SECRET Marvel - bundle exec pod keys set MarvelPrivateKey $MARVEL_API_PUBLIC Marvel - pod repo update script: - fastlane test - bundle exec danger after_success: - bash <(curl -s https://codecov.io/bash) env: global: - secure: iQP72QQ4U3N8v2qLSiU1lcPv9enJAMFY9Bs/5MxI6D8gmOlVreEZloKVW4zS2pLcJITlttXiCLGCwi203DxbfnfXcdeEr04IsbWNDWv2NggtdSK1A/iQZDVIJGss0Qgvi6JW7+BVaPemCMhlndWxo4l0qkpj5jNvwS8bPWSPKJ1MB6AZlMQShktUlDYWujMcx3AfTgczLJbhiFrP8gpa6XCgFOu7lzlw6C+aqvRQgywUeLL9MKWge3OVBZnOMFtStB2ehUeKxvcp6HXOBIGaG8FVVef/NI+YXyLlX4wwLkuUsnR61lO/hEDMtL59ZE6SKOXDhXi00GyITT/E1k0sa/3caMgnQ5apHQxjN5y5vhc96CqEYnMyyAnwsgAktXpHqy5qU7TUJQ4EOfg67oFHFcXXqBbvFIh1AVM15e8oMZdEs/9GrDllm2KTY57FznO+XqqbmzLeFudaoVeWnXWsmy/M68RQZNp6cdRsBU+wVHu2k8uNwaIF91ouHm9sXJLcICFgvGZ/mg5r4nWza9Hu0gRk8FWLVNQtahjboS2OMAn+ZvyybjOungMvZoXU4ZJcXV4izAw6WIdUZgrU5GmzknWNwO1x8+AhJ7Lo/pkCsm22zV6HwB41sIo1CvJRfB8f8UvL7YPtU1DURNn7KIdju2C8jdi8Ae8onfkMknJQhNo= - secure: ZCih/6Pkg6kxc3V/GVfe29KYN1fL9EKVqdGz9vqMgfFyivDsz7a/WXXqoYBqyBXJ0Z9tT+13GZG1QidcZeLoxJ+RAvVvhtZhp4PBH4sa9dsy4laWZkA9hIRmgfoMR3M1Cuab+twmXBz/rPXkTWeOk9dBRlXe4BP5/QfzwrTz9NApezCV1vsGLGFVulQrClHE0USMA0eE6IiAACM7EsT4I4q063zJxQWAAsly9i/hbsL3J+1lUpqE4UcNamou/1Ri07VEFWYzM5Y87+dLB6pD1e96Cj7KjNfQVwBK9uw2K17i4oQt/kgwx8J2A4zdzCPF0kzu54Jmw6hUUrlv7sVscUfKKDPwtdqlQpS/ga7NgV43OzrhNrnnnjZQi7RRJAWvB33zsOjouEab9MVUS5vv6zi5uEUQj4eADgUlGbrhz9bw0Qdj2oX6UUWHnpyt5hS88cLqymZ5VLfvNwnrY3ZUzoGvFFu09KPrL1eUIgccanrpFqQHC0zqQZRiH1qKWH0dq8jgQNYE766mU1zypPge9wZW26dORatb8vat+lalt47wz38B2Wq818FgCN3PeeMyDr0xZoWInY0jTRB1eZzRhrVIX39MpyQ916bScCFr4Ex9biORiio0anUWUox1dyqSHvT/ZRpEdLf5YU2FhySh+9rEcAvluctXyHZvk5hYZUI= ================================================ FILE: Dangerfile ================================================ # Sometimes it's a README fix, or something like that - which isn't relevant for # including in a project's CHANGELOG for example declared_trivial = github.pr_title.include? "#trivial" # Make it more obvious that a PR is a work in progress and shouldn't be merged yet warn("PR is classed as Work in Progress") if github.pr_title.include? "[WIP]" # Warn when there is a big PR warn("Big PR") if git.lines_of_code > 500 message("Test Comment on PR") # Don't let testing shortcuts get into master by accident # fail("fdescribe left in tests") if `grep -r fdescribe specs/ `.length > 1 # fail("fit left in tests") if `grep -r fit specs/ `.length > 1 # Slater config slather.configure("Marvel.xcodeproj", "Marvel", options: { workspace: 'Marvel.xcworkspace', output_directory: "coverage", ignore_list: [ "**/Storyboard.swift", "**/MarvelAPI.swift", "**/MarvelAPIManager.swift" ], ci_service: :travis, coverage_service: :terminal, }) slather.notify_if_coverage_is_less_than(minimum_coverage: 80) slather.notify_if_modified_file_is_less_than(minimum_coverage: 60) slather.show_coverage ================================================ FILE: Gemfile ================================================ # frozen_string_literal: true source "https://rubygems.org" # gem "rails" gem "cocoapods", "1.1.1" gem "cocoapods-keys", "1.7.0" gem "slather", "2.3.0" gem "fastlane", "1.110.0" gem 'danger' gem "danger-slather" ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2016 Thiago Lioy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Marvel/AppDelegate/AppDelegate.swift ================================================ // // AppDelegate.swift // Marvel // // Created by Thiago Lioy on 14/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. ApperanceProxyHelper.customizeNavigationBar() return true } } ================================================ FILE: Marvel/ApperanceProxyHelper.swift ================================================ // // ApperanceProxyHelper.swift // Marvel // // Created by Thiago Lioy on 11/12/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import UIKit struct ApperanceProxyHelper { static func customizeNavigationBar() { let navigationBarAppearace = UINavigationBar.appearance() navigationBarAppearace.tintColor = ColorPalette.white navigationBarAppearace.titleTextAttributes = [NSForegroundColorAttributeName:ColorPalette.white] } } ================================================ FILE: Marvel/Cells/CharacterCollectionCell.swift ================================================ // // CharacterCollectionCell.swift // Marvel // // Created by Thiago Lioy on 20/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import UIKit import Reusable final class CharacterCollectionCell: UICollectionViewCell, NibReusable { @IBOutlet weak var name: UILabel! @IBOutlet weak var thumb: UIImageView! static func size(for parentWidth: CGFloat) -> CGSize { let numberOfCells = CGFloat(2) let width = parentWidth / numberOfCells return CGSize(width: width, height: width) } func setup(item: Character) { name.text = item.name thumb.download(image: item.thumImage?.fullPath() ?? "") } } ================================================ FILE: Marvel/Cells/CharacterTableCell.swift ================================================ // // CharacterTableCell.swift // Marvel // // Created by Thiago Lioy on 15/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import UIKit import Reusable final class CharacterTableCell: UITableViewCell, NibReusable { @IBOutlet weak var name: UILabel! @IBOutlet weak var characterDescription: UILabel! @IBOutlet weak var thumb: UIImageView! static func height() -> CGFloat { return 80 } func setup(item: Character) { name.text = item.name characterDescription.text = item.bio.isEmpty ? "No description" : item.bio thumb.download(image: item.thumImage?.fullPath() ?? "") } } ================================================ FILE: Marvel/Cells/Xibs/CharacterCollectionCell.xib ================================================ ================================================ FILE: Marvel/Cells/Xibs/CharacterTableCell.xib ================================================ ================================================ FILE: Marvel/Controllers/CharacterViewController.swift ================================================ // // CharacterViewController.swift // Marvel // // Created by Thiago Lioy on 14/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import UIKit final class CharacterViewController: UIViewController { @IBOutlet weak var characterDescription: UILabel! @IBOutlet weak var image: UIImageView! var character: Character? } extension CharacterViewController { override func viewDidLoad() { super.viewDidLoad() setupView() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.navigationItem.title = character?.name ?? "" } } extension CharacterViewController { func setupView() { let bio = character?.bio ?? "" characterDescription.text = bio.isEmpty ? "No description" : bio image.download(image: character?.thumImage?.fullPath() ?? "") } } ================================================ FILE: Marvel/Controllers/CharactersViewController.swift ================================================ // // CharactersViewController.swift // Marvel // // Created by Thiago Lioy on 14/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import UIKit protocol CharactersDelegate { func didSelectCharacter(at index: IndexPath) } final class CharactersViewController: UIViewController { var apiManager: MarvelAPICalls = MarvelAPIManager() var tableDatasource: CharactersDatasource? var tableDelegate: CharactersTableDelegate? var collectionDatasource: CharactersCollectionDatasource? var collectionDelegate: CharactersCollectionDelegate? var characters: [Character] = [] var showingAsList = true @IBOutlet weak var searchBar: UISearchBar! @IBOutlet weak var activityIndicator: UIActivityIndicatorView! @IBOutlet weak var tableView: UITableView! @IBOutlet weak var collectionView: UICollectionView! } extension CharactersViewController { override func viewDidLoad() { super.viewDidLoad() setupSearchBar() fetchCharacters() } } extension CharactersViewController { func fetchCharacters(for query: String? = nil) { tableView.isHidden = true collectionView.isHidden = true activityIndicator.startAnimating() apiManager.characters(query: query) { characters in self.activityIndicator.stopAnimating() if let characters = characters { if self.showingAsList { self.setupTableView(with: characters) } else { self.setupCollectionView(with: characters) } } } } func setupSearchBar() { self.searchBar.delegate = self } func setupTableView(with characters: [Character]) { self.characters = characters showingAsList = true tableView.isHidden = false collectionView.isHidden = true tableDelegate = CharactersTableDelegate(self) tableDatasource = CharactersDatasource(items: characters, tableView: self.tableView, delegate: tableDelegate!) } func setupCollectionView(with characters: [Character]) { self.characters = characters showingAsList = false collectionView.isHidden = false tableView.isHidden = true collectionDelegate = CharactersCollectionDelegate(self) collectionDatasource = CharactersCollectionDatasource(items: characters, collectionView: self.collectionView, delegate: collectionDelegate!) } } extension CharactersViewController { @IBAction func showAsGrid(_ sender: UIButton) { setupCollectionView(with: characters) } @IBAction func showAsTable(_ sender: UIButton) { setupTableView(with: characters) } } extension CharactersViewController: CharactersDelegate { func didSelectCharacter(at index: IndexPath) { searchBar.resignFirstResponder() guard let nextController = Storyboard.Main.characterViewControllerScene .viewController() as? CharacterViewController else { return } let character = characters[index.row] nextController.character = character self.navigationController?.pushViewController(nextController, animated: true) } } extension CharactersViewController: UISearchBarDelegate { func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { searchBar.resignFirstResponder() let query = searchBar.text ?? "" if !query.isEmpty { fetchCharacters(for: query) } } func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { searchBar.resignFirstResponder() } } ================================================ FILE: Marvel/Datasources/CharactersCollectionDatasource.swift ================================================ // // CharactersCollectionDatasource.swift // Marvel // // Created by Thiago Lioy on 20/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import UIKit final class CharactersCollectionDatasource: NSObject, ItemsCollectionViewDatasource { var items:[Character] = [] weak var collectionView: UICollectionView? weak var delegate: UICollectionViewDelegate? required init(items: [Character], collectionView: UICollectionView, delegate: UICollectionViewDelegate) { self.items = items self.collectionView = collectionView self.delegate = delegate super.init() collectionView.register(cellType: CharacterCollectionCell.self) self.setupCollectionView() } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return self.items.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: CharacterCollectionCell.self) let character = self.items[indexPath.row] cell.setup(item: character) return cell } } class CharactersCollectionDelegate: NSObject, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { let delegate: CharactersDelegate init(_ delegate: CharactersDelegate) { self.delegate = delegate } func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { delegate.didSelectCharacter(at: indexPath) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let width = collectionView.bounds.size.width return CharacterCollectionCell.size(for: width) } } ================================================ FILE: Marvel/Datasources/CharactersDatasource.swift ================================================ // // CharactersDatasource.swift // Marvel // // Created by Thiago Lioy on 17/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import UIKit final class CharactersDatasource: NSObject, ItemsTableViewDatasource { var items:[Character] = [] weak var tableView: UITableView? weak var delegate: UITableViewDelegate? required init(items: [Character], tableView: UITableView, delegate: UITableViewDelegate) { self.items = items self.tableView = tableView self.delegate = delegate super.init() tableView.register(cellType: CharacterTableCell.self) self.setupTableView() } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(for: indexPath, cellType: CharacterTableCell.self) let character = self.items[indexPath.row] cell.setup(item: character) return cell } } class CharactersTableDelegate: NSObject, UITableViewDelegate { let delegate: CharactersDelegate init(_ delegate: CharactersDelegate) { self.delegate = delegate } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return CharacterTableCell.height() } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { delegate.didSelectCharacter(at: indexPath) } } ================================================ FILE: Marvel/Datasources/ItemsCollectionViewDatasource.swift ================================================ // // ItemsCollectionViewDatasource.swift // Marvel // // Created by Thiago Lioy on 20/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import UIKit protocol ItemsCollectionViewDatasource: UICollectionViewDataSource { associatedtype T var items:[T] {get} weak var collectionView: UICollectionView? {get} weak var delegate: UICollectionViewDelegate? {get} init(items: [T], collectionView: UICollectionView, delegate: UICollectionViewDelegate) func setupCollectionView() } extension ItemsCollectionViewDatasource { func setupCollectionView() { self.collectionView?.dataSource = self self.collectionView?.delegate = self.delegate self.collectionView?.reloadData() } } ================================================ FILE: Marvel/Datasources/ItemsTableViewDatasource.swift ================================================ // // ItemsTableDatasource.swift // Marvel // // Created by Thiago Lioy on 17/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import UIKit protocol ItemsTableViewDatasource: UITableViewDataSource { associatedtype T var items:[T] {get} weak var tableView: UITableView? {get} weak var delegate: UITableViewDelegate? {get} init(items: [T], tableView: UITableView, delegate: UITableViewDelegate) func setupTableView() } extension ItemsTableViewDatasource { func setupTableView() { self.tableView?.dataSource = self self.tableView?.delegate = self.delegate self.tableView?.reloadData() } } ================================================ FILE: Marvel/Models/Character.swift ================================================ // // Character.swift // Marvel // // Created by Thiago Lioy on 14/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import ObjectMapper struct Character { var id: Int = 0 var name: String = "" var bio: String = "" var thumImage: ThumbImage? } extension Character: Mappable { init?(map: Map) { } mutating func mapping(map: Map) { id <- map["id"] name <- map["name"] bio <- map["description"] thumImage <- map["thumbnail"] } } ================================================ FILE: Marvel/Models/ThumbImage.swift ================================================ // // ThumbImage.swift // Marvel // // Created by Thiago Lioy on 17/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import ObjectMapper struct ThumbImage { var path: String = "" var imageExtension: String = "" func fullPath() -> String { return "\(path).\(imageExtension)" } } extension ThumbImage: Mappable { init?(map: Map) { } mutating func mapping(map: Map) { path <- map["path"] imageExtension <- map["extension"] } } ================================================ FILE: Marvel/Network/MarvelAPI.swift ================================================ // // MarvelAPI.swift // Marvel // // Created by Thiago Lioy on 14/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import Moya import CryptoSwift import Dollar import Keys fileprivate struct MarvelAPIConfig { fileprivate static let keys = MarvelKeys() static let privatekey = keys.marvelPrivateKey()! static let apikey = keys.marvelApiKey()! static let ts = Date().timeIntervalSince1970.description static let hash = "\(ts)\(privatekey)\(apikey)".md5() } enum MarvelAPI { case characters(String?) case character(String) } extension MarvelAPI: TargetType { var baseURL: URL { return URL(string: "https://gateway.marvel.com:443")! } var path: String { switch self { case .characters: return "/v1/public/characters" case .character(let characterId): return "/v1/public/characters/\(characterId)" } } var method: Moya.Method { switch self { case .characters, .character: return .get } } func authParameters() -> [String: String] { return ["apikey": MarvelAPIConfig.apikey, "ts": MarvelAPIConfig.ts, "hash": MarvelAPIConfig.hash] } var parameters: [String: Any]? { switch self { case .characters(let query): if let query = query { return $.merge(authParameters(), ["nameStartsWith": query]) } return authParameters() case .character(let characterId): return $.merge(authParameters(), ["characterId": characterId]) } } var task: Task { return .request } var sampleData: Data { switch self { default: return Data() } } } ================================================ FILE: Marvel/Network/MarvelAPIManager.swift ================================================ // // MarvelAPIManager.swift // Marvel // // Created by Thiago Lioy on 14/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import Moya import RxSwift import ObjectMapper import Moya_ObjectMapper extension Response { func removeAPIWrappers() -> Response { guard let json = try? self.mapJSON() as? Dictionary, let results = json?["data"]?["results"] ?? [], let newData = try? JSONSerialization.data(withJSONObject: results, options: .prettyPrinted) else { return self } let newResponse = Response(statusCode: self.statusCode, data: newData, response: self.response) return newResponse } } struct MarvelAPIManager { let provider: RxMoyaProvider let disposeBag = DisposeBag() init() { provider = RxMoyaProvider() } } extension MarvelAPIManager { typealias AdditionalStepsAction = (() -> ()) fileprivate func requestObject(_ token: MarvelAPI, type: T.Type, completion: @escaping (T?) -> Void, additionalSteps: AdditionalStepsAction? = nil) { provider.request(token) .debug() .mapObject(T.self) .subscribe { event -> Void in switch event { case .next(let parsedObject): completion(parsedObject) additionalSteps?() case .error(let error): print(error) completion(nil) default: break } }.addDisposableTo(disposeBag) } fileprivate func requestArray(_ token: MarvelAPI, type: T.Type, completion: @escaping ([T]?) -> Void, additionalSteps: AdditionalStepsAction? = nil) { provider.request(token) .debug() .map { response -> Response in return response.removeAPIWrappers() } .mapArray(T.self) .subscribe { event -> Void in switch event { case .next(let parsedArray): completion(parsedArray) additionalSteps?() case .error(let error): print(error) completion(nil) default: break } }.addDisposableTo(disposeBag) } } protocol MarvelAPICalls { func characters(query: String?, completion: @escaping ([Character]?) -> Void) } extension MarvelAPIManager: MarvelAPICalls { func characters(query: String? = nil, completion: @escaping ([Character]?) -> Void) { requestArray(.characters(query), type: Character.self, completion: completion) } } ================================================ FILE: Marvel/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json ================================================ { "images" : [ { "idiom" : "iphone", "size" : "20x20", "scale" : "2x" }, { "size" : "20x20", "idiom" : "iphone", "filename" : "spotilight_small.png", "scale" : "3x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "settings_small@2x.png", "scale" : "2x" }, { "size" : "29x29", "idiom" : "iphone", "filename" : "settings_small@3x.png", "scale" : "3x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "spotlight_medium.png", "scale" : "2x" }, { "size" : "40x40", "idiom" : "iphone", "filename" : "spotlight_large.png", "scale" : "3x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "iphone_small.png", "scale" : "2x" }, { "size" : "60x60", "idiom" : "iphone", "filename" : "iphone_medium.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Marvel/Resources/Assets.xcassets/Contents.json ================================================ { "info" : { "version" : 1, "author" : "xcode" } } ================================================ FILE: Marvel/Resources/Assets.xcassets/Grid Icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "Grid Icon@1x.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "Grid Icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "Grid Icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "original" } } ================================================ FILE: Marvel/Resources/Assets.xcassets/List Icon.imageset/Contents.json ================================================ { "images" : [ { "idiom" : "universal", "filename" : "List Icon.png", "scale" : "1x" }, { "idiom" : "universal", "filename" : "List Icon@2x.png", "scale" : "2x" }, { "idiom" : "universal", "filename" : "List Icon@3x.png", "scale" : "3x" } ], "info" : { "version" : 1, "author" : "xcode" }, "properties" : { "template-rendering-intent" : "original" } } ================================================ FILE: Marvel/Resources/ColorPalette.swift ================================================ // // ColorPalette.swift // Marvel // // Created by Thiago Lioy on 11/12/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import UIKit enum ColorPalette { static let red = UIColor(red:0.910, green: 0.282, blue: 0.333, alpha: 1.000) static let white = UIColor(red:1.000, green: 0.988, blue: 0.976, alpha: 1.000) static let black = UIColor(red:0.251, green: 0.247, blue: 0.298, alpha: 1.000) } ================================================ FILE: Marvel/Resources/Info.plist ================================================ UIViewControllerBasedStatusBarAppearance CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString 1.0 CFBundleVersion 1 LSRequiresIPhoneOS NSAppTransportSecurity NSAllowsArbitraryLoads UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main UIRequiredDeviceCapabilities armv7 UIStatusBarStyle UIStatusBarStyleLightContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight ================================================ FILE: Marvel/Resources/Storyboard.swift ================================================ // Generated using SwiftGen, by O.Halligon — https://github.com/AliSoftware/SwiftGen import Foundation import UIKit protocol StoryboardSceneType { static var storyboardName: String { get } } extension StoryboardSceneType { static func storyboard() -> UIStoryboard { return UIStoryboard(name: self.storyboardName, bundle: nil) } static func initialViewController() -> UIViewController { guard let vc = storyboard().instantiateInitialViewController() else { fatalError("Failed to instantiate initialViewController for \(self.storyboardName)") } return vc } } extension StoryboardSceneType where Self: RawRepresentable, Self.RawValue == String { func viewController() -> UIViewController { return Self.storyboard().instantiateViewController(withIdentifier: self.rawValue) } static func viewController(identifier: Self) -> UIViewController { return identifier.viewController() } } protocol StoryboardSegueType: RawRepresentable { } extension UIViewController { func performSegue(segue: S, sender: AnyObject? = nil) where S.RawValue == String { performSegue(withIdentifier: segue.rawValue, sender: sender) } } // swiftlint:disable file_length // swiftlint:disable type_body_length struct Storyboard { enum LaunchScreen: StoryboardSceneType { static let storyboardName = "LaunchScreen" } enum Main: String, StoryboardSceneType { static let storyboardName = "Main" static func initialViewController() -> UINavigationController { guard let vc = storyboard().instantiateInitialViewController() as? UINavigationController else { fatalError("Failed to instantiate initialViewController for \(self.storyboardName)") } return vc } case characterViewControllerScene = "CharacterViewController" static func instantiateCharacterViewController() -> CharacterViewController { guard let vc = Storyboard.Main.characterViewControllerScene.viewController() as? CharacterViewController else { fatalError("ViewController 'CharacterViewController' is not of the expected class CharacterViewController.") } return vc } case charactersViewControllerScene = "CharactersViewController" static func instantiateCharactersViewController() -> CharactersViewController { guard let vc = Storyboard.Main.charactersViewControllerScene.viewController() as? CharactersViewController else { fatalError("ViewController 'CharactersViewController' is not of the expected class CharactersViewController.") } return vc } } } struct StoryboardSegue { } ================================================ FILE: Marvel/Storyboards/Base.lproj/LaunchScreen.storyboard ================================================ ================================================ FILE: Marvel/Storyboards/Base.lproj/Main.storyboard ================================================ ================================================ FILE: Marvel/UIImageView+Kingfisher.swift ================================================ // // UIImage+Kingfisher.swift // Marvel // // Created by Thiago Lioy on 20/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import UIKit import Kingfisher extension UIImageView { func download(image url: String) { guard let imageURL = URL(string:url) else { return } self.kf.setImage(with: ImageResource(downloadURL: imageURL)) } } ================================================ FILE: Marvel.xcodeproj/project.pbxproj ================================================ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 46; objects = { /* Begin PBXBuildFile section */ 577769109DC3FB06E7D1882F /* Pods_Marvel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A8D7213E1EA596B7C60C8E59 /* Pods_Marvel.framework */; }; 9922F1571DFDB69200237BFE /* ColorPalette.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9922F1561DFDB69200237BFE /* ColorPalette.swift */; }; 9922F15A1DFDC00900237BFE /* ApperanceProxyHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9922F1591DFDC00900237BFE /* ApperanceProxyHelper.swift */; }; 9939CFC71DDA244A008CE399 /* CharactersViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9939CFC61DDA244A008CE399 /* CharactersViewController.swift */; }; 9939CFC91DDA245A008CE399 /* CharacterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9939CFC81DDA245A008CE399 /* CharacterViewController.swift */; }; 9939CFCF1DDA2E07008CE399 /* MarvelAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9939CFCE1DDA2E07008CE399 /* MarvelAPI.swift */; }; 9939CFD11DDA30DC008CE399 /* MarvelAPIManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9939CFD01DDA30DC008CE399 /* MarvelAPIManager.swift */; }; 9939CFD41DDA333D008CE399 /* Character.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9939CFD31DDA333D008CE399 /* Character.swift */; }; 993AFDFF1DEA764200857C4F /* CharactersViewControllerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993AFDFE1DEA764200857C4F /* CharactersViewControllerSpec.swift */; }; 9976B4F81DEA43F200D2D3C4 /* MockLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9976B4F71DEA43F200D2D3C4 /* MockLoader.swift */; }; 999665411DEA242300E9A5BF /* CharacterSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 999665401DEA242300E9A5BF /* CharacterSpec.swift */; }; 99B248E31DDB427000027C10 /* CharacterTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99B248E21DDB427000027C10 /* CharacterTableCell.swift */; }; 99C67AC21DEB007600FB1E68 /* CharacterViewControllerSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C67AC11DEB007600FB1E68 /* CharacterViewControllerSpec.swift */; }; 99C67AC51DEB032F00FB1E68 /* CharactersDatasourceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C67AC41DEB032F00FB1E68 /* CharactersDatasourceSpec.swift */; }; 99C67AC71DEB06C100FB1E68 /* CharactersDelegateSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C67AC61DEB06C100FB1E68 /* CharactersDelegateSpec.swift */; }; 99C67ACA1DEB0B3D00FB1E68 /* CharactersCollectionDatasourceSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C67AC91DEB0B3D00FB1E68 /* CharactersCollectionDatasourceSpec.swift */; }; 99C67ACC1DEB0CB800FB1E68 /* CharactersCollectionDelegateSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C67ACB1DEB0CB800FB1E68 /* CharactersCollectionDelegateSpec.swift */; }; 99D0DEB21DE2598F00FE34D7 /* ItemsCollectionViewDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D0DEB11DE2598F00FE34D7 /* ItemsCollectionViewDatasource.swift */; }; 99D0DEB41DE259F100FE34D7 /* CharacterCollectionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D0DEB31DE259F100FE34D7 /* CharacterCollectionCell.swift */; }; 99D0DEB61DE25AC900FE34D7 /* CharacterCollectionCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 99D0DEB51DE25AC900FE34D7 /* CharacterCollectionCell.xib */; }; 99D0DEB81DE25BF300FE34D7 /* CharactersCollectionDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D0DEB71DE25BF300FE34D7 /* CharactersCollectionDatasource.swift */; }; 99D0DEBA1DE268D000FE34D7 /* UIImageView+Kingfisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D0DEB91DE268D000FE34D7 /* UIImageView+Kingfisher.swift */; }; 99D0DEBE1DE26D8100FE34D7 /* Storyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D0DEBD1DE26D8100FE34D7 /* Storyboard.swift */; }; 99E1DFE41DDA1F4C006F9D96 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E1DFE31DDA1F4C006F9D96 /* AppDelegate.swift */; }; 99E1DFE91DDA1F4C006F9D96 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 99E1DFE71DDA1F4C006F9D96 /* Main.storyboard */; }; 99E1DFEB1DDA1F4C006F9D96 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 99E1DFEA1DDA1F4C006F9D96 /* Assets.xcassets */; }; 99E1DFEE1DDA1F4C006F9D96 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 99E1DFEC1DDA1F4C006F9D96 /* LaunchScreen.storyboard */; }; 99EF5DB61DEA5CCD00B5569F /* ThumbImageSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99EF5DB51DEA5CCD00B5569F /* ThumbImageSpec.swift */; }; 99F8870E1DEA3E6D0044E3B0 /* characters_response.json in Resources */ = {isa = PBXBuildFile; fileRef = 99F8870D1DEA3E6D0044E3B0 /* characters_response.json */; }; 99F887101DEA3EA00044E3B0 /* character.json in Resources */ = {isa = PBXBuildFile; fileRef = 99F8870F1DEA3EA00044E3B0 /* character.json */; }; 99FC2BC81DDDB0F3006CB7EE /* ItemsTableViewDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FC2BC71DDDB0F3006CB7EE /* ItemsTableViewDatasource.swift */; }; 99FC2BCA1DDDB1BF006CB7EE /* CharactersDatasource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FC2BC91DDDB1BF006CB7EE /* CharactersDatasource.swift */; }; 99FC2BCD1DDDB8C7006CB7EE /* CharacterTableCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 99FC2BCC1DDDB8C7006CB7EE /* CharacterTableCell.xib */; }; 99FC2BCF1DDDB966006CB7EE /* ThumbImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FC2BCE1DDDB966006CB7EE /* ThumbImage.swift */; }; FAB8878D2CDDAC3D491738F7 /* Pods_MarvelTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8C6A61295B862198BB138719 /* Pods_MarvelTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 9996653B1DEA236300E9A5BF /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 99E1DFD81DDA1F4C006F9D96 /* Project object */; proxyType = 1; remoteGlobalIDString = 99E1DFDF1DDA1F4C006F9D96; remoteInfo = Marvel; }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ 46E32511C8C4E886533F9B7E /* Pods-Marvel.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Marvel.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Marvel/Pods-Marvel.debug.xcconfig"; sourceTree = ""; }; 8289D5AF9F3BDEDBC4EE6E82 /* Pods-Marvel.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Marvel.release.xcconfig"; path = "Pods/Target Support Files/Pods-Marvel/Pods-Marvel.release.xcconfig"; sourceTree = ""; }; 8C6A61295B862198BB138719 /* Pods_MarvelTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MarvelTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9922F1561DFDB69200237BFE /* ColorPalette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ColorPalette.swift; sourceTree = ""; }; 9922F1591DFDC00900237BFE /* ApperanceProxyHelper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApperanceProxyHelper.swift; sourceTree = ""; }; 9939CFC61DDA244A008CE399 /* CharactersViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharactersViewController.swift; sourceTree = ""; }; 9939CFC81DDA245A008CE399 /* CharacterViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterViewController.swift; sourceTree = ""; }; 9939CFCE1DDA2E07008CE399 /* MarvelAPI.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarvelAPI.swift; sourceTree = ""; }; 9939CFD01DDA30DC008CE399 /* MarvelAPIManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MarvelAPIManager.swift; sourceTree = ""; }; 9939CFD31DDA333D008CE399 /* Character.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Character.swift; sourceTree = ""; }; 993AFDFE1DEA764200857C4F /* CharactersViewControllerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharactersViewControllerSpec.swift; sourceTree = ""; }; 9976B4F71DEA43F200D2D3C4 /* MockLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockLoader.swift; sourceTree = ""; }; 999665361DEA236300E9A5BF /* MarvelTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MarvelTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 9996653A1DEA236300E9A5BF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 999665401DEA242300E9A5BF /* CharacterSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterSpec.swift; sourceTree = ""; }; 99B248E21DDB427000027C10 /* CharacterTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterTableCell.swift; sourceTree = ""; }; 99C67AC11DEB007600FB1E68 /* CharacterViewControllerSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterViewControllerSpec.swift; sourceTree = ""; }; 99C67AC41DEB032F00FB1E68 /* CharactersDatasourceSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharactersDatasourceSpec.swift; sourceTree = ""; }; 99C67AC61DEB06C100FB1E68 /* CharactersDelegateSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharactersDelegateSpec.swift; sourceTree = ""; }; 99C67AC91DEB0B3D00FB1E68 /* CharactersCollectionDatasourceSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharactersCollectionDatasourceSpec.swift; sourceTree = ""; }; 99C67ACB1DEB0CB800FB1E68 /* CharactersCollectionDelegateSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharactersCollectionDelegateSpec.swift; sourceTree = ""; }; 99D0DEB11DE2598F00FE34D7 /* ItemsCollectionViewDatasource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemsCollectionViewDatasource.swift; sourceTree = ""; }; 99D0DEB31DE259F100FE34D7 /* CharacterCollectionCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharacterCollectionCell.swift; sourceTree = ""; }; 99D0DEB51DE25AC900FE34D7 /* CharacterCollectionCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CharacterCollectionCell.xib; sourceTree = ""; }; 99D0DEB71DE25BF300FE34D7 /* CharactersCollectionDatasource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharactersCollectionDatasource.swift; sourceTree = ""; }; 99D0DEB91DE268D000FE34D7 /* UIImageView+Kingfisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+Kingfisher.swift"; sourceTree = ""; }; 99D0DEBD1DE26D8100FE34D7 /* Storyboard.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Storyboard.swift; sourceTree = ""; }; 99E1DFE01DDA1F4C006F9D96 /* Marvel.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Marvel.app; sourceTree = BUILT_PRODUCTS_DIR; }; 99E1DFE31DDA1F4C006F9D96 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 99E1DFE81DDA1F4C006F9D96 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 99E1DFEA1DDA1F4C006F9D96 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 99E1DFED1DDA1F4C006F9D96 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 99E1DFEF1DDA1F4C006F9D96 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 99EF5DB51DEA5CCD00B5569F /* ThumbImageSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbImageSpec.swift; sourceTree = ""; }; 99F8870D1DEA3E6D0044E3B0 /* characters_response.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = characters_response.json; sourceTree = ""; }; 99F8870F1DEA3EA00044E3B0 /* character.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = character.json; sourceTree = ""; }; 99FC2BC71DDDB0F3006CB7EE /* ItemsTableViewDatasource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ItemsTableViewDatasource.swift; sourceTree = ""; }; 99FC2BC91DDDB1BF006CB7EE /* CharactersDatasource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CharactersDatasource.swift; sourceTree = ""; }; 99FC2BCC1DDDB8C7006CB7EE /* CharacterTableCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = CharacterTableCell.xib; sourceTree = ""; }; 99FC2BCE1DDDB966006CB7EE /* ThumbImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThumbImage.swift; sourceTree = ""; }; A8D7213E1EA596B7C60C8E59 /* Pods_Marvel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Marvel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BF2D032F8BA77A47BCBA607A /* Pods-MarvelTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MarvelTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MarvelTests/Pods-MarvelTests.debug.xcconfig"; sourceTree = ""; }; E54675F9B824A595E2F0D533 /* Pods-MarvelTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MarvelTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MarvelTests/Pods-MarvelTests.release.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 999665331DEA236300E9A5BF /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( FAB8878D2CDDAC3D491738F7 /* Pods_MarvelTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 99E1DFDD1DDA1F4C006F9D96 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 577769109DC3FB06E7D1882F /* Pods_Marvel.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 9922F1581DFDBFF400237BFE /* Apperance */ = { isa = PBXGroup; children = ( 9922F1591DFDC00900237BFE /* ApperanceProxyHelper.swift */, ); name = Apperance; sourceTree = ""; }; 9939CFC51DDA2417008CE399 /* Controllers */ = { isa = PBXGroup; children = ( 9939CFC81DDA245A008CE399 /* CharacterViewController.swift */, 9939CFC61DDA244A008CE399 /* CharactersViewController.swift */, ); path = Controllers; sourceTree = ""; }; 9939CFCA1DDA267D008CE399 /* Storyboards */ = { isa = PBXGroup; children = ( 99E1DFEC1DDA1F4C006F9D96 /* LaunchScreen.storyboard */, 99E1DFE71DDA1F4C006F9D96 /* Main.storyboard */, ); path = Storyboards; sourceTree = ""; }; 9939CFCB1DDA26A3008CE399 /* Resources */ = { isa = PBXGroup; children = ( 99D0DEBD1DE26D8100FE34D7 /* Storyboard.swift */, 99E1DFEA1DDA1F4C006F9D96 /* Assets.xcassets */, 99E1DFEF1DDA1F4C006F9D96 /* Info.plist */, 9922F1561DFDB69200237BFE /* ColorPalette.swift */, ); path = Resources; sourceTree = ""; }; 9939CFCC1DDA2716008CE399 /* AppDelegate */ = { isa = PBXGroup; children = ( 99E1DFE31DDA1F4C006F9D96 /* AppDelegate.swift */, ); path = AppDelegate; sourceTree = ""; }; 9939CFCD1DDA2DF1008CE399 /* Network */ = { isa = PBXGroup; children = ( 9939CFCE1DDA2E07008CE399 /* MarvelAPI.swift */, 9939CFD01DDA30DC008CE399 /* MarvelAPIManager.swift */, ); path = Network; sourceTree = ""; }; 9939CFD21DDA332A008CE399 /* Models */ = { isa = PBXGroup; children = ( 9939CFD31DDA333D008CE399 /* Character.swift */, 99FC2BCE1DDDB966006CB7EE /* ThumbImage.swift */, ); path = Models; sourceTree = ""; }; 993AFDFD1DEA762C00857C4F /* Controllers */ = { isa = PBXGroup; children = ( 993AFDFE1DEA764200857C4F /* CharactersViewControllerSpec.swift */, 99C67AC11DEB007600FB1E68 /* CharacterViewControllerSpec.swift */, ); name = Controllers; sourceTree = ""; }; 9976B4F61DEA43D100D2D3C4 /* TestHelpers */ = { isa = PBXGroup; children = ( 9976B4F71DEA43F200D2D3C4 /* MockLoader.swift */, ); name = TestHelpers; sourceTree = ""; }; 999665371DEA236300E9A5BF /* MarvelTests */ = { isa = PBXGroup; children = ( 99C67AC81DEB06C700FB1E68 /* Delegates */, 99C67AC31DEB031E00FB1E68 /* Datasources */, 993AFDFD1DEA762C00857C4F /* Controllers */, 99EF5DB41DEA5CBD00B5569F /* Models */, 9976B4F61DEA43D100D2D3C4 /* TestHelpers */, 99F8870C1DEA3E560044E3B0 /* Mocks */, 9996653A1DEA236300E9A5BF /* Info.plist */, ); path = MarvelTests; sourceTree = ""; }; 99B248E11DDB423900027C10 /* Cells */ = { isa = PBXGroup; children = ( 99FC2BCB1DDDB8B1006CB7EE /* Xibs */, 99B248E21DDB427000027C10 /* CharacterTableCell.swift */, 99D0DEB31DE259F100FE34D7 /* CharacterCollectionCell.swift */, ); path = Cells; sourceTree = ""; }; 99C67AC31DEB031E00FB1E68 /* Datasources */ = { isa = PBXGroup; children = ( 99C67AC41DEB032F00FB1E68 /* CharactersDatasourceSpec.swift */, 99C67AC91DEB0B3D00FB1E68 /* CharactersCollectionDatasourceSpec.swift */, ); name = Datasources; sourceTree = ""; }; 99C67AC81DEB06C700FB1E68 /* Delegates */ = { isa = PBXGroup; children = ( 99C67AC61DEB06C100FB1E68 /* CharactersDelegateSpec.swift */, 99C67ACB1DEB0CB800FB1E68 /* CharactersCollectionDelegateSpec.swift */, ); name = Delegates; sourceTree = ""; }; 99D0DEBB1DE268D500FE34D7 /* Extensions */ = { isa = PBXGroup; children = ( 99D0DEB91DE268D000FE34D7 /* UIImageView+Kingfisher.swift */, ); name = Extensions; sourceTree = ""; }; 99E1DFD71DDA1F4C006F9D96 = { isa = PBXGroup; children = ( A614E78BE86E798A7E93A6F8 /* Frameworks */, 99E1DFE21DDA1F4C006F9D96 /* Marvel */, 999665371DEA236300E9A5BF /* MarvelTests */, F31159AB537646F3DA1B5700 /* Pods */, 99E1DFE11DDA1F4C006F9D96 /* Products */, ); sourceTree = ""; }; 99E1DFE11DDA1F4C006F9D96 /* Products */ = { isa = PBXGroup; children = ( 99E1DFE01DDA1F4C006F9D96 /* Marvel.app */, 999665361DEA236300E9A5BF /* MarvelTests.xctest */, ); name = Products; sourceTree = ""; }; 99E1DFE21DDA1F4C006F9D96 /* Marvel */ = { isa = PBXGroup; children = ( 9922F1581DFDBFF400237BFE /* Apperance */, 99D0DEBB1DE268D500FE34D7 /* Extensions */, 9939CFCC1DDA2716008CE399 /* AppDelegate */, 99B248E11DDB423900027C10 /* Cells */, 9939CFC51DDA2417008CE399 /* Controllers */, 99FC2BC61DDDB0A5006CB7EE /* Datasources */, 9939CFD21DDA332A008CE399 /* Models */, 9939CFCD1DDA2DF1008CE399 /* Network */, 9939CFCB1DDA26A3008CE399 /* Resources */, 9939CFCA1DDA267D008CE399 /* Storyboards */, ); path = Marvel; sourceTree = ""; }; 99EF5DB41DEA5CBD00B5569F /* Models */ = { isa = PBXGroup; children = ( 999665401DEA242300E9A5BF /* CharacterSpec.swift */, 99EF5DB51DEA5CCD00B5569F /* ThumbImageSpec.swift */, ); name = Models; sourceTree = ""; }; 99F8870C1DEA3E560044E3B0 /* Mocks */ = { isa = PBXGroup; children = ( 99F8870D1DEA3E6D0044E3B0 /* characters_response.json */, 99F8870F1DEA3EA00044E3B0 /* character.json */, ); name = Mocks; sourceTree = ""; }; 99FC2BC61DDDB0A5006CB7EE /* Datasources */ = { isa = PBXGroup; children = ( 99FC2BC91DDDB1BF006CB7EE /* CharactersDatasource.swift */, 99FC2BC71DDDB0F3006CB7EE /* ItemsTableViewDatasource.swift */, 99D0DEB11DE2598F00FE34D7 /* ItemsCollectionViewDatasource.swift */, 99D0DEB71DE25BF300FE34D7 /* CharactersCollectionDatasource.swift */, ); path = Datasources; sourceTree = ""; }; 99FC2BCB1DDDB8B1006CB7EE /* Xibs */ = { isa = PBXGroup; children = ( 99FC2BCC1DDDB8C7006CB7EE /* CharacterTableCell.xib */, 99D0DEB51DE25AC900FE34D7 /* CharacterCollectionCell.xib */, ); path = Xibs; sourceTree = ""; }; A614E78BE86E798A7E93A6F8 /* Frameworks */ = { isa = PBXGroup; children = ( A8D7213E1EA596B7C60C8E59 /* Pods_Marvel.framework */, 8C6A61295B862198BB138719 /* Pods_MarvelTests.framework */, ); name = Frameworks; sourceTree = ""; }; F31159AB537646F3DA1B5700 /* Pods */ = { isa = PBXGroup; children = ( 46E32511C8C4E886533F9B7E /* Pods-Marvel.debug.xcconfig */, 8289D5AF9F3BDEDBC4EE6E82 /* Pods-Marvel.release.xcconfig */, BF2D032F8BA77A47BCBA607A /* Pods-MarvelTests.debug.xcconfig */, E54675F9B824A595E2F0D533 /* Pods-MarvelTests.release.xcconfig */, ); name = Pods; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 999665351DEA236300E9A5BF /* MarvelTests */ = { isa = PBXNativeTarget; buildConfigurationList = 9996653F1DEA236300E9A5BF /* Build configuration list for PBXNativeTarget "MarvelTests" */; buildPhases = ( 40FD7841A58E5007D40EC477 /* [CP] Check Pods Manifest.lock */, 999665321DEA236300E9A5BF /* Sources */, 999665331DEA236300E9A5BF /* Frameworks */, 999665341DEA236300E9A5BF /* Resources */, AF9627A2960EE733DD7D8871 /* [CP] Embed Pods Frameworks */, 2163A2BCCD3A507A6B8CD6FB /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( 9996653C1DEA236300E9A5BF /* PBXTargetDependency */, ); name = MarvelTests; productName = MarvelTests; productReference = 999665361DEA236300E9A5BF /* MarvelTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; 99E1DFDF1DDA1F4C006F9D96 /* Marvel */ = { isa = PBXNativeTarget; buildConfigurationList = 99E1DFF21DDA1F4C006F9D96 /* Build configuration list for PBXNativeTarget "Marvel" */; buildPhases = ( 349470B03A6A7FDAFB705949 /* [CP] Check Pods Manifest.lock */, 99D0DEBC1DE26D0F00FE34D7 /* SwiftGen */, 99E1DFDC1DDA1F4C006F9D96 /* Sources */, 99E1DFDD1DDA1F4C006F9D96 /* Frameworks */, 99E1DFDE1DDA1F4C006F9D96 /* Resources */, 65B66DF972813F1C9E6FAB9F /* [CP] Embed Pods Frameworks */, 572BC285D19F6AE624CEB662 /* [CP] Copy Pods Resources */, ); buildRules = ( ); dependencies = ( ); name = Marvel; productName = Marvel; productReference = 99E1DFE01DDA1F4C006F9D96 /* Marvel.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 99E1DFD81DDA1F4C006F9D96 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0810; LastUpgradeCheck = 0810; ORGANIZATIONNAME = "Thiago Lioy"; TargetAttributes = { 999665351DEA236300E9A5BF = { CreatedOnToolsVersion = 8.1; DevelopmentTeam = 6UM2WYVE6K; ProvisioningStyle = Automatic; TestTargetID = 99E1DFDF1DDA1F4C006F9D96; }; 99E1DFDF1DDA1F4C006F9D96 = { CreatedOnToolsVersion = 8.1; DevelopmentTeam = 6UM2WYVE6K; ProvisioningStyle = Automatic; }; }; }; buildConfigurationList = 99E1DFDB1DDA1F4C006F9D96 /* Build configuration list for PBXProject "Marvel" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 99E1DFD71DDA1F4C006F9D96; productRefGroup = 99E1DFE11DDA1F4C006F9D96 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 99E1DFDF1DDA1F4C006F9D96 /* Marvel */, 999665351DEA236300E9A5BF /* MarvelTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 999665341DEA236300E9A5BF /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 99F8870E1DEA3E6D0044E3B0 /* characters_response.json in Resources */, 99F887101DEA3EA00044E3B0 /* character.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 99E1DFDE1DDA1F4C006F9D96 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 99FC2BCD1DDDB8C7006CB7EE /* CharacterTableCell.xib in Resources */, 99D0DEB61DE25AC900FE34D7 /* CharacterCollectionCell.xib in Resources */, 99E1DFEE1DDA1F4C006F9D96 /* LaunchScreen.storyboard in Resources */, 99E1DFEB1DDA1F4C006F9D96 /* Assets.xcassets in Resources */, 99E1DFE91DDA1F4C006F9D96 /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 2163A2BCCD3A507A6B8CD6FB /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MarvelTests/Pods-MarvelTests-resources.sh\"\n"; showEnvVarsInLog = 0; }; 349470B03A6A7FDAFB705949 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 40FD7841A58E5007D40EC477 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; showEnvVarsInLog = 0; }; 572BC285D19F6AE624CEB662 /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Copy Pods Resources"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Marvel/Pods-Marvel-resources.sh\"\n"; showEnvVarsInLog = 0; }; 65B66DF972813F1C9E6FAB9F /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Marvel/Pods-Marvel-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 99D0DEBC1DE26D0F00FE34D7 /* SwiftGen */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = SwiftGen; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "$PODS_ROOT/SwiftGen/bin/swiftgen storyboards -t swift3 $SOURCE_ROOT --output $SOURCE_ROOT/Marvel/Resources/Storyboard.swift --sceneEnumName Storyboard"; }; AF9627A2960EE733DD7D8871 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-MarvelTests/Pods-MarvelTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 999665321DEA236300E9A5BF /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 9976B4F81DEA43F200D2D3C4 /* MockLoader.swift in Sources */, 99C67AC51DEB032F00FB1E68 /* CharactersDatasourceSpec.swift in Sources */, 99C67AC71DEB06C100FB1E68 /* CharactersDelegateSpec.swift in Sources */, 999665411DEA242300E9A5BF /* CharacterSpec.swift in Sources */, 99C67AC21DEB007600FB1E68 /* CharacterViewControllerSpec.swift in Sources */, 99C67ACA1DEB0B3D00FB1E68 /* CharactersCollectionDatasourceSpec.swift in Sources */, 99EF5DB61DEA5CCD00B5569F /* ThumbImageSpec.swift in Sources */, 993AFDFF1DEA764200857C4F /* CharactersViewControllerSpec.swift in Sources */, 99C67ACC1DEB0CB800FB1E68 /* CharactersCollectionDelegateSpec.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 99E1DFDC1DDA1F4C006F9D96 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 99D0DEB81DE25BF300FE34D7 /* CharactersCollectionDatasource.swift in Sources */, 99D0DEBE1DE26D8100FE34D7 /* Storyboard.swift in Sources */, 99FC2BC81DDDB0F3006CB7EE /* ItemsTableViewDatasource.swift in Sources */, 9939CFD11DDA30DC008CE399 /* MarvelAPIManager.swift in Sources */, 9922F15A1DFDC00900237BFE /* ApperanceProxyHelper.swift in Sources */, 99D0DEBA1DE268D000FE34D7 /* UIImageView+Kingfisher.swift in Sources */, 9939CFC91DDA245A008CE399 /* CharacterViewController.swift in Sources */, 99D0DEB41DE259F100FE34D7 /* CharacterCollectionCell.swift in Sources */, 99B248E31DDB427000027C10 /* CharacterTableCell.swift in Sources */, 9939CFC71DDA244A008CE399 /* CharactersViewController.swift in Sources */, 99D0DEB21DE2598F00FE34D7 /* ItemsCollectionViewDatasource.swift in Sources */, 9939CFD41DDA333D008CE399 /* Character.swift in Sources */, 9922F1571DFDB69200237BFE /* ColorPalette.swift in Sources */, 99E1DFE41DDA1F4C006F9D96 /* AppDelegate.swift in Sources */, 99FC2BCA1DDDB1BF006CB7EE /* CharactersDatasource.swift in Sources */, 9939CFCF1DDA2E07008CE399 /* MarvelAPI.swift in Sources */, 99FC2BCF1DDDB966006CB7EE /* ThumbImage.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 9996653C1DEA236300E9A5BF /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 99E1DFDF1DDA1F4C006F9D96 /* Marvel */; targetProxy = 9996653B1DEA236300E9A5BF /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ 99E1DFE71DDA1F4C006F9D96 /* Main.storyboard */ = { isa = PBXVariantGroup; children = ( 99E1DFE81DDA1F4C006F9D96 /* Base */, ); name = Main.storyboard; path = .; sourceTree = ""; }; 99E1DFEC1DDA1F4C006F9D96 /* LaunchScreen.storyboard */ = { isa = PBXVariantGroup; children = ( 99E1DFED1DDA1F4C006F9D96 /* Base */, ); name = LaunchScreen.storyboard; path = .; sourceTree = ""; }; /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ 9996653D1DEA236300E9A5BF /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = BF2D032F8BA77A47BCBA607A /* Pods-MarvelTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; DEVELOPMENT_TEAM = 6UM2WYVE6K; INFOPLIST_FILE = MarvelTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.tpioy.marvelapp.MarvelTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Marvel.app/Marvel"; }; name = Debug; }; 9996653E1DEA236300E9A5BF /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = E54675F9B824A595E2F0D533 /* Pods-MarvelTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; DEVELOPMENT_TEAM = 6UM2WYVE6K; INFOPLIST_FILE = MarvelTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.tpioy.marvelapp.MarvelTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Marvel.app/Marvel"; }; name = Release; }; 99E1DFF01DDA1F4C006F9D96 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 99E1DFF11DDA1F4C006F9D96 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_SUSPICIOUS_MOVES = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 10.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 99E1DFF31DDA1F4C006F9D96 /* Debug */ = { isa = XCBuildConfiguration; baseConfigurationReference = 46E32511C8C4E886533F9B7E /* Pods-Marvel.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = 6UM2WYVE6K; INFOPLIST_FILE = Marvel/Resources/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.tpioy.marvelapp.Marvel; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 1; }; name = Debug; }; 99E1DFF41DDA1F4C006F9D96 /* Release */ = { isa = XCBuildConfiguration; baseConfigurationReference = 8289D5AF9F3BDEDBC4EE6E82 /* Pods-Marvel.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; DEVELOPMENT_TEAM = 6UM2WYVE6K; INFOPLIST_FILE = Marvel/Resources/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.tpioy.marvelapp.Marvel; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; TARGETED_DEVICE_FAMILY = 1; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 9996653F1DEA236300E9A5BF /* Build configuration list for PBXNativeTarget "MarvelTests" */ = { isa = XCConfigurationList; buildConfigurations = ( 9996653D1DEA236300E9A5BF /* Debug */, 9996653E1DEA236300E9A5BF /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 99E1DFDB1DDA1F4C006F9D96 /* Build configuration list for PBXProject "Marvel" */ = { isa = XCConfigurationList; buildConfigurations = ( 99E1DFF01DDA1F4C006F9D96 /* Debug */, 99E1DFF11DDA1F4C006F9D96 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 99E1DFF21DDA1F4C006F9D96 /* Build configuration list for PBXNativeTarget "Marvel" */ = { isa = XCConfigurationList; buildConfigurations = ( 99E1DFF31DDA1F4C006F9D96 /* Debug */, 99E1DFF41DDA1F4C006F9D96 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; rootObject = 99E1DFD81DDA1F4C006F9D96 /* Project object */; } ================================================ FILE: Marvel.xcodeproj/project.xcworkspace/contents.xcworkspacedata ================================================ ================================================ FILE: Marvel.xcodeproj/xcshareddata/xcschemes/Marvel.xcscheme ================================================ ================================================ FILE: MarvelTests/CharacterSpec.swift ================================================ // // NewTestSpec.swift // Marvel // // Created by Thiago Lioy on 26/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Quick import Nimble @testable import Marvel class CharacterSpec: QuickSpec { override func spec() { describe("a character") { var character: Marvel.Character! beforeEach { let testBundle = Bundle(for: type(of: self)) let mockLoader = MockLoader(file: "character", in: testBundle) character = mockLoader?.map(to: Character.self) } it("should be able to create a chracter from json") { expect(character).toNot(beNil()) } it("should have a thumbImage") { expect(character.thumImage).toNot(beNil()) } } } } ================================================ FILE: MarvelTests/CharacterViewControllerSpec.swift ================================================ // // CharacterViewControllerSpec.swift // Marvel // // Created by Thiago Lioy on 27/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import Quick import Nimble @testable import Marvel class CharacterViewControllerSpec: QuickSpec { override func spec() { describe("CharacterViewController") { var controller: CharacterViewController! var character: Marvel.Character! beforeEach { let testBundle = Bundle(for: type(of: self)) let mockLoader = MockLoader(file: "character", in: testBundle) character = (mockLoader?.map(to: Character.self))! controller = Storyboard.Main.characterViewControllerScene.viewController() as! CharacterViewController controller.character = character //Load view components let _ = controller.view } context("valid character") { it("should setup properties with character information") { controller.viewDidLoad() let name = controller.name.text expect(name).to(equal(character.name)) } } context("nil character") { it("should setup properties with default values") { controller.character = nil controller.viewDidLoad() let name = controller.name.text expect(name).to(equal("")) } } } } } ================================================ FILE: MarvelTests/CharactersCollectionDatasourceSpec.swift ================================================ // // CharactersCollectionDatasourceSpec.swift // Marvel // // Created by Thiago Lioy on 27/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import Quick import Nimble @testable import Marvel class CharactersCollectionDatasourceSpec: QuickSpec { override func spec() { describe("CharactersCollectionDatasource") { var controller: CharactersViewController! var character: Marvel.Character! beforeEach { let testBundle = Bundle(for: type(of: self)) let mockLoader = MockLoader(file: "character", in: testBundle) character = (mockLoader?.map(to: Character.self))! let apiMock = MarvelAPICallsMock(characters: [character]) controller = Storyboard.Main.charactersViewControllerScene.viewController() as! CharactersViewController controller.apiManager = apiMock //Load view components let _ = controller.view controller.showAsGrid(UIButton()) } it("should have a valid datasource") { expect(controller.collectionDatasource).toNot(beNil()) } it("should have a cell of expected type") { let indexPath = IndexPath(row: 0, section: 0) let cell = controller.collectionDatasource!.collectionView(controller.collectionView, cellForItemAt: indexPath) expect(cell.isKind(of: CharacterCollectionCell.self)).to(beTruthy()) } it("should have a configured cell") { let indexPath = IndexPath(row: 0, section: 0) let cell = controller.collectionDatasource!.collectionView(controller.collectionView, cellForItemAt: indexPath) as! CharacterCollectionCell let name = cell.name.text! expect(name).to(equal(character.name)) } it("should have the right numberOfRowsInSection") { let count = controller.collectionDatasource!.collectionView(controller.collectionView, numberOfItemsInSection: 0) expect(count).to(equal(1)) } } } } ================================================ FILE: MarvelTests/CharactersCollectionDelegateSpec.swift ================================================ // // CharactersCollectionDelegateSpec.swift // Marvel // // Created by Thiago Lioy on 27/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import Quick import Nimble @testable import Marvel class CharactersCollectionDelegateMock: CharactersDelegate { var didSelectRowTrigged = false func didSelectCharacter(at index: IndexPath) { didSelectRowTrigged = true } } class CharactersCollectionDelegateSpec: QuickSpec { override func spec() { describe("CharactersCollectionDelegate") { var controller: CharactersViewController! var character: Marvel.Character! beforeEach { let testBundle = Bundle(for: type(of: self)) let mockLoader = MockLoader(file: "character", in: testBundle) character = (mockLoader?.map(to: Character.self))! let apiMock = MarvelAPICallsMock(characters: [character]) controller = Storyboard.Main.charactersViewControllerScene.viewController() as! CharactersViewController controller.apiManager = apiMock //Load view components let _ = controller.view controller.showAsGrid(UIButton()) } it("should have a valid delegate") { expect(controller.collectionDelegate).toNot(beNil()) } it("should have a cell of expected size") { let indexPath = IndexPath(row: 0, section: 0) let size = controller.collectionDelegate!.collectionView(controller.collectionView, layout: controller.collectionView.collectionViewLayout, sizeForItemAt: indexPath) let width = controller.collectionView.bounds.size.width let expectedSize = CharacterCollectionCell.size(for: width) expect(size.height).to(equal(expectedSize.height)) expect(size.width).to(equal(expectedSize.width)) } it("should call delegate on didSelectedRowAt") { let indexPath = IndexPath(row: 0, section: 0) let charactersDelegateMock = CharactersCollectionDelegateMock() controller.collectionDelegate = CharactersCollectionDelegate(charactersDelegateMock) expect(charactersDelegateMock.didSelectRowTrigged).to(beFalsy()) controller.collectionDelegate!.collectionView(controller.collectionView, didSelectItemAt: indexPath) expect(charactersDelegateMock.didSelectRowTrigged).to(beTruthy()) } } } } ================================================ FILE: MarvelTests/CharactersDatasourceSpec.swift ================================================ // // CharactersDatasourceSpec.swift // Marvel // // Created by Thiago Lioy on 27/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import Quick import Nimble @testable import Marvel class CharactersDatasourceSpec: QuickSpec { override func spec() { describe("CharactersDatasource") { var controller: CharactersViewController! var character: Marvel.Character! beforeEach { let testBundle = Bundle(for: type(of: self)) let mockLoader = MockLoader(file: "character", in: testBundle) character = (mockLoader?.map(to: Character.self))! let apiMock = MarvelAPICallsMock(characters: [character]) controller = Storyboard.Main.charactersViewControllerScene.viewController() as! CharactersViewController controller.apiManager = apiMock //Load view components let _ = controller.view } it("should have a valid datasource") { expect(controller.tableDatasource).toNot(beNil()) } it("should have a cell of expected type") { let indexPath = IndexPath(row: 0, section: 0) let cell = controller.tableDatasource!.tableView(controller.tableView, cellForRowAt: indexPath) expect(cell.isKind(of: CharacterTableCell.self)).to(beTruthy()) } it("should have a configured cell") { let indexPath = IndexPath(row: 0, section: 0) let cell = controller.tableDatasource!.tableView(controller.tableView, cellForRowAt: indexPath) as! CharacterTableCell let name = cell.name.text! expect(name).to(equal(character.name)) } it("should have the right numberOfRowsInSection") { let count = controller.tableDatasource!.tableView(controller.tableView, numberOfRowsInSection: 0) expect(count).to(equal(1)) } } } } ================================================ FILE: MarvelTests/CharactersDelegateSpec.swift ================================================ // // CharactersDelegateSpec.swift // Marvel // // Created by Thiago Lioy on 27/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import Quick import Nimble @testable import Marvel class CharactersDelegateMock: CharactersDelegate { var didSelectRowTrigged = false func didSelectCharacter(at index: IndexPath) { didSelectRowTrigged = true } } class CharactersDelegateSpec: QuickSpec { override func spec() { describe("CharactersDelegate") { var controller: CharactersViewController! var character: Marvel.Character! beforeEach { let testBundle = Bundle(for: type(of: self)) let mockLoader = MockLoader(file: "character", in: testBundle) character = (mockLoader?.map(to: Character.self))! let apiMock = MarvelAPICallsMock(characters: [character]) controller = Storyboard.Main.charactersViewControllerScene.viewController() as! CharactersViewController controller.apiManager = apiMock //Load view components let _ = controller.view } it("should have a valid datasource") { expect(controller.tableDelegate).toNot(beNil()) } it("should have a cell of expected height") { let indexPath = IndexPath(row: 0, section: 0) let height = controller.tableDelegate!.tableView(controller.tableView, heightForRowAt: indexPath) expect(height).to(equal(80)) } it("should call delegate on didSelectedRowAt") { let indexPath = IndexPath(row: 0, section: 0) let charactersDelegateMock = CharactersDelegateMock() controller.tableDelegate = CharactersTableDelegate(charactersDelegateMock) expect(charactersDelegateMock.didSelectRowTrigged).to(beFalsy()) controller.tableDelegate!.tableView(controller.tableView, didSelectRowAt: indexPath) expect(charactersDelegateMock.didSelectRowTrigged).to(beTruthy()) } } } } ================================================ FILE: MarvelTests/CharactersViewControllerSpec.swift ================================================ // // CharactersViewControllerSpec.swift // Marvel // // Created by Thiago Lioy on 27/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import Quick import Nimble @testable import Marvel struct MarvelAPICallsMock: MarvelAPICalls { let characters: [Marvel.Character] func characters(query: String? = nil, completion: @escaping ([Marvel.Character]?) -> Void) { completion(characters) } } class CharactersViewControllerSpec: QuickSpec { override func spec() { describe("CharactersViewController") { var controller: CharactersViewController! var apiMock: MarvelAPICalls! beforeEach { let testBundle = Bundle(for: type(of: self)) let mockLoader = MockLoader(file: "character", in: testBundle) let character = (mockLoader?.map(to: Character.self))! apiMock = MarvelAPICallsMock(characters: [character]) controller = Storyboard.Main.charactersViewControllerScene.viewController() as! CharactersViewController controller.apiManager = apiMock //Load view components let _ = controller.view } it("should have expected props setup") { controller.viewDidLoad() expect(controller.apiManager).toNot(beNil()) expect(controller.tableDatasource).toNot(beNil()) expect(controller.tableDelegate).toNot(beNil()) expect(controller.collectionDatasource).to(beNil()) expect(controller.collectionDelegate).to(beNil()) expect(controller.characters).toNot(beNil()) expect(controller.searchBar).toNot(beNil()) expect(controller.activityIndicator).toNot(beNil()) expect(controller.tableView).toNot(beNil()) expect(controller.collectionView).toNot(beNil()) } it("should use mock response on fetchCharacters") { controller.viewDidLoad() let count = controller.tableDatasource?.items.count ?? 0 expect(count).toEventually(equal(1)) } it("should be able to display content as tableView") { controller.viewDidLoad() controller.showAsTable(UIButton()) expect(controller.collectionView.isHidden).to(beTruthy()) expect(controller.tableView.isHidden).to(beFalsy()) } it("should be able to display content as collectionView") { controller.viewDidLoad() controller.showAsGrid(UIButton()) expect(controller.tableView.isHidden).to(beTruthy()) expect(controller.collectionView.isHidden).to(beFalsy()) } context("Empty search") { it("should not fetchCharacters when no searchTerm is provided") { controller.searchBar.text = "" let searchBar = controller.searchBar controller.characters = [] controller.searchBarSearchButtonClicked(searchBar!) expect(controller.characters.isEmpty).to(beTruthy()) } } context("Not empty search") { it("should fetchCharacters when searchTerm is provided") { controller.searchBar.text = "searchThis" let searchBar = controller.searchBar controller.characters = [] controller.searchBarSearchButtonClicked(searchBar!) expect(controller.characters.isEmpty).to(beFalsy()) } } it("should hide keyboard with click on searchbar cancel button") { let searchBar = controller.searchBar! searchBar.becomeFirstResponder() controller.searchBarCancelButtonClicked(searchBar) expect(searchBar.isFirstResponder).to(beFalsy()) } context("didSelectCharacter") { beforeEach { let navController: UINavigationController = Storyboard.Main.initialViewController() controller = navController.viewControllers.first as! CharactersViewController controller.apiManager = apiMock let _ = controller.view controller.viewDidLoad() } it("should navigate do next controller when selecting a character") { let indexPath = IndexPath(row: 0, section: 0) let controllerCounts = controller.navigationController?.viewControllers.count expect(controllerCounts).to(equal(1)) controller.didSelectCharacter(at: indexPath) expect(controller.navigationController?.viewControllers.count ?? 0) .toEventually(equal(2), timeout: 3) } } } } } ================================================ FILE: MarvelTests/Info.plist ================================================ CFBundleDevelopmentRegion en CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType BNDL CFBundleShortVersionString 1.0 CFBundleVersion 1 ================================================ FILE: MarvelTests/MockLoader.swift ================================================ // // MockLoader.swift // Marvel // // Created by Thiago Lioy on 26/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import ObjectMapper @testable import Marvel struct MockLoader { let data: Data let json: String init?(file: String, withExtension fileExt: String = "json", in bundle:Bundle = Bundle.main) { guard let path = bundle.path(forResource: file, ofType: fileExt) else { return nil } let pathURL = URL(fileURLWithPath: path) do { data = try Data(contentsOf: pathURL, options: .dataReadingMapped) if let decoded = NSString(data: data, encoding: 0) as? String { json = decoded } else { return nil } } catch{ return nil } } } extension MockLoader { func map(to type: T.Type) -> T? { return Mapper().map(JSONString: json) } } ================================================ FILE: MarvelTests/ThumbImageSpec.swift ================================================ // // ThumbImageSpec.swift // Marvel // // Created by Thiago Lioy on 26/11/16. // Copyright © 2016 Thiago Lioy. All rights reserved. // import Foundation import Quick import Nimble @testable import Marvel class ThumbImageSpec: QuickSpec { override func spec() { describe("a thumbImage") { var thumbImage = ThumbImage() it("should be able to create a chracter from json") { thumbImage.imageExtension = "png" thumbImage.path = "whatever" expect(thumbImage.fullPath()).to(equal("whatever.png")) } } } } ================================================ FILE: MarvelTests/character.json ================================================ { "id": 1011334, "name": "3-D Man", "description": "", "modified": "2014-04-29T14:18:17-0400", "thumbnail": { "path": "http://i.annihil.us/u/prod/marvel/i/mg/c/e0/535fecbbb9784", "extension": "jpg" }, "resourceURI": "http://gateway.marvel.com/v1/public/characters/1011334", "comics": { "available": 11, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1011334/comics", "items": [ { "resourceURI": "http://gateway.marvel.com/v1/public/comics/21366", "name": "Avengers: The Initiative (2007) #14" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/24571", "name": "Avengers: The Initiative (2007) #14 (SPOTLIGHT VARIANT)" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/21546", "name": "Avengers: The Initiative (2007) #15" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/21741", "name": "Avengers: The Initiative (2007) #16" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/21975", "name": "Avengers: The Initiative (2007) #17" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/22299", "name": "Avengers: The Initiative (2007) #18" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/22300", "name": "Avengers: The Initiative (2007) #18 (ZOMBIE VARIANT)" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/22506", "name": "Avengers: The Initiative (2007) #19" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/10223", "name": "Marvel Premiere (1972) #35" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/10224", "name": "Marvel Premiere (1972) #36" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/10225", "name": "Marvel Premiere (1972) #37" } ], "returned": 11 }, "series": { "available": 2, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1011334/series", "items": [ { "resourceURI": "http://gateway.marvel.com/v1/public/series/1945", "name": "Avengers: The Initiative (2007 - 2010)" }, { "resourceURI": "http://gateway.marvel.com/v1/public/series/2045", "name": "Marvel Premiere (1972 - 1981)" } ], "returned": 2 }, "stories": { "available": 17, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1011334/stories", "items": [ { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19947", "name": "Cover #19947", "type": "cover" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19948", "name": "The 3-D Man!", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19949", "name": "Cover #19949", "type": "cover" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19950", "name": "The Devil's Music!", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19951", "name": "Cover #19951", "type": "cover" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19952", "name": "Code-Name: The Cold Warrior!", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/47185", "name": "Avengers: The Initiative (2007) #14 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/47499", "name": "Avengers: The Initiative (2007) #15 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/47792", "name": "Avengers: The Initiative (2007) #16", "type": "cover" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/47793", "name": "Avengers: The Initiative (2007) #16 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/48362", "name": "Avengers: The Initiative (2007) #17 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/49104", "name": "Avengers: The Initiative (2007) #18 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/49106", "name": "Avengers: The Initiative (2007) #18, Zombie Variant - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/49888", "name": "Avengers: The Initiative (2007) #19", "type": "cover" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/49889", "name": "Avengers: The Initiative (2007) #19 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/54371", "name": "Avengers: The Initiative (2007) #14, Spotlight Variant - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/96303", "name": "Deadpool (1997) #44", "type": "interiorStory" } ], "returned": 17 }, "events": { "available": 1, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1011334/events", "items": [ { "resourceURI": "http://gateway.marvel.com/v1/public/events/269", "name": "Secret Invasion" } ], "returned": 1 }, "urls": [ { "type": "detail", "url": "http://marvel.com/characters/74/3-d_man?utm_campaign=apiRef&utm_source=c71376dc66cd17b7f74bfeed02a6b9c2" }, { "type": "wiki", "url": "http://marvel.com/universe/3-D_Man_(Chandler)?utm_campaign=apiRef&utm_source=c71376dc66cd17b7f74bfeed02a6b9c2" }, { "type": "comiclink", "url": "http://marvel.com/comics/characters/1011334/3-d_man?utm_campaign=apiRef&utm_source=c71376dc66cd17b7f74bfeed02a6b9c2" } ] } ================================================ FILE: MarvelTests/characters_response.json ================================================ { "code": 200, "status": "Ok", "copyright": "© 2016 MARVEL", "attributionText": "Data provided by Marvel. © 2016 MARVEL", "attributionHTML": "Data provided by Marvel. © 2016 MARVEL", "etag": "e1cef8a6e120071d86387f876a7eb1c011a1b0ac", "data": { "offset": 0, "limit": 2, "total": 1485, "count": 2, "results": [ { "id": 1011334, "name": "3-D Man", "description": "", "modified": "2014-04-29T14:18:17-0400", "thumbnail": { "path": "http://i.annihil.us/u/prod/marvel/i/mg/c/e0/535fecbbb9784", "extension": "jpg" }, "resourceURI": "http://gateway.marvel.com/v1/public/characters/1011334", "comics": { "available": 11, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1011334/comics", "items": [ { "resourceURI": "http://gateway.marvel.com/v1/public/comics/21366", "name": "Avengers: The Initiative (2007) #14" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/24571", "name": "Avengers: The Initiative (2007) #14 (SPOTLIGHT VARIANT)" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/21546", "name": "Avengers: The Initiative (2007) #15" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/21741", "name": "Avengers: The Initiative (2007) #16" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/21975", "name": "Avengers: The Initiative (2007) #17" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/22299", "name": "Avengers: The Initiative (2007) #18" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/22300", "name": "Avengers: The Initiative (2007) #18 (ZOMBIE VARIANT)" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/22506", "name": "Avengers: The Initiative (2007) #19" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/10223", "name": "Marvel Premiere (1972) #35" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/10224", "name": "Marvel Premiere (1972) #36" }, { "resourceURI": "http://gateway.marvel.com/v1/public/comics/10225", "name": "Marvel Premiere (1972) #37" } ], "returned": 11 }, "series": { "available": 2, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1011334/series", "items": [ { "resourceURI": "http://gateway.marvel.com/v1/public/series/1945", "name": "Avengers: The Initiative (2007 - 2010)" }, { "resourceURI": "http://gateway.marvel.com/v1/public/series/2045", "name": "Marvel Premiere (1972 - 1981)" } ], "returned": 2 }, "stories": { "available": 17, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1011334/stories", "items": [ { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19947", "name": "Cover #19947", "type": "cover" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19948", "name": "The 3-D Man!", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19949", "name": "Cover #19949", "type": "cover" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19950", "name": "The Devil's Music!", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19951", "name": "Cover #19951", "type": "cover" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/19952", "name": "Code-Name: The Cold Warrior!", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/47185", "name": "Avengers: The Initiative (2007) #14 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/47499", "name": "Avengers: The Initiative (2007) #15 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/47792", "name": "Avengers: The Initiative (2007) #16", "type": "cover" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/47793", "name": "Avengers: The Initiative (2007) #16 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/48362", "name": "Avengers: The Initiative (2007) #17 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/49104", "name": "Avengers: The Initiative (2007) #18 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/49106", "name": "Avengers: The Initiative (2007) #18, Zombie Variant - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/49888", "name": "Avengers: The Initiative (2007) #19", "type": "cover" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/49889", "name": "Avengers: The Initiative (2007) #19 - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/54371", "name": "Avengers: The Initiative (2007) #14, Spotlight Variant - Int", "type": "interiorStory" }, { "resourceURI": "http://gateway.marvel.com/v1/public/stories/96303", "name": "Deadpool (1997) #44", "type": "interiorStory" } ], "returned": 17 }, "events": { "available": 1, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1011334/events", "items": [ { "resourceURI": "http://gateway.marvel.com/v1/public/events/269", "name": "Secret Invasion" } ], "returned": 1 }, "urls": [ { "type": "detail", "url": "http://marvel.com/characters/74/3-d_man?utm_campaign=apiRef&utm_source=c71376dc66cd17b7f74bfeed02a6b9c2" }, { "type": "wiki", "url": "http://marvel.com/universe/3-D_Man_(Chandler)?utm_campaign=apiRef&utm_source=c71376dc66cd17b7f74bfeed02a6b9c2" }, { "type": "comiclink", "url": "http://marvel.com/comics/characters/1011334/3-d_man?utm_campaign=apiRef&utm_source=c71376dc66cd17b7f74bfeed02a6b9c2" } ] }, { "id": 1017100, "name": "A-Bomb (HAS)", "description": "Rick Jones has been Hulk's best bud since day one, but now he's more than a friend...he's a teammate! Transformed by a Gamma energy explosion, A-Bomb's thick, armored skin is just as strong and powerful as it is blue. And when he curls into action, he uses it like a giant bowling ball of destruction! ", "modified": "2013-09-18T15:54:04-0400", "thumbnail": { "path": "http://i.annihil.us/u/prod/marvel/i/mg/3/20/5232158de5b16", "extension": "jpg" }, "resourceURI": "http://gateway.marvel.com/v1/public/characters/1017100", "comics": { "available": 0, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1017100/comics", "items": [], "returned": 0 }, "series": { "available": 0, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1017100/series", "items": [], "returned": 0 }, "stories": { "available": 1, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1017100/stories", "items": [ { "resourceURI": "http://gateway.marvel.com/v1/public/stories/105929", "name": "cover from Free Comic Book Day 2013 (Avengers/Hulk) (2013) #1", "type": "cover" } ], "returned": 1 }, "events": { "available": 0, "collectionURI": "http://gateway.marvel.com/v1/public/characters/1017100/events", "items": [], "returned": 0 }, "urls": [ { "type": "detail", "url": "http://marvel.com/characters/76/a-bomb?utm_campaign=apiRef&utm_source=c71376dc66cd17b7f74bfeed02a6b9c2" }, { "type": "comiclink", "url": "http://marvel.com/comics/characters/1017100/a-bomb_has?utm_campaign=apiRef&utm_source=c71376dc66cd17b7f74bfeed02a6b9c2" } ] } ] } } ================================================ FILE: Podfile ================================================ # Uncomment the next line to define a global platform for your project platform :ios, '9.0' target 'Marvel' do # Comment the next line if you're not using Swift and don't want to use dynamic frameworks use_frameworks! plugin 'cocoapods-keys', { :project => "Marvel", :target => "Marvel", :keys => [ "MarvelApiKey", "MarvelPrivateKey" ]} # Pods for Marvel pod 'SwiftGen' pod 'RxSwift', '~> 3.0.0-beta.2' pod 'Moya/RxSwift','~> 8.0.0-beta.1' pod 'Moya-ObjectMapper/RxSwift', :git => 'https://github.com/ivanbruel/Moya-ObjectMapper' pod 'CryptoSwift' pod 'Dollar' pod 'Kingfisher' pod "Reusable" end target 'MarvelTests' do use_frameworks! pod 'Quick' pod 'Nimble' pod 'Fakery' pod 'ObjectMapper' end post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| config.build_settings['SWIFT_VERSION'] = '3.0' end end end ================================================ FILE: README.md ================================================ # Marvel App [![Twitter: @tplioy](https://img.shields.io/badge/contact-@tplioy-blue.svg?style=flat)](https://twitter.com/tplioy) [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/thiagolioy/marvelapp/blob/master/LICENSE) [![Build Status](https://img.shields.io/travis/thiagolioy/marvelapp/master.svg?style=flat)](https://travis-ci.org/thiagolioy/marvelapp) [![GitHub forks](https://img.shields.io/github/forks/thiagolioy/marvelapp.svg)](https://github.com/thiagolioy/marvelapp/network) [![GitHub stars](https://img.shields.io/github/stars/thiagolioy/marvelapp.svg)](https://github.com/thiagolioy/marvelapp/stargazers) [![GitHub issues](https://img.shields.io/github/issues/thiagolioy/marvelapp.svg)](https://github.com/thiagolioy/marvelapp/issues) -------

AppearanceMotivationPostsInstallation

------- ## Appearance

Marvel Screens

## Motivation This repository supports a series of posts that will show how to create an iOS app from scratch, using many different pods and tools that will make your life easier. The project will have Marvel's theme and use its [API](https://developer.marvel.com). You can usually find this information elsewhere but it is usually splitted in different unrelated tutorials, my approach here is to convey all within a single project ## Posts Creating a Marvel iOS App from scratch.. - [Part 1 | Tools, pods, tricks of the trade and more](https://medium.com/cocoaacademymag/creating-a-ios-app-from-scratch-tools-pods-tricks-of-the-trade-and-more-part-1-a0a3f18fbd13#.fu8u4puxu) - [Part 2 | Tests, coverage and more](https://medium.com/cocoaacademymag/creating-a-ios-app-from-scratch-part-2-tests-coverage-and-more-73b94178b695#.4s4omxm48) - [Part 3 | Travis, Danger and Fastlane](https://medium.com/cocoaacademymag/creating-a-ios-app-from-scratch-part-3-travis-danger-and-fastlane-8ac91a003c95#.ii2fy9oc5) - [Part 4 | Sketch for developers](https://medium.com/cocoaacademymag/creating-a-marvel-ios-app-from-scratch-part-4-sketch-for-developers-2344a221482a#.kr3lhhobz) Other posts built upon Marvel's iOS App.. - [Migrating a Marvel's App to view code!](https://medium.com/cocoaacademymag/migrating-an-app-to-view-code-ffe3f1510408#.jwzemxaqa) - [Testing Marvel's View Code project .. With 100 % Code coverage !!](https://medium.com/cocoaacademymag/testing-marvels-view-code-project-with-100-code-coverage-23c55de4053b#.j16lslb7k) - [Marvel iOS App! Favoriting a character with View Code, Realm & RxSwift ..](https://medium.com/cocoaacademymag/marvel-ios-app-favoriting-a-character-with-view-code-realm-rxswift-e43b187c0f8e#.dd6bmjkil) - [Architecture Thoughts: Migrating Marvel's iOS App to ReSwift](https://medium.com/cocoaacademymag/architecture-thoughts-migrating-marvels-ios-app-to-reswift-ef7f20e84e60#.nl0b3aizp) ## Installation This project uses [Bundler](http://bundler.io) and [CocoaPods](https://cocoapods.org). All you need to setup it properly is: ``` bundle bundle exec pod install ``` ## Tests And Coverage You can run the tests any time. All your need to do is: ``` bundle exec fastlane test ``` ## License This project is licensed under the terms of the MIT license. See the LICENSE file. ================================================ FILE: coverage/AppDelegate.swift.html ================================================ AppDelegate.swift - Slather
Slather logo

Coverage for "AppDelegate.swift" : 100.00%

(4 of 4 relevant lines covered)

Marvel/AppDelegate/AppDelegate.swift

1
//
2
//  AppDelegate.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 14/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import UIKit
10
11
@UIApplicationMain
12
class AppDelegate: UIResponder, UIApplicationDelegate {
13
14
    var window: UIWindow?
15
16
17
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
1x
18
        // Override point for customization after application launch.
1x
19
        return true
1x
20
    }
1x
21
22
23
}
24
================================================ FILE: coverage/Character.swift.html ================================================ Character.swift - Slather
Slather logo

Coverage for "Character.swift" : 100.00%

(8 of 8 relevant lines covered)

Marvel/Models/Character.swift

1
//
2
//  Character.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 14/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import Foundation
10
import ObjectMapper
11
12
struct Character {
13
    var id: Int = 0
14
    var name: String = ""
15
    var thumImage: ThumbImage?
16
}
17
18
extension Character: Mappable {
19
    init?(map: Map) {
26x
20
        
26x
21
    }
26x
22
    
23
    mutating func mapping(map: Map) {
26x
24
        id    <- map["id"]
26x
25
        name    <- map["name"]
26x
26
        thumImage    <- map["thumbnail"]
26x
27
    }
26x
28
}
================================================ FILE: coverage/CharacterCollectionCell.swift.html ================================================ CharacterCollectionCell.swift - Slather
Slather logo

Coverage for "CharacterCollectionCell.swift" : 90.00%

(9 of 10 relevant lines covered)

Marvel/Cells/CharacterCollectionCell.swift

1
//
2
//  CharacterCollectionCell.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 20/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import UIKit
10
import Reusable
11
12
final class CharacterCollectionCell: UICollectionViewCell, NibReusable {
13
    @IBOutlet weak var name: UILabel!
14
    @IBOutlet weak var thumb: UIImageView!
15
    
16
    static let paddingBtwCells = CGFloat(10)
17
    
18
    static func size(for parentWidth: CGFloat) -> CGSize {
4x
19
        let numberOfCells = CGFloat(2)
4x
20
        let totalPadding = CGFloat(numberOfCells+1) * paddingBtwCells
4x
21
        let width = (parentWidth - totalPadding) / numberOfCells
4x
22
        return CGSize(width: width, height: width)
4x
23
    }
4x
24
    
25
    func setup(item: Character) {
2x
26
        name.text = item.name
2x
27
        thumb.download(image: item.thumImage?.fullPath() ?? "")
!
28
    }
2x
29
}
================================================ FILE: coverage/CharacterTableCell.swift.html ================================================ CharacterTableCell.swift - Slather
Slather logo

Coverage for "CharacterTableCell.swift" : 85.71%

(6 of 7 relevant lines covered)

Marvel/Cells/CharacterTableCell.swift

1
//
2
//  CharacterTableCell.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 15/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import UIKit
10
import Reusable
11
12
final class CharacterTableCell: UITableViewCell, NibReusable {
13
    @IBOutlet weak var name: UILabel!
14
    @IBOutlet weak var thumb: UIImageView!
15
    
16
    static func height() -> CGFloat {
33x
17
        return 80
33x
18
    }
33x
19
    
20
    func setup(item: Character) {
2x
21
        name.text = item.name
2x
22
        thumb.download(image: item.thumImage?.fullPath() ?? "")
!
23
    }
2x
24
}
================================================ FILE: coverage/CharacterViewController.swift.html ================================================ CharacterViewController.swift - Slather
Slather logo

Coverage for "CharacterViewController.swift" : 100.00%

(8 of 8 relevant lines covered)

Marvel/Controllers/CharacterViewController.swift

1
//
2
//  CharacterViewController.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 14/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import UIKit
10
11
final class CharacterViewController: UIViewController {
12
    @IBOutlet weak var name: UILabel!
13
    @IBOutlet weak var image: UIImageView!
14
    
15
    var character: Character?
16
}
17
18
extension CharacterViewController {
19
    override func viewDidLoad() {
4x
20
        super.viewDidLoad()
4x
21
        setupView()
4x
22
    }
4x
23
}
24
25
26
extension CharacterViewController {
27
    func setupView() {
4x
28
        name.text = character?.name ?? ""
1x
29
        image.download(image: character?.thumImage?.fullPath() ?? "")
1x
30
    }
4x
31
}
================================================ FILE: coverage/CharactersCollectionDatasource.swift.html ================================================ CharactersCollectionDatasource.swift - Slather
Slather logo

Coverage for "CharactersCollectionDatasource.swift" : 100.00%

(27 of 27 relevant lines covered)

Marvel/Datasources/CharactersCollectionDatasource.swift

1
//
2
//  CharactersCollectionDatasource.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 20/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import UIKit
10
11
final class CharactersCollectionDatasource: NSObject, ItemsCollectionViewDatasource {
12
    
13
    var items:[Character] = []
14
    weak var collectionView: UICollectionView?
15
    weak var delegate: UICollectionViewDelegate?
16
    
17
    required init(items: [Character], collectionView: UICollectionView, delegate: UICollectionViewDelegate) {
8x
18
        self.items = items
8x
19
        self.collectionView = collectionView
8x
20
        self.delegate = delegate
8x
21
        super.init()
8x
22
        collectionView.register(cellType: CharacterCollectionCell.self)
8x
23
        self.setupCollectionView()
8x
24
    }
8x
25
    
26
27
    
28
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
3x
29
        return self.items.count
3x
30
    }
3x
31
    
32
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
2x
33
        let cell = collectionView.dequeueReusableCell(for: indexPath, cellType: CharacterCollectionCell.self)
2x
34
        let character = self.items[indexPath.row]
2x
35
        cell.setup(item: character)
2x
36
        return cell
2x
37
    }
2x
38
}
39
40
class CharactersCollectionDelegate: NSObject, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
41
    
42
    let delegate: CharactersDelegate
43
    
44
    init(_ delegate: CharactersDelegate) {
9x
45
        self.delegate = delegate
9x
46
    }
9x
47
    
48
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
1x
49
        delegate.didSelectCharacter(at: indexPath)
1x
50
    }
1x
51
    
52
    
53
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
3x
54
        let width = collectionView.bounds.size.width
3x
55
        return CharacterCollectionCell.size(for: width)
3x
56
    }
3x
57
}
================================================ FILE: coverage/CharactersDatasource.swift.html ================================================ CharactersDatasource.swift - Slather
Slather logo

Coverage for "CharactersDatasource.swift" : 100.00%

(26 of 26 relevant lines covered)

Marvel/Datasources/CharactersDatasource.swift

1
//
2
//  CharactersDatasource.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 17/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import UIKit
10
11
final class CharactersDatasource: NSObject, ItemsTableViewDatasource {
12
    
13
    var items:[Character] = []
14
    weak var tableView: UITableView?
15
    weak var delegate: UITableViewDelegate?
16
    
17
    required init(items: [Character], tableView: UITableView, delegate: UITableViewDelegate) {
30x
18
        self.items = items
30x
19
        self.tableView = tableView
30x
20
        self.delegate = delegate
30x
21
        super.init()
30x
22
        tableView.register(cellType: CharacterTableCell.self)
30x
23
        self.setupTableView()
30x
24
    }
30x
25
    
26
    
27
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
31x
28
        return self.items.count
31x
29
    }
31x
30
    
31
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
2x
32
        let cell = tableView.dequeueReusableCell(for: indexPath, cellType: CharacterTableCell.self)
2x
33
        let character = self.items[indexPath.row]
2x
34
        cell.setup(item: character)
2x
35
        return cell
2x
36
    }
2x
37
}
38
39
class CharactersTableDelegate: NSObject, UITableViewDelegate {
40
    
41
    let delegate: CharactersDelegate
42
    
43
    init(_ delegate: CharactersDelegate) {
31x
44
        self.delegate = delegate
31x
45
    }
31x
46
    
47
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
33x
48
        return CharacterTableCell.height()
33x
49
    }
33x
50
    
51
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
1x
52
        delegate.didSelectCharacter(at: indexPath)
1x
53
    }
1x
54
}
================================================ FILE: coverage/CharactersViewController.swift.html ================================================ CharactersViewController.swift - Slather
Slather logo

Coverage for "CharactersViewController.swift" : 95.00%

(57 of 60 relevant lines covered)

Marvel/Controllers/CharactersViewController.swift

1
//
2
//  CharactersViewController.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 14/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import UIKit
10
11
protocol CharactersDelegate {
12
    func didSelectCharacter(at index: IndexPath)
13
}
14
15
16
final class CharactersViewController: UIViewController {
17
    var apiManager: MarvelAPICalls = MarvelAPIManager()
18
    
19
    var tableDatasource: CharactersDatasource?
20
    var tableDelegate: CharactersTableDelegate?
21
    
22
    var collectionDatasource: CharactersCollectionDatasource?
23
    var collectionDelegate: CharactersCollectionDelegate?
24
    
25
    var characters: [Character] = []
26
    
27
    @IBOutlet weak var searchBar: UISearchBar!
28
    @IBOutlet weak var activityIndicator: UIActivityIndicatorView!
29
    @IBOutlet weak var tableView: UITableView!
30
    @IBOutlet weak var collectionView: UICollectionView!
31
    
32
}
33
34
extension CharactersViewController {
35
    override func viewDidLoad() {
29x
36
        super.viewDidLoad()
29x
37
        setupSearchBar()
29x
38
        fetchCharacters()
29x
39
    }
29x
40
}
41
42
extension CharactersViewController {
43
    func fetchCharacters(for query: String? = nil) {
30x
44
        tableView.isHidden = true
30x
45
        collectionView.isHidden = true
30x
46
        activityIndicator.startAnimating()
30x
47
        apiManager.characters(query: query) { characters in
29x
48
            self.activityIndicator.stopAnimating()
29x
49
            if let characters = characters {
29x
50
                self.setupTableView(with: characters)
29x
51
            }
29x
52
        }
29x
53
    }
30x
54
    
55
    func setupSearchBar() {
29x
56
        self.searchBar.delegate = self
29x
57
    }
29x
58
    
59
    func setupTableView(with characters: [Character]) {
30x
60
        self.characters = characters
30x
61
        tableView.isHidden = false
30x
62
        collectionView.isHidden = true
30x
63
        tableDelegate = CharactersTableDelegate(self)
30x
64
        tableDatasource = CharactersDatasource(items: characters, tableView: self.tableView, delegate: tableDelegate!)
30x
65
    }
30x
66
    
67
    func setupCollectionView(with characters: [Character]) {
8x
68
        self.characters = characters
8x
69
        collectionView.isHidden = false
8x
70
        tableView.isHidden = true
8x
71
        collectionDelegate = CharactersCollectionDelegate(self)
8x
72
        collectionDatasource = CharactersCollectionDatasource(items: characters, collectionView: self.collectionView, delegate: collectionDelegate!)
8x
73
    }
8x
74
}
75
76
extension CharactersViewController {
77
    @IBAction func showAsGrid(_ sender: UIButton) {
8x
78
        setupCollectionView(with: characters)
8x
79
    }
8x
80
    
81
    @IBAction func showAsTable(_ sender: UIButton) {
1x
82
        setupTableView(with: characters)
1x
83
    }
1x
84
}
85
86
extension CharactersViewController: CharactersDelegate {
87
    func didSelectCharacter(at index: IndexPath) {
1x
88
        searchBar.resignFirstResponder()
1x
89
        guard let nextController = Storyboard.Main.characterViewControllerScene
1x
90
            .viewController() as? CharacterViewController else {
!
91
            return
!
92
        }
1x
93
        
1x
94
        let character = characters[index.row]
1x
95
        nextController.character = character
1x
96
        self.navigationController?.pushViewController(nextController, animated: true)
1x
97
    }
1x
98
}
99
100
extension CharactersViewController: UISearchBarDelegate {
101
    func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
2x
102
        searchBar.resignFirstResponder()
2x
103
        let query = searchBar.text ?? ""
!
104
        if !query.isEmpty {
1x
105
            fetchCharacters(for: query)
1x
106
        }
2x
107
    }
2x
108
    func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
1x
109
        searchBar.resignFirstResponder()
1x
110
    }
1x
111
}
112
================================================ FILE: coverage/ItemsCollectionViewDatasource.swift.html ================================================ ItemsCollectionViewDatasource.swift - Slather
Slather logo

Coverage for "ItemsCollectionViewDatasource.swift" : 100.00%

(5 of 5 relevant lines covered)

Marvel/Datasources/ItemsCollectionViewDatasource.swift

1
//
2
//  ItemsCollectionViewDatasource.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 20/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import UIKit
10
11
protocol ItemsCollectionViewDatasource: UICollectionViewDataSource {
12
    associatedtype T
13
    var items:[T] {get}
14
    weak var collectionView: UICollectionView? {get}
15
    weak var delegate: UICollectionViewDelegate? {get}
16
    
17
    init(items: [T], collectionView: UICollectionView, delegate: UICollectionViewDelegate)
18
    
19
    func setupCollectionView()
20
}
21
22
extension ItemsCollectionViewDatasource {
23
    func setupCollectionView() {
8x
24
        self.collectionView?.dataSource = self
8x
25
        self.collectionView?.delegate = self.delegate
8x
26
        self.collectionView?.reloadData()
8x
27
    }
8x
28
}
29
================================================ FILE: coverage/ItemsTableViewDatasource.swift.html ================================================ ItemsTableViewDatasource.swift - Slather
Slather logo

Coverage for "ItemsTableViewDatasource.swift" : 100.00%

(5 of 5 relevant lines covered)

Marvel/Datasources/ItemsTableViewDatasource.swift

1
//
2
//  ItemsTableDatasource.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 17/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import UIKit
10
11
protocol ItemsTableViewDatasource: UITableViewDataSource {
12
    associatedtype T
13
    var items:[T] {get}
14
    weak var tableView: UITableView? {get}
15
    weak var delegate: UITableViewDelegate? {get}
16
    
17
    init(items: [T], tableView: UITableView, delegate: UITableViewDelegate)
18
    
19
    func setupTableView()
20
}
21
22
extension ItemsTableViewDatasource {
23
    func setupTableView() {
30x
24
        self.tableView?.dataSource = self
30x
25
        self.tableView?.delegate = self.delegate
30x
26
        self.tableView?.reloadData()
30x
27
    }
30x
28
}
================================================ FILE: coverage/ThumbImage.swift.html ================================================ ThumbImage.swift - Slather
Slather logo

Coverage for "ThumbImage.swift" : 100.00%

(10 of 10 relevant lines covered)

Marvel/Models/ThumbImage.swift

1
//
2
//  ThumbImage.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 17/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import Foundation
10
import ObjectMapper
11
12
13
struct ThumbImage {
14
    var path: String = ""
15
    var imageExtension: String = ""
16
    
17
    func fullPath() -> String {
8x
18
        return "\(path).\(imageExtension)"
8x
19
    }
8x
20
}
21
22
extension ThumbImage: Mappable {
23
    init?(map: Map) {
26x
24
        
26x
25
    }
26x
26
    
27
    mutating func mapping(map: Map) {
26x
28
        path    <- map["path"]
26x
29
        imageExtension    <- map["extension"]
26x
30
    }
26x
31
}
================================================ FILE: coverage/UIImageView+Kingfisher.swift.html ================================================ UIImageView+Kingfisher.swift - Slather
Slather logo

Coverage for "UIImageView+Kingfisher.swift" : 100.00%

(6 of 6 relevant lines covered)

Marvel/UIImageView+Kingfisher.swift

1
//
2
//  UIImage+Kingfisher.swift
3
//  Marvel
4
//
5
//  Created by Thiago Lioy on 20/11/16.
6
//  Copyright �� 2016 Thiago Lioy. All rights reserved.
7
//
8
9
import UIKit
10
import Kingfisher
11
12
extension UIImageView {
13
    func download(image url: String) {
8x
14
        guard let imageURL = URL(string:url) else {
1x
15
            return
1x
16
        }
7x
17
        self.kf.setImage(with: ImageResource(downloadURL: imageURL))
7x
18
    }
7x
19
}
================================================ FILE: coverage/highlight.pack.js ================================================ !function(e){"undefined"!=typeof exports?e(exports):(window.hljs=e({}),"function"==typeof define&&define.amd&&define("hljs",[],function(){return window.hljs}))}(function(e){function n(e){return e.replace(/&/gm,"&").replace(//gm,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0==t.index}function a(e){return/no-?highlight|plain|text/.test(e)}function i(e){var n,t,r,i=e.className+" ";if(i+=e.parentNode?e.parentNode.className:"",t=/\blang(?:uage)?-([\w-]+)\b/.exec(i))return E(t[1])?t[1]:"no-highlight";for(i=i.split(/\s+/),n=0,r=i.length;r>n;n++)if(E(i[n])||a(i[n]))return i[n]}function o(e,n){var t,r={};for(t in e)r[t]=e[t];if(n)for(t in n)r[t]=n[t];return r}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3==i.nodeType?a+=i.nodeValue.length:1==i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!=r[0].offset?e[0].offset"}function u(e){f+=""}function c(e){("start"==e.event?o:u)(e.node)}for(var s=0,f="",l=[];e.length||r.length;){var g=i();if(f+=n(a.substr(s,g[0].offset-s)),s=g[0].offset,g==e){l.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g==e&&g.length&&g[0].offset==s);l.reverse().forEach(o)}else"start"==g[0].event?l.push(g[0].node):l.pop(),c(g.splice(0,1)[0])}return f+n(a.substr(s))}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var u={},c=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");u[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?c("keyword",a.k):Object.keys(a.k).forEach(function(e){c(e,a.k[e])}),a.k=u}a.lR=t(a.l||/\b\w+\b/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),void 0===a.r&&(a.r=1),a.c||(a.c=[]);var s=[];a.c.forEach(function(e){e.v?e.v.forEach(function(n){s.push(o(e,n))}):s.push("self"==e?a:e)}),a.c=s,a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var f=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=f.length?t(f.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){for(var t=0;t";return i+=e+'">',i+n+o}function p(){if(!L.k)return n(B);var e="",t=0;L.lR.lastIndex=0;for(var r=L.lR.exec(B);r;){e+=n(B.substr(t,r.index-t));var a=g(L,r);a?(y+=a[1],e+=h(a[0],n(r[0]))):e+=n(r[0]),t=L.lR.lastIndex,r=L.lR.exec(B)}return e+n(B.substr(t))}function d(){if(L.sL&&!x[L.sL])return n(B);var e=L.sL?f(L.sL,B,!0,M[L.sL]):l(B);return L.r>0&&(y+=e.r),"continuous"==L.subLanguageMode&&(M[L.sL]=e.top),h(e.language,e.value,!1,!0)}function b(){return void 0!==L.sL?d():p()}function v(e,t){var r=e.cN?h(e.cN,"",!0):"";e.rB?(k+=r,B=""):e.eB?(k+=n(t)+r,B=""):(k+=r,B=t),L=Object.create(e,{parent:{value:L}})}function m(e,t){if(B+=e,void 0===t)return k+=b(),0;var r=o(t,L);if(r)return k+=b(),v(r,t),r.rB?0:t.length;var a=u(L,t);if(a){var i=L;i.rE||i.eE||(B+=t),k+=b();do L.cN&&(k+=""),y+=L.r,L=L.parent;while(L!=a.parent);return i.eE&&(k+=n(t)),B="",a.starts&&v(a.starts,""),i.rE?0:t.length}if(c(t,L))throw new Error('Illegal lexeme "'+t+'" for mode "'+(L.cN||"")+'"');return B+=t,t.length||1}var N=E(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,L=i||N,M={},k="";for(R=L;R!=N;R=R.parent)R.cN&&(k=h(R.cN,"",!0)+k);var B="",y=0;try{for(var C,j,I=0;;){if(L.t.lastIndex=I,C=L.t.exec(t),!C)break;j=m(t.substr(I,C.index-I),C[0]),I=C.index+j}for(m(t.substr(I)),R=L;R.parent;R=R.parent)R.cN&&(k+="");return{r:y,value:k,language:e,top:L}}catch(O){if(-1!=O.message.indexOf("Illegal"))return{r:0,value:n(t)};throw O}}function l(e,t){t=t||w.languages||Object.keys(x);var r={r:0,value:n(e)},a=r;return t.forEach(function(n){if(E(n)){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}}),a.language&&(r.second_best=a),r}function g(e){return w.tabReplace&&(e=e.replace(/^((<[^>]+>|\t)+)/gm,function(e,n){return n.replace(/\t/g,w.tabReplace)})),w.useBR&&(e=e.replace(/\n/g,"
")),e}function h(e,n,t){var r=n?R[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function p(e){var n=i(e);if(!a(n)){var t;w.useBR?(t=document.createElementNS("http://www.w3.org/1999/xhtml","div"),t.innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n")):t=e;var r=t.textContent,o=n?f(n,r,!0):l(r),s=u(t);if(s.length){var p=document.createElementNS("http://www.w3.org/1999/xhtml","div");p.innerHTML=o.value,o.value=c(s,u(p),r)}o.value=g(o.value),e.innerHTML=o.value,e.className=h(e.className,n,o.language),e.result={language:o.language,re:o.r},o.second_best&&(e.second_best={language:o.second_best.language,re:o.second_best.r})}}function d(e){w=o(w,e)}function b(){if(!b.called){b.called=!0;var e=document.querySelectorAll("pre code");Array.prototype.forEach.call(e,p)}}function v(){addEventListener("DOMContentLoaded",b,!1),addEventListener("load",b,!1)}function m(n,t){var r=x[n]=t(e);r.aliases&&r.aliases.forEach(function(e){R[e]=n})}function N(){return Object.keys(x)}function E(e){return x[e]||x[R[e]]}var w={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0},x={},R={};return e.highlight=f,e.highlightAuto=l,e.fixMarkup=g,e.highlightBlock=p,e.configure=d,e.initHighlighting=b,e.initHighlightingOnLoad=v,e.registerLanguage=m,e.listLanguages=N,e.getLanguage=E,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="\\b(0[xX][a-fA-F0-9]+|(\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",bK:"TODO FIXME NOTE BUG XXX",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e});hljs.registerLanguage("json",function(e){var t={literal:"true false null"},i=[e.QSM,e.CNM],l={cN:"value",e:",",eW:!0,eE:!0,c:i,k:t},c={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:!0,eE:!0,c:[e.BE],i:"\\n",starts:l}],i:"\\S"},n={b:"\\[",e:"\\]",c:[e.inherit(l,{cN:null})],i:"\\S"};return i.splice(i.length,0,c,n),{c:i,k:t,i:"\\S"}});hljs.registerLanguage("objectivec",function(e){var t={cN:"built_in",b:"(AV|CA|CF|CG|CI|MK|MP|NS|UI)\\w+"},i={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},o=/[a-zA-Z@][a-zA-Z0-9_]*/,n="@interface @class @protocol @implementation";return{aliases:["mm","objc","obj-c"],k:i,l:o,i:""}]}]},{cN:"class",b:"("+n.split(" ").join("|")+")\\b",e:"({|$)",eE:!0,k:n,l:o,c:[e.UTM]},{cN:"variable",b:"\\."+e.UIR,r:0}]}});hljs.registerLanguage("cpp",function(t){var e={cN:"keyword",b:"[a-z\\d_]*_t"},r={keyword:"false int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using true class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignof constexpr decltype noexcept nullptr static_assert thread_local restrict _Bool complex _Complex _Imaginary atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong",built_in:"std string cin cout cerr clog stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf"};return{aliases:["c","cc","h","c++","h++","hpp"],k:r,i:""]',k:"include",i:"\\n"},t.CLCM]},{b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:r,c:["self",e]},{b:t.IR+"::",k:r},{bK:"new throw return else",r:0},{cN:"function",b:"("+t.IR+"\\s+)+"+t.IR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:t.IR+"\\s*\\(",rB:!0,c:[t.TM],r:0},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[t.CBCM]},t.CLCM,t.CBCM]}]}}); ================================================ FILE: coverage/index.html ================================================ Marvel.xcodeproj - Slather
Slather logo

Files for "Marvel.xcodeproj"

Total Coverage : 97.16%

% File Lines Relevant Covered Missed
100.00 AppDelegate.swift 24 4 4 0
90.00 CharacterCollectionCell.swift 29 10 9 1
85.71 CharacterTableCell.swift 24 7 6 1
100.00 CharacterViewController.swift 31 8 8 0
95.00 CharactersViewController.swift 112 60 57 3
100.00 CharactersCollectionDatasource.swift 57 27 27 0
100.00 CharactersDatasource.swift 54 26 26 0
100.00 ItemsCollectionViewDatasource.swift 29 5 5 0
100.00 ItemsTableViewDatasource.swift 28 5 5 0
100.00 Character.swift 28 8 8 0
100.00 ThumbImage.swift 31 10 10 0
100.00 UIImageView+Kingfisher.swift 19 6 6 0
================================================ FILE: coverage/slather.css ================================================ /* -------------------------------------------------------- Slather stylesheet version: 0.1 author: Ikhsan Assaat (@ixnixnixn) ----------------------------------------------------------*/ /* General */ html { position: relative; min-height: 100%; } body { font: 16px "Helvetica", sans-serif; margin: 0 0 120px; color: #333; } .row { margin: 0 2em; } /* Header */ header { margin-top: 1em; } header img { width: auto; height: 120px; } /* Coverage */ #reports > h2 { margin-bottom: 0; } #reports > h4 { margin-top: 5px; } .percentage { padding: 4px ; font-weight: bold; } .cov_high { color: #67CF7C; } .cov_medium { color: #F89404; } .cov_low { color: #F86769; } .cov_title { margin-bottom: 0; } .cov_subtitle { margin-top: 0.2em; } .cov_filepath { font-style: italic; } /* Index Table */ table.coverage_list { width: 90%; min-width: 400px; } table.coverage_list th, table.coverage_list td { padding: .6em .5em; text-align: left; } table.coverage_list th.col_num { width: 70px; } table.coverage_list th.col_percent { width: 75px; } table.coverage_list thead, tfoot { background: #FDCD9B; } table.coverage_list tbody tr:hover { background: #FCF2E6; } table.coverage_list tbody td { border-bottom: 1px solid #CCC; } table.coverage_list td a { color: #333; text-decoration: none; border-bottom: 1px dotted; } table.coverage_list td a:hover { border-bottom: none; } /* Source Code */ table.source_code { width: 100%; max-width: 1200px; min-width: 400px; font-size: 13px; border-spacing: 0; background: #FCF2E6; padding: 1.2em 1em; margin-bottom: 2em; } table.source_code td { padding-bottom: 0.3em; } table.source_code tr.missed td { background-color: rgba(248, 103, 105, 0.2); } table.source_code tr.covered td { background-color: rgba(103, 207, 124, 0.2); } table.source_code td.num { border-right: 1px rgba(0,0,0,0.1) solid; text-align: right; padding-right: 1em; width: 30px; } table.source_code td.src { border-left: 1px rgba(255,255,255,0.7) solid; padding-left: 1em; } table.source_code td.src pre { white-space: pre-wrap; white-space: -moz-pre-wrap; white-space: -pre-wrap; white-space: -o-pre-wrap; word-wrap: break-word; margin: 0; } table.source_code td.src pre code { font: 13px "Menlo", "Courier New"; } table.source_code td.coverage { text-align: right; padding-right: 0.5em; } /* Footer */ footer { background-color: #67CDCF; height: 80px; position: absolute; left: 0; bottom: 0; width: 100%; overflow:hidden; } footer p, footer a { color: #ffffff; font-weight: bold; text-align: center; } /* ---------------------------------------------------------- Syntax Highlighting using highlight.js (https://highlightjs.org) ------------------------------------------------------------- */ .hljs { display: block; overflow-x: auto; -webkit-text-size-adjust: none; } .hljs, .hljs-subst, .hljs-tag .hljs-title, .nginx .hljs-title { color: #333; } .hljs-string, .hljs-title, .hljs-constant, .hljs-parent, .hljs-tag .hljs-value, .hljs-rule .hljs-value, .hljs-preprocessor, .hljs-pragma, .hljs-name, .haml .hljs-symbol, .ruby .hljs-symbol, .ruby .hljs-symbol .hljs-string, .hljs-template_tag, .django .hljs-variable, .smalltalk .hljs-class, .hljs-addition, .hljs-flow, .hljs-stream, .bash .hljs-variable, .pf .hljs-variable, .apache .hljs-tag, .apache .hljs-cbracket, .tex .hljs-command, .tex .hljs-special, .erlang_repl .hljs-function_or_atom, .asciidoc .hljs-header, .markdown .hljs-header, .coffeescript .hljs-attribute, .tp .hljs-variable { color: #D14F4F; } .smartquote, .hljs-comment, .hljs-annotation, .diff .hljs-header, .hljs-chunk, .asciidoc .hljs-blockquote, .markdown .hljs-blockquote { color: #888; } .hljs-number, .hljs-date, .hljs-regexp, .hljs-literal, .hljs-hexcolor, .smalltalk .hljs-symbol, .smalltalk .hljs-char, .go .hljs-constant, .hljs-change, .lasso .hljs-variable, .makefile .hljs-variable, .asciidoc .hljs-bullet, .markdown .hljs-bullet, .asciidoc .hljs-link_url, .markdown .hljs-link_url { color: #05A5A8; } .hljs-label, .ruby .hljs-string, .hljs-decorator, .hljs-filter .hljs-argument, .hljs-localvars, .hljs-array, .hljs-attr_selector, .hljs-important, .hljs-pseudo, .hljs-pi, .haml .hljs-bullet, .hljs-doctype, .hljs-deletion, .hljs-envvar, .hljs-shebang, .apache .hljs-sqbracket, .nginx .hljs-built_in, .tex .hljs-formula, .erlang_repl .hljs-reserved, .hljs-prompt, .asciidoc .hljs-link_label, .markdown .hljs-link_label, .vhdl .hljs-attribute, .clojure .hljs-attribute, .asciidoc .hljs-attribute, .lasso .hljs-attribute, .coffeescript .hljs-property, .hljs-phony { color: #087599; } .hljs-keyword, .hljs-id, .hljs-title, .hljs-built_in, .css .hljs-tag, .hljs-doctag, .smalltalk .hljs-class, .hljs-winutils, .bash .hljs-variable, .pf .hljs-variable, .apache .hljs-tag, .hljs-type, .hljs-typename, .tex .hljs-command, .asciidoc .hljs-strong, .markdown .hljs-strong, .hljs-request, .hljs-status, .tp .hljs-data, .tp .hljs-io { font-weight: bold; } .asciidoc .hljs-emphasis, .markdown .hljs-emphasis, .tp .hljs-units { font-style: italic; } .nginx .hljs-built_in { font-weight: normal; } .coffeescript .javascript, .javascript .xml, .lasso .markup, .tex .hljs-formula, .xml .javascript, .xml .vbscript, .xml .css, .xml .hljs-cdata { opacity: 0.5; } /* ------------------------------------------------------- Sorting & Filtering with List.js (http://www.listjs.com/) ------------------------------------------------------- */ input.search { border:solid 1px #ccc; border-radius: 4px; padding:7px; margin-bottom:10px; font-size: 12px; } input.search:focus { outline:none; border-color:#aaa; } th.sort::-moz-selection { background:transparent; } th.sort::selection { background:transparent; } th.sort { cursor:pointer; } th.sort:after { content:''; display:inline-block; width: 0; height: 0; position: relative; top: -3px; right: -6px; border-width:0 4px 4px; border-style:solid; border-color:#404040 transparent; visibility:hidden; } th.sort:hover:after { visibility:visible; } th.sort.desc:after, th.sort.asc:after, th.sort.asc:hover:after { visibility:visible; opacity:0.6; } th.sort.desc:after { border-bottom:none; border-width:4px 4px 0; } ================================================ FILE: fastlane/Appfile ================================================ app_identifier "" # The bundle identifier of your app apple_id "" # Your Apple email address team_id "" # Developer Portal Team ID # you can even provide different app identifiers, Apple IDs and team names per lane: # More information: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md ================================================ FILE: fastlane/Fastfile ================================================ # Customise this file, documentation can be found here: # https://github.com/fastlane/fastlane/tree/master/fastlane/docs # All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md # can also be listed using the `fastlane actions` command # Change the syntax highlighting to Ruby # All lines starting with a # are ignored when running `fastlane` # If you want to automatically update fastlane if a new version is available: # update_fastlane # This is the minimum version number required. # Update this, if you use features of a newer version fastlane_version "1.110.0" default_platform :ios platform :ios do before_all do # ENV["SLACK_URL"] = "https://hooks.slack.com/services/..." cocoapods end desc "Runs all the tests" lane :test do scan(scheme: "Marvel") slather( output_directory: "coverage", workspace: "Marvel.xcworkspace", scheme: "Marvel", proj: "Marvel.xcodeproj", html: true, ignore: [ "**/Storyboard.swift", "**/MarvelAPI.swift", "**/MarvelAPIManager.swift" ] ) end desc "Submit a new Beta Build to Apple TestFlight" desc "This will also make sure the profile is up to date" lane :beta do # match(type: "appstore") # more information: https://codesigning.guide gym(scheme: "Marvel") # Build your app - more options available pilot # sh "your_script.sh" # You can also use other beta testing services here (run `fastlane actions`) end desc "Deploy a new version to the App Store" lane :release do # match(type: "appstore") # snapshot gym(scheme: "Marvel") # Build your app - more options available deliver(force: true) # frameit end # You can define as many lanes as you want after_all do |lane| # This block is called, only if the executed lane was successful # slack( # message: "Successfully deployed new App Update." # ) end error do |lane, exception| # slack( # message: exception.message, # success: false # ) end end # More information about multiple platforms in fastlane: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Platforms.md # All available actions: https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Actions.md # fastlane reports which actions are used # No personal data is recorded. Learn more at https://github.com/fastlane/enhancer ================================================ FILE: fastlane/README.md ================================================ fastlane documentation ================ # Installation ``` sudo gem install fastlane ``` # Available Actions ## iOS ### ios test ``` fastlane ios test ``` Runs all the tests ### ios beta ``` fastlane ios beta ``` Submit a new Beta Build to Apple TestFlight This will also make sure the profile is up to date ### ios release ``` fastlane ios release ``` Deploy a new version to the App Store ---- This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. More information about fastlane can be found on [https://fastlane.tools](https://fastlane.tools). The documentation of fastlane can be found on [GitHub](https://github.com/fastlane/fastlane/tree/master/fastlane).