{"id":20740069,"url":"https://github.com/rcasanovan/imarvel","last_synced_at":"2025-08-07T20:14:27.554Z","repository":{"id":243133254,"uuid":"162038471","full_name":"rcasanovan/iMarvel","owner":"rcasanovan","description":"A simple iOS app to show heroes and villains from Marvel using the Marvel API","archived":false,"fork":false,"pushed_at":"2019-01-17T22:00:40.000Z","size":3685,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-18T01:43:48.334Z","etag":null,"topics":["cocoapods","protocol","realm","swift","viper","viper-architecture","viper-pattern-architecture"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rcasanovan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-12-16T20:32:12.000Z","updated_at":"2019-09-07T23:07:41.000Z","dependencies_parsed_at":"2024-06-06T22:41:00.660Z","dependency_job_id":"460b7daa-5226-4fc4-b434-ca154018c872","html_url":"https://github.com/rcasanovan/iMarvel","commit_stats":null,"previous_names":["rcasanovan/imarvel"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcasanovan%2FiMarvel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcasanovan%2FiMarvel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcasanovan%2FiMarvel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcasanovan%2FiMarvel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rcasanovan","download_url":"https://codeload.github.com/rcasanovan/iMarvel/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243030775,"owners_count":20224663,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cocoapods","protocol","realm","swift","viper","viper-architecture","viper-pattern-architecture"],"created_at":"2024-11-17T06:27:19.935Z","updated_at":"2025-03-11T11:50:27.966Z","avatar_url":"https://github.com/rcasanovan.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# iMarvel\n\nThis is a project to create a simple app to search Marvel characters\n\n## 🚨 Important note 🚨\n\nThis project is using cocoapods. Please be sure to run the **pod install** command before running the project.\n\nIf you have any doubt about cocoapods you can check the reference [here](https://cocoapods.org).\n\nTo run the project you just need to add your API \u0026 private keys in EndPoint swift file\n\n```swift\nstatic let apiKey: String = \"ADD YOUR API KEY HERE\"\nstatic let privateKey: String = \"ADD YOUR PRIVATE KEY HERE\"\n```\n\n## Project Architecture \n![alt tag](https://github.com/rcasanovan/iMarvel/blob/master/Images/projectArchitecture.jpeg?raw=true)\n\nReferences:\n* [Viper architecture](https://www.objc.io/issues/13-architecture/viper/)\n* [Viper for iOS](https://medium.com/@smalam119/viper-design-pattern-for-ios-application-development-7a9703902af6)\n\n## How did I implement VIPER?\n\nBasically I have a protocol file for each scene in the app. This file defines the interaction between each layer as following:\n\n* View - Presenter: protocols to notify changes and to inject information to the UI.\n* Presenter - Interactor: protocols to request / receive information to / from the interator.\n* Presenter - Router: protocol to define the transitions between scenes (I skiped this protocols for the demo because I have only a scene there).\n\nWhith this protocols file is really easy to know how each layer notify / request / information to the other ones so we don't have any other way to communicate all the layers.\n\nAnother important point is because I'm using protocols it's really easy to define mocks views / presenters / interactors / routers for testing.\n\n```swift\n// View / Presenter\nprotocol CharactersListViewInjection : class {\n    func showProgress(_ show: Bool, status: String)\n    func showProgress(_ show: Bool)\n    func loadCharacters(_ viewModels: [CharactersListViewModel], totalResults: Int, copyright: String?, fromBeginning: Bool, allCharactersLoaded: Bool)\n    func loadSuggestions(_ suggestions: [SuggestionViewModel])\n    func showMessageWith(title: String, message: String, actionTitle: String)\n}\n\nprotocol CharactersListPresenterDelegate : class {\n    func viewDidLoad()\n    func searchCharacter(_ character: String)\n    func loadNextPage()\n    func getSuggestions()\n    func suggestionSelectedAt(index: Int)\n    func showCharacterDetailAt(index: Int)\n    func refreshResults()\n}\n\n// Presenter / Interactor\ntypealias CharactersListGetCharactersCompletionBlock = (_ viewModel: [CharactersListViewModel]?, _ total: Int, _ copyright: String?, _ success: Bool, _ error: ResultError?, _ allCharactersSync: Bool) -\u003e Void\ntypealias CharactersListGetSuggestionsCompletionBlock = ([SuggestionViewModel]) -\u003e Void\n\nprotocol CharactersListInteractorDelegate : class {\n    func shouldGetCharacters() -\u003e Bool\n    func clearSearch()\n    func getCharactersWith(character: String?, completion: @escaping CharactersListGetCharactersCompletionBlock)\n    func saveSearch(_ search: String)\n    func getAllSuggestions(completion: @escaping CharactersListGetSuggestionsCompletionBlock)\n    func getSuggestionAt(index: Int) -\u003e SuggestionViewModel?\n    func getCurrentSearchCharacter() -\u003e String?\n    func updateSearchCharacter(_ searchCharacter: String)\n    func getCharacterAt(index: Int) -\u003e CharactersListViewModel?\n}\n\n// Presenter / Router\nprotocol CharactersListRouterDelegate : class {\n    func showDetail(_ character: CharactersListViewModel)\n}\n```\n\n## First at all. Where is the data came from?\n\nI'm using the api from **Marvel** (you can check the api documentation [here](https://developer.marvel.com/)).\n\nYou just need to create an account to have access to the api. Once you do it you'll able to get information for characters in a JSON format.\n\n## Data models\n\n### Network data models\n\n#### Character list data models\n\n```swift\npublic struct CharactersResponse: Decodable {\n    \n    let copyright: String\n    let attributionText: String\n    let data: DataResponse\n    \n}\n\npublic struct DataResponse: Decodable {\n    \n    let offset: Int\n    let limit: Int\n    let total: Int\n    let count: Int\n    let results: [CharacterResponse]\n    \n}\n\npublic struct CharacterResponse: Decodable {\n    \n    let id: Int32\n    let name: String\n    let description: String\n    let modified: String\n    let thumbnail: ThumbnailResponse\n    let comics: ParticipationResponse\n    let series: ParticipationResponse\n    let stories: ParticipationResponse\n    let events: ParticipationResponse\n    \n}\n\npublic struct ThumbnailResponse: Decodable {\n    \n    let path: String\n    let ext: String\n\n    //__ This is little trick.\n    //__ The \"thumbnail\" field has another field inside called \"extension\"\n    //__ The problem is we can't process this field using Swift\n    //__ so we need to create an enum like a \"bridge\" to process the fields\n    enum CodingKeys: String, CodingKey {\n        case path = \"path\"\n        case ext = \"extension\"\n    }\n    \n}\n\npublic struct ParticipationResponse: Decodable {\n    \n    let available: Int\n    \n}\n```\n\n#### Character detail data models\n\n```swift\npublic struct ComicsResponse: Decodable {\n    \n    let copyright: String\n    let attributionText: String\n    let data: ComicDataResponse\n    \n}\n\npublic struct ComicDataResponse: Decodable {\n    \n    let offset: Int\n    let limit: Int\n    let total: Int\n    let count: Int\n    let results: [ComicResponse]\n    \n}\n\npublic struct ComicResponse: Decodable {\n    \n    let id: Int32\n    let title: String\n    let description: String?\n    let thumbnail: ThumbnailResponse?\n    let urls: [UrlResponse]?\n    \n}\n\npublic struct UrlResponse: Decodable {\n    \n    let type: String\n    let url: String\n    \n}\n```\n\n\nI'm using a Swift Standard Library decodable functionality in order to manage a type that can decode itself from an external representation (I really ❤ this from Swift).\n\n**Are more properties there??**\n\nObviously the response has more properties for each character. I decided to use only these ones.\n\nReference: [Apple documentation](https://developer.apple.com/documentation/swift/swift_standard_library/encoding_decoding_and_serialization)\n\n### Suggestions data model\n\nThis model is used for the characters suggestions (last 10​ ​successful​ ​queries​ - exclude​ ​suggestions​ ​that​ ​return​ ​errors)\n\n```swift\nclass IMSearchSuggestion: Object {\n    @objc dynamic var suggestionId: String?\n    @objc dynamic var suggestion: String = \"\"\n    @objc dynamic var timestamp: TimeInterval = NSDate().timeIntervalSince1970\n    \n    override class func primaryKey() -\u003e String? {\n        return \"suggestionId\"\n    }\n}\n```\n\nAs I'm using Realm for this it's important to define a class to manage each model in the database. In this case we only have one model (SearchSuggestion)\n\nReference: [Realm](https://realm.io/docs/swift/latest)\n\n## Managers\n\nI think using managers is a good idea but be careful!. Please don't create managers as if the world were going to end tomorrow.\n\nI'm using only 3 here:\n\n### ImageManager\n\nUsed to manage the images (create the urls to retrieve the images)\n\n### ReachabilityManager\n\nUsed to manage the reachability. In this case I would like to notify a little issue related with the simulator. *It seems Xcode has an issue with the simulator because if you try to turn off the wifi and turning on again, the observer for the state change is not triggering. It's working 100% fine in a real device*\n\n## How it looks like?\n\n### Character list results\n![alt tag](https://github.com/rcasanovan/iMarvel/blob/master/Images/01.png?raw=true)\n![alt tag](https://github.com/rcasanovan/iMarvel/blob/master/Images/07.png?raw=true)\n\n### Character detail\n![alt tag](https://github.com/rcasanovan/iMarvel/blob/master/Images/02.png?raw=true)\n![alt tag](https://github.com/rcasanovan/iMarvel/blob/master/Images/03.png?raw=true)\n![alt tag](https://github.com/rcasanovan/iMarvel/blob/master/Images/08.png?raw=true)\n![alt tag](https://github.com/rcasanovan/iMarvel/blob/master/Images/05.png?raw=true)\n\n### Handling errors and states\n![alt tag](https://github.com/rcasanovan/iMarvel/blob/master/Images/10.png?raw=true)\n![alt tag](https://github.com/rcasanovan/iMarvel/blob/master/Images/09.png?raw=true)\n![alt tag](https://github.com/rcasanovan/iMarvel/blob/master/Images/04.png?raw=true)\n![alt tag](https://github.com/rcasanovan/iMarvel/blob/master/Images/06.png?raw=true)\n\n## What's left in the demo?\n\n* Realm migration process: It would be nice to add a process to migrate the realm database to a new model (just in case you need to add a new field into the database)\n* Localizable files: This demo doesn't include the localizable files to translate the app to different languages.\n* Limit the number of retries for the api calls: This demo doesn't include a limitation for the number of retries while the app is doing the api calls.\n\n## Programming languages \u0026\u0026 Development tools\n\n* Swift 4.2\n* Xcode 10.1\n* [Cocoapods](https://cocoapods.org) 1.5.3\n* Minimun iOS version: 12.1\n\n## Third-Party Libraries\n\n* [Haneke](https://github.com/Haneke/Haneke) (1.0): A lightweight zero-config image cache for iOS\n* [RealmSwift](https://github.com/realm/realm-cocoa) (3.7.6): A mobile database that runs directly inside phones, tablets or wearables\n* [SVProgressHUD](https://github.com/SVProgressHUD/SVProgressHUD) (2.2.5): A clean and lightweight progress HUD for your iOS and tvOS app.\n\n## Support \u0026\u0026 contact\n\n### Email\n\nYou can contact me using my email: ricardo.casanova@outlook.com\n\n### Twitter\n\nFollow me [@rcasanovan](http://twitter.com/rcasanovan) on twitter.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcasanovan%2Fimarvel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frcasanovan%2Fimarvel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcasanovan%2Fimarvel/lists"}