{"id":20740060,"url":"https://github.com/rcasanovan/weather-app","last_synced_at":"2025-08-10T22:05:08.290Z","repository":{"id":243133260,"uuid":"159937900","full_name":"rcasanovan/weather-app","owner":"rcasanovan","description":"A simple weather app for iOS","archived":false,"fork":false,"pushed_at":"2018-12-21T19:09:12.000Z","size":1268,"stargazers_count":2,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-01-18T01:43:48.245Z","etag":null,"topics":["cocoapods","protocol","reachability","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-01T11:30:29.000Z","updated_at":"2021-08-03T04:35:08.000Z","dependencies_parsed_at":"2024-06-06T22:41:01.448Z","dependency_job_id":"3509f264-4cde-421c-9e8e-42eaccf5ebdd","html_url":"https://github.com/rcasanovan/weather-app","commit_stats":null,"previous_names":["rcasanovan/weather-app"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcasanovan%2Fweather-app","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcasanovan%2Fweather-app/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcasanovan%2Fweather-app/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rcasanovan%2Fweather-app/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rcasanovan","download_url":"https://codeload.github.com/rcasanovan/weather-app/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","reachability","realm","swift","viper","viper-architecture","viper-pattern-architecture"],"created_at":"2024-11-17T06:27:13.703Z","updated_at":"2025-03-11T11:50:23.785Z","avatar_url":"https://github.com/rcasanovan.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# weather-app\n\nThis is a project to create a simple weather app for iOS\n\n## 🚨 Important note 🚨\n\nThis project is using cocoapods but a gitignore file is there so the third-party libraries are not part of the repo. 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 APP ID in EndPoint swift file\n\n```swift\nstatic let appID: String = \"ADD YOUR API ID HERE\"\n```\n\n## FAQS 🤔\n\n* **Q: I want to test the project using the Xcode simulator. How could I get a location (latitude, longitude)?**\n\nYou can use \n\n```swift\nLocationManager.shared.simulateLocation = true\n```\n\nAt **didFinishLaunchingWithOptions** in **AppDelegate** file. In this case you'll use a simulated location (50.075539, 14.437800)\n\n* **Q: What´s the logic to refresh the weather when you're using the app?**\n\nWe have a few scenarios here:\n* You install the app: The app will get the wather information from the server\n* You open the app once installed: If more than 20 min has passed since the last synchronization, the app will get the weather information from the server. In any other case, it will display the information locally.\n* If the user has moved more than two kilometers since the last synchronization, the app will get the weather information from the server. In any other case, it will display the information locally.\n\n* **Q: Wich information is shared using the app?**\n\nIf the user decided to press the share option he / she will be able to share a simple text with link to this repo and a screenshot of the Today screen.\n\n\n## Project Architecture \n![alt tag](https://github.com/rcasanovan/weather-app/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.\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 TodayViewInjection : class {\n    func loadWeatherInformationWithViewModel(_ viewModel: TodayViewModel)\n}\n\nprotocol TodayPresenterDelegate : class {\n    func viewDidLoad()\n}\n\n// Presenter / Interactor\ntypealias getWeatherInteractorCompletionBlock = (_ viewModel: TodayViewModel?, _ success: Bool, _ error: ResultError?) -\u003e Void\n\nprotocol TodayInteractorDelegate : class {\n    func requestLocationAuthorizationIfNeeded()\n    func getCurrentWeather(completion: @escaping getWeatherInteractorCompletionBlock)\n    func getLocalWeatherInformation() -\u003e TodayViewModel?\n    func shouldGetLocalWeatherInformation() -\u003e Bool\n}\n```\n\n## First at all. Where is the data came from?\n\nI'm using the api from **open weather map** (you can check the api documentation [here](http://openweathermap.org/api)).\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 movies in a JSON format like this:\n\n```json\n{\n    \"cod\": \"200\",\n    \"message\": 0.178,\n    \"cnt\": 40,\n    \"list\": [\n        {\n            \"dt\": 1543687200,\n            \"main\": {\n                \"temp\": 8.52,\n                \"temp_min\": 8.17,\n                \"temp_max\": 8.52,\n                \"pressure\": 992.89,\n                \"sea_level\": 1034.86,\n                \"grnd_level\": 992.89,\n                \"humidity\": 72,\n                \"temp_kf\": 0.35\n            },\n            \"weather\": [\n                {\n                    \"id\": 800,\n                    \"main\": \"Clear\",\n                    \"description\": \"clear sky\",\n                    \"icon\": \"01n\"\n                }\n            ],\n            \"clouds\": {\n                \"all\": 0\n            },\n            \"wind\": {\n                \"speed\": 0.96,\n                \"deg\": 244.502\n            },\n            \"sys\": {\n                \"pod\": \"n\"\n            },\n            \"dt_txt\": \"2018-12-01 18:00:00\"\n        },\n        ...\n    ],\n    \"city\": {\n    \t\"id\": 2509954,\n        \"name\": \"Valencia\",\n        \"coord\": {\n            \"lat\": 39.4697,\n            \"lon\": -0.3774\n        },\n        \"country\": \"ES\",\n        \"population\": 814208\n    }\n}\n```\n\nThis is an example of the api call:\nhttp://api.openweathermap.org/data/2.5/forecast?lat=50.075539\u0026lon=14.4378\u0026APPID=28bc6d7ace6e643065fd95756fae8b9c\u0026units=imperial\u0026lang=en\n\n## Data models\n\n### Network data models\n\nThese includes the following models:\n\n```swift\npublic struct WeatherResponse: Codable {\n    let cod: String\n    let message: CGFloat\n    let cnt: UInt\n    let list: [WeatherListResponse]\n    let city: WeatherCityResponse\n}\n\npublic struct WeatherListResponse: Codable {\n    let dt: Double\n    let main: WeatherListMainResponse\n    let weather: [WeatherListWeatherResponse]\n    let wind: WeatherListWindResponse\n    let rain: WeatherRainResponse?\n}\n\npublic struct WeatherRainResponse: Codable {\n    let rain3h: CGFloat?\n    \n    //__ This is little trick.\n    //__ The \"rain\" field has another field inside called \"3h\"\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 field\n    enum CodingKeys: String, CodingKey {\n        case rain3h = \"3h\"\n    }\n}\n\npublic struct WeatherListMainResponse: Codable {\n    let temp: CGFloat\n    let temp_min: CGFloat\n    let temp_max: CGFloat\n    let pressure: CGFloat\n    let sea_level: CGFloat\n    let grnd_level: CGFloat\n    let humidity: Int\n    let temp_kf: CGFloat\n}\n\npublic struct WeatherListWeatherResponse: Codable {\n    let id: Int\n    let main: String\n    let description: String\n    let icon: String\n}\n\npublic struct WeatherListWindResponse: Codable {\n    let speed: CGFloat\n    let deg: CGFloat\n}\n\npublic struct WeatherCityResponse: Codable {\n    let id: Int64\n    let name: String\n    let coord: WeatherCityCoordResponse\n    let country: String\n    let population: Int64?\n}\n\npublic struct WeatherCityCoordResponse: Codable {\n    let lat: CGFloat\n    let lon: CGFloat\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. 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\n### Local weather data model\n\nThis model is used for save the last weather information locally:\n\n```swift\nclass LocalWeather: Object {\n    @objc dynamic var weatherId: String?\n    @objc dynamic var weatherData: Data? = nil\n    \n    override class func primaryKey() -\u003e String? {\n        return \"weatherId\"\n    }\n}\n```\n\nAnd this one is used to generate a user with an unique user id:\n\n```swift\nclass User: Object {\n    @objc dynamic var userId: String?\n    \n    override class func primaryKey() -\u003e String? {\n        return \"userId\"\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 (IMSearchSuggestion)\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 5 here:\n\n### RemoteDabaBaseManager\n\nUsed to store information remotely (Firebase)\n\n### LocationManager\n\nUsed to manage all the location stuff (request auth, get the current location)\n\n### ReachabilityManager\n\nUsed to manage the reachability\n\n### LocalWeatherManager\n\nUsed to store the last weather information locally (Realm)\n\n### ShareManager\n\nUser to share the current weather using UIActivityViewController\n\n## How it looks like?\n\n### Today weather \u0026 Forecast\n![alt tag](https://github.com/rcasanovan/weather-app/blob/master/Images/01.png?raw=true)\n![alt tag](https://github.com/rcasanovan/weather-app/blob/master/Images/02.png?raw=true)\n\n### No internet connection \u0026 share option\n![alt tag](https://github.com/rcasanovan/weather-app/blob/master/Images/03.png?raw=true)\n![alt tag](https://github.com/rcasanovan/weather-app/blob/master/Images/04.png?raw=true)\n\n## What's left in the demo?\n\n* Location permission denied: The logic to manage if the user deny the location permission is not defined. Maybe I could show a message to the user in this case.\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\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: 11.0\n\n## Third-Party Libraries\n\n* [RealmSwift](https://github.com/realm/realm-cocoa) (3.7.6): A mobile database that runs directly inside phones, tablets or wearables.\n* [ReachabilitySwift](https://github.com/ashleymills/Reachability.swift) (4.2.1): Replacement for Apple's Reachability re-written in Swift with callbacks.\n* [CollectionViewCenteredFlowLayout](https://github.com/Coeur/CollectionViewCenteredFlowLayout) (1.0.1): A layout for UICollectionView that aligns the cells to the center.\n* [Firebase](https://firebase.google.com): A Backend as a Service —BaaS—.\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%2Fweather-app","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frcasanovan%2Fweather-app","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frcasanovan%2Fweather-app/lists"}