├── README.md ├── Screenshoot └── Screenshoot-1.png ├── Weather.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── irfanrafi.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist └── Weather ├── App ├── AppDelegate.swift ├── Base.lproj │ └── LaunchScreen.storyboard ├── HostingController.swift └── SceneDelegate.swift ├── Assets ├── Assets.xcassets │ ├── 01d.imageset │ │ ├── 01d@2x.png │ │ └── Contents.json │ ├── 01n.imageset │ │ ├── 01n@2x.png │ │ └── Contents.json │ ├── 02d.imageset │ │ ├── 02d@2x.png │ │ └── Contents.json │ ├── 02n.imageset │ │ ├── 02n@2x.png │ │ └── Contents.json │ ├── 03d.imageset │ │ ├── 03d@2x.png │ │ └── Contents.json │ ├── 03n.imageset │ │ ├── 03n@2x.png │ │ └── Contents.json │ ├── 04d.imageset │ │ ├── 04d@2x.png │ │ └── Contents.json │ ├── 04n.imageset │ │ ├── 04n@2x.png │ │ └── Contents.json │ ├── 09d.imageset │ │ ├── 09d@2x.png │ │ └── Contents.json │ ├── 09n.imageset │ │ ├── 09n@2x.png │ │ └── Contents.json │ ├── 10d.imageset │ │ ├── 10d@2x.png │ │ └── Contents.json │ ├── 10n.imageset │ │ ├── 10n@2x.png │ │ └── Contents.json │ ├── 11d.imageset │ │ ├── 11d@2x.png │ │ └── Contents.json │ ├── 11n.imageset │ │ ├── 11n@2x.png │ │ └── Contents.json │ ├── 13d.imageset │ │ ├── 13d@2x.png │ │ └── Contents.json │ ├── 13n.imageset │ │ ├── 13n@2x.png │ │ └── Contents.json │ ├── 50d.imageset │ │ ├── 50d@2x.png │ │ └── Contents.json │ ├── 50n.imageset │ │ ├── 50n@2x.png │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── 100.png │ │ ├── 1024.png │ │ ├── 114.png │ │ ├── 120.png │ │ ├── 144.png │ │ ├── 152.png │ │ ├── 167.png │ │ ├── 172.png │ │ ├── 180.png │ │ ├── 196.png │ │ ├── 20.png │ │ ├── 216.png │ │ ├── 29.png │ │ ├── 40.png │ │ ├── 48.png │ │ ├── 50.png │ │ ├── 55.png │ │ ├── 57.png │ │ ├── 58.png │ │ ├── 60.png │ │ ├── 72.png │ │ ├── 76.png │ │ ├── 80.png │ │ ├── 87.png │ │ ├── 88.png │ │ └── Contents.json │ └── Contents.json └── WeatherLogo.png ├── Info.plist ├── Model ├── Coordinate.swift ├── CurrentWeather.swift ├── CurrentWeatherMainValue.swift ├── CurrentWeatherSys.swift ├── ForecastWeather.swift ├── ForecastWeatherCity.swift ├── ForecastWeatherMainValue.swift ├── ForecastWeatherResponse.swift ├── StateView.swift ├── WeatherClouds.swift ├── WeatherElement.swift └── WeatherWind.swift ├── Network ├── OpenweatherAPIClient.swift └── ResponseError.swift ├── Preview Content └── Preview Assets.xcassets │ ├── Contents.json │ └── WeatherIcon.imageset │ ├── 20.png │ ├── 40.png │ ├── 60.png │ └── Contents.json ├── Screen └── MainScreenView.swift ├── Utilities └── extension.swift ├── View ├── ActivityIndicatorView.swift ├── BackgroundView.swift ├── DailyWeatherCellView.swift ├── DailyWeatherView.swift ├── DetailsCurrentWeatherCellView.swift ├── DetailsCurrentWeatherView.swift ├── HourlyWeatherCellView.swift ├── HourlyWeatherView.swift └── LocationAndTemperatureHeaderView.swift └── ViewModel └── WeatherViewModel.swift /README.md: -------------------------------------------------------------------------------- 1 | # iOS WeatherApp with SwiftUI 2 | 3 |
4 | 5 | 6 | ## Overview 7 | 8 | `iOS WeatherApp` is copy design from `Apple Weather App` uses [Openweathermap API](https://openweathermap.org/api) and build with SwiftUI. Feature this app same with apple weather like get current weather & details, get hourly forecast weather and get daily forecast. But detail not fully same because limited feature free plan from Openweathermap API. 9 | 10 | ### Keyword 11 | - SwiftUI 12 | - JSON & Codable 13 | - MVVM 14 | - ObservableObject 15 | 16 | ## Getting Started 17 | 18 | ### Prerequisites 19 | 20 | - A valid API key from Openweathermap 21 | - A Mac running macOS Catalina 22 | - Xcode 11.3.1 23 | 24 | ### Installation 25 | 26 | 1. Clone or download the project to your local machine 27 | 2. Open the project in Xcode 28 | 3. Replace `YOURAPIKEY` with your valid Openweathermap API key in `OpenweathermapAPIClient.swift` 29 | 30 | ```swift 31 | class OpenweathermapClient { 32 | 33 | ``` 34 | private let apiKey = "YOURAPIKEY" 35 | ``` 36 | 37 | 4. Replace `cityId` with your city want to use in `WeatherViewModel.swift` you can download list city json in [here](http://bulk.openweathermap.org/sample/) 38 | 39 | ```swift 40 | class WeatherViewModel: ObservableObject { 41 | 42 | ``` 43 | private let cityId = "1627459" 44 | ``` 45 | 46 | 5. Run the simulator 47 | 48 | ## Thanks to 49 | 50 | Apple amazing library [SwiftUI](https://developer.apple.com/xcode/swiftui/) 51 | 52 | Open API from [OpenWeatherMap](https://openweathermap.org/api) 53 | 54 | Inspiring project from [Weather-SwiftUI](https://github.com/bobbyconti/Weather-SwiftUI) 55 | -------------------------------------------------------------------------------- /Screenshoot/Screenshoot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Screenshoot/Screenshoot-1.png -------------------------------------------------------------------------------- /Weather.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EC04C6A5243471F00010AD0C /* BackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC04C6A4243471F00010AD0C /* BackgroundView.swift */; }; 11 | EC04C6A7243472D50010AD0C /* LocationAndTemperatureHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC04C6A6243472D50010AD0C /* LocationAndTemperatureHeaderView.swift */; }; 12 | EC116388243823EE0032E98A /* ForecastWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC116387243823EE0032E98A /* ForecastWeather.swift */; }; 13 | EC11638B24382ADE0032E98A /* ForecastWeatherMainValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC11638A24382ADE0032E98A /* ForecastWeatherMainValue.swift */; }; 14 | EC11638D24382AF50032E98A /* WeatherElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC11638C24382AF50032E98A /* WeatherElement.swift */; }; 15 | EC11638F24382B0E0032E98A /* WeatherWind.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC11638E24382B0E0032E98A /* WeatherWind.swift */; }; 16 | EC11639124382B260032E98A /* WeatherClouds.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC11639024382B260032E98A /* WeatherClouds.swift */; }; 17 | EC17BEBF24348D640030A904 /* DailyWeatherCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC17BEBE24348D640030A904 /* DailyWeatherCellView.swift */; }; 18 | EC17BEC12434A3E10030A904 /* HourlyWeatherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC17BEC02434A3E10030A904 /* HourlyWeatherView.swift */; }; 19 | EC17BEC32434A4A70030A904 /* HourlyWeatherCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC17BEC22434A4A70030A904 /* HourlyWeatherCellView.swift */; }; 20 | EC17BEC52434A8320030A904 /* DailyWeatherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC17BEC42434A8320030A904 /* DailyWeatherView.swift */; }; 21 | EC17BEC72434B2EC0030A904 /* HostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC17BEC62434B2EC0030A904 /* HostingController.swift */; }; 22 | EC481E6B2439CBDC00454911 /* extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC481E6A2439CBDC00454911 /* extension.swift */; }; 23 | EC6DAA0C24395B4F005F4C79 /* CurrentWeather.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6DAA0B24395B4F005F4C79 /* CurrentWeather.swift */; }; 24 | EC6DAA0E24395C52005F4C79 /* CurrentWeatherMainValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6DAA0D24395C52005F4C79 /* CurrentWeatherMainValue.swift */; }; 25 | EC6DAA1024395CF6005F4C79 /* Coordinate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6DAA0F24395CF6005F4C79 /* Coordinate.swift */; }; 26 | EC6DAA1224395E23005F4C79 /* CurrentWeatherSys.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6DAA1124395E23005F4C79 /* CurrentWeatherSys.swift */; }; 27 | EC6DAA1424395EB6005F4C79 /* ForecastWeatherResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6DAA1324395EB6005F4C79 /* ForecastWeatherResponse.swift */; }; 28 | EC6DAA1624395EF4005F4C79 /* ForecastWeatherCity.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6DAA1524395EF4005F4C79 /* ForecastWeatherCity.swift */; }; 29 | EC6DAA1824396031005F4C79 /* OpenweatherAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6DAA1724396031005F4C79 /* OpenweatherAPIClient.swift */; }; 30 | EC6DAA1A24396C89005F4C79 /* ResponseError.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6DAA1924396C89005F4C79 /* ResponseError.swift */; }; 31 | EC775B7C243059530051F63E /* WeatherLogo.png in Resources */ = {isa = PBXBuildFile; fileRef = EC775B7B243059530051F63E /* WeatherLogo.png */; }; 32 | EC7E01DF243A0E7000072F3B /* StateView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7E01DE243A0E7000072F3B /* StateView.swift */; }; 33 | EC7E01E1243A10B200072F3B /* ActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7E01E0243A10B200072F3B /* ActivityIndicatorView.swift */; }; 34 | EC7E01E3243A1AA100072F3B /* DetailsCurrentWeatherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7E01E2243A1AA100072F3B /* DetailsCurrentWeatherView.swift */; }; 35 | EC7E01E5243A233700072F3B /* DetailsCurrentWeatherCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7E01E4243A233700072F3B /* DetailsCurrentWeatherCellView.swift */; }; 36 | EC9431F2243A08A100F0345E /* WeatherViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC9431F1243A08A100F0345E /* WeatherViewModel.swift */; }; 37 | ECBF87BB242AEE1B0004638B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBF87BA242AEE1B0004638B /* AppDelegate.swift */; }; 38 | ECBF87BD242AEE1B0004638B /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBF87BC242AEE1B0004638B /* SceneDelegate.swift */; }; 39 | ECBF87BF242AEE1B0004638B /* MainScreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBF87BE242AEE1B0004638B /* MainScreenView.swift */; }; 40 | ECBF87C1242AEE1C0004638B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ECBF87C0242AEE1C0004638B /* Assets.xcassets */; }; 41 | ECBF87C4242AEE1D0004638B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ECBF87C3242AEE1D0004638B /* Preview Assets.xcassets */; }; 42 | ECBF87C7242AEE1D0004638B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ECBF87C5242AEE1D0004638B /* LaunchScreen.storyboard */; }; 43 | /* End PBXBuildFile section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | EC04C6A4243471F00010AD0C /* BackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundView.swift; sourceTree = ""; }; 47 | EC04C6A6243472D50010AD0C /* LocationAndTemperatureHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationAndTemperatureHeaderView.swift; sourceTree = ""; }; 48 | EC116387243823EE0032E98A /* ForecastWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastWeather.swift; sourceTree = ""; }; 49 | EC11638A24382ADE0032E98A /* ForecastWeatherMainValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastWeatherMainValue.swift; sourceTree = ""; }; 50 | EC11638C24382AF50032E98A /* WeatherElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherElement.swift; sourceTree = ""; }; 51 | EC11638E24382B0E0032E98A /* WeatherWind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherWind.swift; sourceTree = ""; }; 52 | EC11639024382B260032E98A /* WeatherClouds.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherClouds.swift; sourceTree = ""; }; 53 | EC17BEBE24348D640030A904 /* DailyWeatherCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyWeatherCellView.swift; sourceTree = ""; }; 54 | EC17BEC02434A3E10030A904 /* HourlyWeatherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HourlyWeatherView.swift; sourceTree = ""; }; 55 | EC17BEC22434A4A70030A904 /* HourlyWeatherCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HourlyWeatherCellView.swift; sourceTree = ""; }; 56 | EC17BEC42434A8320030A904 /* DailyWeatherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DailyWeatherView.swift; sourceTree = ""; }; 57 | EC17BEC62434B2EC0030A904 /* HostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HostingController.swift; sourceTree = ""; }; 58 | EC481E6A2439CBDC00454911 /* extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = extension.swift; sourceTree = ""; }; 59 | EC6DAA0B24395B4F005F4C79 /* CurrentWeather.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentWeather.swift; sourceTree = ""; }; 60 | EC6DAA0D24395C52005F4C79 /* CurrentWeatherMainValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentWeatherMainValue.swift; sourceTree = ""; }; 61 | EC6DAA0F24395CF6005F4C79 /* Coordinate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Coordinate.swift; sourceTree = ""; }; 62 | EC6DAA1124395E23005F4C79 /* CurrentWeatherSys.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CurrentWeatherSys.swift; sourceTree = ""; }; 63 | EC6DAA1324395EB6005F4C79 /* ForecastWeatherResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastWeatherResponse.swift; sourceTree = ""; }; 64 | EC6DAA1524395EF4005F4C79 /* ForecastWeatherCity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastWeatherCity.swift; sourceTree = ""; }; 65 | EC6DAA1724396031005F4C79 /* OpenweatherAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenweatherAPIClient.swift; sourceTree = ""; }; 66 | EC6DAA1924396C89005F4C79 /* ResponseError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseError.swift; sourceTree = ""; }; 67 | EC775B7B243059530051F63E /* WeatherLogo.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = WeatherLogo.png; sourceTree = ""; }; 68 | EC7E01DE243A0E7000072F3B /* StateView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateView.swift; sourceTree = ""; }; 69 | EC7E01E0243A10B200072F3B /* ActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorView.swift; sourceTree = ""; }; 70 | EC7E01E2243A1AA100072F3B /* DetailsCurrentWeatherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsCurrentWeatherView.swift; sourceTree = ""; }; 71 | EC7E01E4243A233700072F3B /* DetailsCurrentWeatherCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetailsCurrentWeatherCellView.swift; sourceTree = ""; }; 72 | EC9431F1243A08A100F0345E /* WeatherViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherViewModel.swift; sourceTree = ""; }; 73 | ECBF87B7242AEE1B0004638B /* Weather.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Weather.app; sourceTree = BUILT_PRODUCTS_DIR; }; 74 | ECBF87BA242AEE1B0004638B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 75 | ECBF87BC242AEE1B0004638B /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 76 | ECBF87BE242AEE1B0004638B /* MainScreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainScreenView.swift; sourceTree = ""; }; 77 | ECBF87C0242AEE1C0004638B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 78 | ECBF87C3242AEE1D0004638B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 79 | ECBF87C6242AEE1D0004638B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 80 | ECBF87C8242AEE1D0004638B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 81 | /* End PBXFileReference section */ 82 | 83 | /* Begin PBXFrameworksBuildPhase section */ 84 | ECBF87B4242AEE1B0004638B /* Frameworks */ = { 85 | isa = PBXFrameworksBuildPhase; 86 | buildActionMask = 2147483647; 87 | files = ( 88 | ); 89 | runOnlyForDeploymentPostprocessing = 0; 90 | }; 91 | /* End PBXFrameworksBuildPhase section */ 92 | 93 | /* Begin PBXGroup section */ 94 | EC04C6A3243471D50010AD0C /* Screen */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | ECBF87BE242AEE1B0004638B /* MainScreenView.swift */, 98 | ); 99 | path = Screen; 100 | sourceTree = ""; 101 | }; 102 | EC481E692439CBCA00454911 /* Utilities */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | EC481E6A2439CBDC00454911 /* extension.swift */, 106 | ); 107 | path = Utilities; 108 | sourceTree = ""; 109 | }; 110 | EC775B75243054440051F63E /* Model */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | EC6DAA0F24395CF6005F4C79 /* Coordinate.swift */, 114 | EC6DAA0B24395B4F005F4C79 /* CurrentWeather.swift */, 115 | EC6DAA0D24395C52005F4C79 /* CurrentWeatherMainValue.swift */, 116 | EC6DAA1124395E23005F4C79 /* CurrentWeatherSys.swift */, 117 | EC116387243823EE0032E98A /* ForecastWeather.swift */, 118 | EC6DAA1524395EF4005F4C79 /* ForecastWeatherCity.swift */, 119 | EC11638A24382ADE0032E98A /* ForecastWeatherMainValue.swift */, 120 | EC6DAA1324395EB6005F4C79 /* ForecastWeatherResponse.swift */, 121 | EC11639024382B260032E98A /* WeatherClouds.swift */, 122 | EC11638C24382AF50032E98A /* WeatherElement.swift */, 123 | EC11638E24382B0E0032E98A /* WeatherWind.swift */, 124 | EC7E01DE243A0E7000072F3B /* StateView.swift */, 125 | ); 126 | path = Model; 127 | sourceTree = ""; 128 | }; 129 | EC775B76243054510051F63E /* ViewModel */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | EC9431F1243A08A100F0345E /* WeatherViewModel.swift */, 133 | ); 134 | path = ViewModel; 135 | sourceTree = ""; 136 | }; 137 | EC775B772430545C0051F63E /* View */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | EC7E01E0243A10B200072F3B /* ActivityIndicatorView.swift */, 141 | EC04C6A4243471F00010AD0C /* BackgroundView.swift */, 142 | EC17BEBE24348D640030A904 /* DailyWeatherCellView.swift */, 143 | EC17BEC42434A8320030A904 /* DailyWeatherView.swift */, 144 | EC7E01E4243A233700072F3B /* DetailsCurrentWeatherCellView.swift */, 145 | EC7E01E2243A1AA100072F3B /* DetailsCurrentWeatherView.swift */, 146 | EC17BEC22434A4A70030A904 /* HourlyWeatherCellView.swift */, 147 | EC17BEC02434A3E10030A904 /* HourlyWeatherView.swift */, 148 | EC04C6A6243472D50010AD0C /* LocationAndTemperatureHeaderView.swift */, 149 | ); 150 | path = View; 151 | sourceTree = ""; 152 | }; 153 | EC775B78243054A40051F63E /* Network */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | EC6DAA1724396031005F4C79 /* OpenweatherAPIClient.swift */, 157 | EC6DAA1924396C89005F4C79 /* ResponseError.swift */, 158 | ); 159 | path = Network; 160 | sourceTree = ""; 161 | }; 162 | EC775B79243054B60051F63E /* App */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | ECBF87BA242AEE1B0004638B /* AppDelegate.swift */, 166 | ECBF87C5242AEE1D0004638B /* LaunchScreen.storyboard */, 167 | ECBF87BC242AEE1B0004638B /* SceneDelegate.swift */, 168 | EC17BEC62434B2EC0030A904 /* HostingController.swift */, 169 | ); 170 | path = App; 171 | sourceTree = ""; 172 | }; 173 | EC775B7A2430591E0051F63E /* Assets */ = { 174 | isa = PBXGroup; 175 | children = ( 176 | EC775B7B243059530051F63E /* WeatherLogo.png */, 177 | ECBF87C0242AEE1C0004638B /* Assets.xcassets */, 178 | ); 179 | path = Assets; 180 | sourceTree = ""; 181 | }; 182 | ECBF87AE242AEE1B0004638B = { 183 | isa = PBXGroup; 184 | children = ( 185 | ECBF87B9242AEE1B0004638B /* Weather */, 186 | ECBF87B8242AEE1B0004638B /* Products */, 187 | ); 188 | sourceTree = ""; 189 | }; 190 | ECBF87B8242AEE1B0004638B /* Products */ = { 191 | isa = PBXGroup; 192 | children = ( 193 | ECBF87B7242AEE1B0004638B /* Weather.app */, 194 | ); 195 | name = Products; 196 | sourceTree = ""; 197 | }; 198 | ECBF87B9242AEE1B0004638B /* Weather */ = { 199 | isa = PBXGroup; 200 | children = ( 201 | ECBF87C8242AEE1D0004638B /* Info.plist */, 202 | EC775B79243054B60051F63E /* App */, 203 | EC775B75243054440051F63E /* Model */, 204 | EC775B78243054A40051F63E /* Network */, 205 | ECBF87C2242AEE1D0004638B /* Preview Content */, 206 | EC04C6A3243471D50010AD0C /* Screen */, 207 | EC775B772430545C0051F63E /* View */, 208 | EC775B76243054510051F63E /* ViewModel */, 209 | EC481E692439CBCA00454911 /* Utilities */, 210 | EC775B7A2430591E0051F63E /* Assets */, 211 | ); 212 | path = Weather; 213 | sourceTree = ""; 214 | }; 215 | ECBF87C2242AEE1D0004638B /* Preview Content */ = { 216 | isa = PBXGroup; 217 | children = ( 218 | ECBF87C3242AEE1D0004638B /* Preview Assets.xcassets */, 219 | ); 220 | path = "Preview Content"; 221 | sourceTree = ""; 222 | }; 223 | /* End PBXGroup section */ 224 | 225 | /* Begin PBXNativeTarget section */ 226 | ECBF87B6242AEE1B0004638B /* Weather */ = { 227 | isa = PBXNativeTarget; 228 | buildConfigurationList = ECBF87CB242AEE1D0004638B /* Build configuration list for PBXNativeTarget "Weather" */; 229 | buildPhases = ( 230 | ECBF87B3242AEE1B0004638B /* Sources */, 231 | ECBF87B4242AEE1B0004638B /* Frameworks */, 232 | ECBF87B5242AEE1B0004638B /* Resources */, 233 | ); 234 | buildRules = ( 235 | ); 236 | dependencies = ( 237 | ); 238 | name = Weather; 239 | productName = Weather; 240 | productReference = ECBF87B7242AEE1B0004638B /* Weather.app */; 241 | productType = "com.apple.product-type.application"; 242 | }; 243 | /* End PBXNativeTarget section */ 244 | 245 | /* Begin PBXProject section */ 246 | ECBF87AF242AEE1B0004638B /* Project object */ = { 247 | isa = PBXProject; 248 | attributes = { 249 | LastSwiftUpdateCheck = 1130; 250 | LastUpgradeCheck = 1130; 251 | ORGANIZATIONNAME = Irmusyafa; 252 | TargetAttributes = { 253 | ECBF87B6242AEE1B0004638B = { 254 | CreatedOnToolsVersion = 11.3.1; 255 | }; 256 | }; 257 | }; 258 | buildConfigurationList = ECBF87B2242AEE1B0004638B /* Build configuration list for PBXProject "Weather" */; 259 | compatibilityVersion = "Xcode 9.3"; 260 | developmentRegion = en; 261 | hasScannedForEncodings = 0; 262 | knownRegions = ( 263 | en, 264 | Base, 265 | ); 266 | mainGroup = ECBF87AE242AEE1B0004638B; 267 | productRefGroup = ECBF87B8242AEE1B0004638B /* Products */; 268 | projectDirPath = ""; 269 | projectRoot = ""; 270 | targets = ( 271 | ECBF87B6242AEE1B0004638B /* Weather */, 272 | ); 273 | }; 274 | /* End PBXProject section */ 275 | 276 | /* Begin PBXResourcesBuildPhase section */ 277 | ECBF87B5242AEE1B0004638B /* Resources */ = { 278 | isa = PBXResourcesBuildPhase; 279 | buildActionMask = 2147483647; 280 | files = ( 281 | ECBF87C7242AEE1D0004638B /* LaunchScreen.storyboard in Resources */, 282 | ECBF87C4242AEE1D0004638B /* Preview Assets.xcassets in Resources */, 283 | ECBF87C1242AEE1C0004638B /* Assets.xcassets in Resources */, 284 | EC775B7C243059530051F63E /* WeatherLogo.png in Resources */, 285 | ); 286 | runOnlyForDeploymentPostprocessing = 0; 287 | }; 288 | /* End PBXResourcesBuildPhase section */ 289 | 290 | /* Begin PBXSourcesBuildPhase section */ 291 | ECBF87B3242AEE1B0004638B /* Sources */ = { 292 | isa = PBXSourcesBuildPhase; 293 | buildActionMask = 2147483647; 294 | files = ( 295 | EC17BEC72434B2EC0030A904 /* HostingController.swift in Sources */, 296 | EC9431F2243A08A100F0345E /* WeatherViewModel.swift in Sources */, 297 | EC11638F24382B0E0032E98A /* WeatherWind.swift in Sources */, 298 | EC6DAA1624395EF4005F4C79 /* ForecastWeatherCity.swift in Sources */, 299 | EC6DAA0C24395B4F005F4C79 /* CurrentWeather.swift in Sources */, 300 | EC7E01E5243A233700072F3B /* DetailsCurrentWeatherCellView.swift in Sources */, 301 | EC6DAA1024395CF6005F4C79 /* Coordinate.swift in Sources */, 302 | EC11639124382B260032E98A /* WeatherClouds.swift in Sources */, 303 | ECBF87BB242AEE1B0004638B /* AppDelegate.swift in Sources */, 304 | EC6DAA1A24396C89005F4C79 /* ResponseError.swift in Sources */, 305 | EC481E6B2439CBDC00454911 /* extension.swift in Sources */, 306 | EC17BEC52434A8320030A904 /* DailyWeatherView.swift in Sources */, 307 | EC116388243823EE0032E98A /* ForecastWeather.swift in Sources */, 308 | EC6DAA0E24395C52005F4C79 /* CurrentWeatherMainValue.swift in Sources */, 309 | ECBF87BD242AEE1B0004638B /* SceneDelegate.swift in Sources */, 310 | EC04C6A5243471F00010AD0C /* BackgroundView.swift in Sources */, 311 | EC7E01E1243A10B200072F3B /* ActivityIndicatorView.swift in Sources */, 312 | EC6DAA1224395E23005F4C79 /* CurrentWeatherSys.swift in Sources */, 313 | EC04C6A7243472D50010AD0C /* LocationAndTemperatureHeaderView.swift in Sources */, 314 | EC17BEC32434A4A70030A904 /* HourlyWeatherCellView.swift in Sources */, 315 | ECBF87BF242AEE1B0004638B /* MainScreenView.swift in Sources */, 316 | EC17BEBF24348D640030A904 /* DailyWeatherCellView.swift in Sources */, 317 | EC7E01DF243A0E7000072F3B /* StateView.swift in Sources */, 318 | EC11638B24382ADE0032E98A /* ForecastWeatherMainValue.swift in Sources */, 319 | EC6DAA1824396031005F4C79 /* OpenweatherAPIClient.swift in Sources */, 320 | EC17BEC12434A3E10030A904 /* HourlyWeatherView.swift in Sources */, 321 | EC6DAA1424395EB6005F4C79 /* ForecastWeatherResponse.swift in Sources */, 322 | EC7E01E3243A1AA100072F3B /* DetailsCurrentWeatherView.swift in Sources */, 323 | EC11638D24382AF50032E98A /* WeatherElement.swift in Sources */, 324 | ); 325 | runOnlyForDeploymentPostprocessing = 0; 326 | }; 327 | /* End PBXSourcesBuildPhase section */ 328 | 329 | /* Begin PBXVariantGroup section */ 330 | ECBF87C5242AEE1D0004638B /* LaunchScreen.storyboard */ = { 331 | isa = PBXVariantGroup; 332 | children = ( 333 | ECBF87C6242AEE1D0004638B /* Base */, 334 | ); 335 | name = LaunchScreen.storyboard; 336 | sourceTree = ""; 337 | }; 338 | /* End PBXVariantGroup section */ 339 | 340 | /* Begin XCBuildConfiguration section */ 341 | ECBF87C9242AEE1D0004638B /* Debug */ = { 342 | isa = XCBuildConfiguration; 343 | buildSettings = { 344 | ALWAYS_SEARCH_USER_PATHS = NO; 345 | CLANG_ANALYZER_NONNULL = YES; 346 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 347 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 348 | CLANG_CXX_LIBRARY = "libc++"; 349 | CLANG_ENABLE_MODULES = YES; 350 | CLANG_ENABLE_OBJC_ARC = YES; 351 | CLANG_ENABLE_OBJC_WEAK = YES; 352 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 353 | CLANG_WARN_BOOL_CONVERSION = YES; 354 | CLANG_WARN_COMMA = YES; 355 | CLANG_WARN_CONSTANT_CONVERSION = YES; 356 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 357 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 358 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 359 | CLANG_WARN_EMPTY_BODY = YES; 360 | CLANG_WARN_ENUM_CONVERSION = YES; 361 | CLANG_WARN_INFINITE_RECURSION = YES; 362 | CLANG_WARN_INT_CONVERSION = YES; 363 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 364 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 365 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 366 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 367 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 368 | CLANG_WARN_STRICT_PROTOTYPES = YES; 369 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 370 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 371 | CLANG_WARN_UNREACHABLE_CODE = YES; 372 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 373 | COPY_PHASE_STRIP = NO; 374 | DEBUG_INFORMATION_FORMAT = dwarf; 375 | ENABLE_STRICT_OBJC_MSGSEND = YES; 376 | ENABLE_TESTABILITY = YES; 377 | GCC_C_LANGUAGE_STANDARD = gnu11; 378 | GCC_DYNAMIC_NO_PIC = NO; 379 | GCC_NO_COMMON_BLOCKS = YES; 380 | GCC_OPTIMIZATION_LEVEL = 0; 381 | GCC_PREPROCESSOR_DEFINITIONS = ( 382 | "DEBUG=1", 383 | "$(inherited)", 384 | ); 385 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 386 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 387 | GCC_WARN_UNDECLARED_SELECTOR = YES; 388 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 389 | GCC_WARN_UNUSED_FUNCTION = YES; 390 | GCC_WARN_UNUSED_VARIABLE = YES; 391 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 392 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 393 | MTL_FAST_MATH = YES; 394 | ONLY_ACTIVE_ARCH = YES; 395 | SDKROOT = iphoneos; 396 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 397 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 398 | }; 399 | name = Debug; 400 | }; 401 | ECBF87CA242AEE1D0004638B /* Release */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | ALWAYS_SEARCH_USER_PATHS = NO; 405 | CLANG_ANALYZER_NONNULL = YES; 406 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 407 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 408 | CLANG_CXX_LIBRARY = "libc++"; 409 | CLANG_ENABLE_MODULES = YES; 410 | CLANG_ENABLE_OBJC_ARC = YES; 411 | CLANG_ENABLE_OBJC_WEAK = YES; 412 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 413 | CLANG_WARN_BOOL_CONVERSION = YES; 414 | CLANG_WARN_COMMA = YES; 415 | CLANG_WARN_CONSTANT_CONVERSION = YES; 416 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 417 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 418 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 419 | CLANG_WARN_EMPTY_BODY = YES; 420 | CLANG_WARN_ENUM_CONVERSION = YES; 421 | CLANG_WARN_INFINITE_RECURSION = YES; 422 | CLANG_WARN_INT_CONVERSION = YES; 423 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 424 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 425 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 426 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 427 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 428 | CLANG_WARN_STRICT_PROTOTYPES = YES; 429 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 430 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 431 | CLANG_WARN_UNREACHABLE_CODE = YES; 432 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 433 | COPY_PHASE_STRIP = NO; 434 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 435 | ENABLE_NS_ASSERTIONS = NO; 436 | ENABLE_STRICT_OBJC_MSGSEND = YES; 437 | GCC_C_LANGUAGE_STANDARD = gnu11; 438 | GCC_NO_COMMON_BLOCKS = YES; 439 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 440 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 441 | GCC_WARN_UNDECLARED_SELECTOR = YES; 442 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 443 | GCC_WARN_UNUSED_FUNCTION = YES; 444 | GCC_WARN_UNUSED_VARIABLE = YES; 445 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 446 | MTL_ENABLE_DEBUG_INFO = NO; 447 | MTL_FAST_MATH = YES; 448 | SDKROOT = iphoneos; 449 | SWIFT_COMPILATION_MODE = wholemodule; 450 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 451 | VALIDATE_PRODUCT = YES; 452 | }; 453 | name = Release; 454 | }; 455 | ECBF87CC242AEE1D0004638B /* Debug */ = { 456 | isa = XCBuildConfiguration; 457 | buildSettings = { 458 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 459 | CODE_SIGN_STYLE = Automatic; 460 | DEVELOPMENT_ASSET_PATHS = "\"Weather/Preview Content\""; 461 | DEVELOPMENT_TEAM = H934H94MVK; 462 | ENABLE_PREVIEWS = YES; 463 | INFOPLIST_FILE = Weather/Info.plist; 464 | LD_RUNPATH_SEARCH_PATHS = ( 465 | "$(inherited)", 466 | "@executable_path/Frameworks", 467 | ); 468 | PRODUCT_BUNDLE_IDENTIFIER = com.irmusyafa.Weather; 469 | PRODUCT_NAME = "$(TARGET_NAME)"; 470 | SWIFT_VERSION = 5.0; 471 | TARGETED_DEVICE_FAMILY = "1,2"; 472 | }; 473 | name = Debug; 474 | }; 475 | ECBF87CD242AEE1D0004638B /* Release */ = { 476 | isa = XCBuildConfiguration; 477 | buildSettings = { 478 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 479 | CODE_SIGN_STYLE = Automatic; 480 | DEVELOPMENT_ASSET_PATHS = "\"Weather/Preview Content\""; 481 | DEVELOPMENT_TEAM = H934H94MVK; 482 | ENABLE_PREVIEWS = YES; 483 | INFOPLIST_FILE = Weather/Info.plist; 484 | LD_RUNPATH_SEARCH_PATHS = ( 485 | "$(inherited)", 486 | "@executable_path/Frameworks", 487 | ); 488 | PRODUCT_BUNDLE_IDENTIFIER = com.irmusyafa.Weather; 489 | PRODUCT_NAME = "$(TARGET_NAME)"; 490 | SWIFT_VERSION = 5.0; 491 | TARGETED_DEVICE_FAMILY = "1,2"; 492 | }; 493 | name = Release; 494 | }; 495 | /* End XCBuildConfiguration section */ 496 | 497 | /* Begin XCConfigurationList section */ 498 | ECBF87B2242AEE1B0004638B /* Build configuration list for PBXProject "Weather" */ = { 499 | isa = XCConfigurationList; 500 | buildConfigurations = ( 501 | ECBF87C9242AEE1D0004638B /* Debug */, 502 | ECBF87CA242AEE1D0004638B /* Release */, 503 | ); 504 | defaultConfigurationIsVisible = 0; 505 | defaultConfigurationName = Release; 506 | }; 507 | ECBF87CB242AEE1D0004638B /* Build configuration list for PBXNativeTarget "Weather" */ = { 508 | isa = XCConfigurationList; 509 | buildConfigurations = ( 510 | ECBF87CC242AEE1D0004638B /* Debug */, 511 | ECBF87CD242AEE1D0004638B /* Release */, 512 | ); 513 | defaultConfigurationIsVisible = 0; 514 | defaultConfigurationName = Release; 515 | }; 516 | /* End XCConfigurationList section */ 517 | }; 518 | rootObject = ECBF87AF242AEE1B0004638B /* Project object */; 519 | } 520 | -------------------------------------------------------------------------------- /Weather.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Weather.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Weather.xcodeproj/xcuserdata/irfanrafi.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /Weather.xcodeproj/xcuserdata/irfanrafi.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Weather.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /Weather/App/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 25/03/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /Weather/App/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Weather/App/HostingController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HostingController.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 01/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | class HostingController: UIHostingController { 12 | override var preferredStatusBarStyle: UIStatusBarStyle { 13 | return .lightContent 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Weather/App/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 25/03/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = ContentView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = HostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/01d.imageset/01d@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/01d.imageset/01d@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/01d.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "01d@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/01n.imageset/01n@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/01n.imageset/01n@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/01n.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "01n@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/02d.imageset/02d@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/02d.imageset/02d@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/02d.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "02d@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/02n.imageset/02n@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/02n.imageset/02n@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/02n.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "02n@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/03d.imageset/03d@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/03d.imageset/03d@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/03d.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "03d@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/03n.imageset/03n@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/03n.imageset/03n@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/03n.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "03n@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/04d.imageset/04d@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/04d.imageset/04d@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/04d.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "04d@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/04n.imageset/04n@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/04n.imageset/04n@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/04n.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "04n@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/09d.imageset/09d@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/09d.imageset/09d@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/09d.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "09d@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/09n.imageset/09n@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/09n.imageset/09n@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/09n.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "09n@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/10d.imageset/10d@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/10d.imageset/10d@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/10d.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "10d@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/10n.imageset/10n@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/10n.imageset/10n@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/10n.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "10n@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/11d.imageset/11d@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/11d.imageset/11d@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/11d.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "11d@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/11n.imageset/11n@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/11n.imageset/11n@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/11n.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "11n@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/13d.imageset/13d@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/13d.imageset/13d@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/13d.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "13d@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/13n.imageset/13n@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/13n.imageset/13n@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/13n.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "13n@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/50d.imageset/50d@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/50d.imageset/50d@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/50d.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "50d@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/50n.imageset/50n@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/50n.imageset/50n@2x.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/50n.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "50n@2x.png" 6 | } 7 | ], 8 | "info" : { 9 | "version" : 1, 10 | "author" : "xcode" 11 | } 12 | } -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/100.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/1024.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/114.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/120.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/144.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/152.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/167.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/172.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/172.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/180.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/196.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/20.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/216.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/216.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/29.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/40.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/48.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/50.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/55.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/55.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/57.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/58.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/60.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/72.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/76.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/80.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/80.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/87.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/87.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/88.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/Assets.xcassets/AppIcon.appiconset/88.png -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | {"images":[{"size":"60x60","expected-size":"180","filename":"180.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"40x40","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"60x60","expected-size":"120","filename":"120.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"57x57","expected-size":"57","filename":"57.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"1x"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"57x57","expected-size":"114","filename":"114.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"2x"},{"size":"20x20","expected-size":"60","filename":"60.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"iphone","scale":"3x"},{"size":"1024x1024","filename":"1024.png","expected-size":"1024","idiom":"ios-marketing","folder":"Assets.xcassets/AppIcon.appiconset/","scale":"1x"},{"size":"40x40","expected-size":"80","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"72x72","expected-size":"72","filename":"72.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"76x76","expected-size":"152","filename":"152.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"50x50","expected-size":"100","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"76x76","expected-size":"76","filename":"76.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"29x29","expected-size":"29","filename":"29.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"50x50","expected-size":"50","filename":"50.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"72x72","expected-size":"144","filename":"144.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"40x40","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"83.5x83.5","expected-size":"167","filename":"167.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"size":"20x20","expected-size":"20","filename":"20.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"1x"},{"size":"20x20","expected-size":"40","filename":"40.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"ipad","scale":"2x"},{"idiom":"watch","filename":"172.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"86x86","expected-size":"172","role":"quickLook"},{"idiom":"watch","filename":"80.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"40x40","expected-size":"80","role":"appLauncher"},{"idiom":"watch","filename":"88.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"40mm","scale":"2x","size":"44x44","expected-size":"88","role":"appLauncher"},{"idiom":"watch","filename":"100.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"50x50","expected-size":"100","role":"appLauncher"},{"idiom":"watch","filename":"196.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"98x98","expected-size":"196","role":"quickLook"},{"idiom":"watch","filename":"216.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"44mm","scale":"2x","size":"108x108","expected-size":"216","role":"quickLook"},{"idiom":"watch","filename":"48.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"38mm","scale":"2x","size":"24x24","expected-size":"48","role":"notificationCenter"},{"idiom":"watch","filename":"55.png","folder":"Assets.xcassets/AppIcon.appiconset/","subtype":"42mm","scale":"2x","size":"27.5x27.5","expected-size":"55","role":"notificationCenter"},{"size":"29x29","expected-size":"87","filename":"87.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"3x"},{"size":"29x29","expected-size":"58","filename":"58.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch","role":"companionSettings","scale":"2x"},{"size":"1024x1024","expected-size":"1024","filename":"1024.png","folder":"Assets.xcassets/AppIcon.appiconset/","idiom":"watch-marketing","scale":"1x"}]} -------------------------------------------------------------------------------- /Weather/Assets/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Weather/Assets/WeatherLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Assets/WeatherLogo.png -------------------------------------------------------------------------------- /Weather/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /Weather/Model/Coordinate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Coordinate.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Coordinate: Codable { 12 | let lon, lat: Double 13 | 14 | static func emptyInit() -> Coordinate { 15 | return Coordinate(lon: 0, lat: 0) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Weather/Model/CurrentWeather.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentWeather.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct CurrentWeather: Codable { 12 | let timezone, id: Int 13 | let name: String 14 | let coordinate: Coordinate 15 | let elements: [WeatherElement] 16 | let base: String 17 | let mainValue: CurrentWeatherMainValue 18 | let visibility: Int 19 | let wind: WeatherWind 20 | let clouds: WeatherClouds 21 | let date: Int 22 | let sys: CurrentWeatherSys 23 | let code: Int 24 | 25 | enum CodingKeys: String, CodingKey { 26 | case base, visibility, wind, clouds, sys, timezone, id, name 27 | case elements = "weather" 28 | case coordinate = "coord" 29 | case mainValue = "main" 30 | case date = "dt" 31 | case code = "cod" 32 | } 33 | 34 | static func emptyInit() -> CurrentWeather { 35 | return CurrentWeather( 36 | timezone: 0, 37 | id: 0, 38 | name: "", 39 | coordinate: Coordinate.emptyInit(), 40 | elements: [], 41 | base: "", 42 | mainValue: CurrentWeatherMainValue.emptyInit(), 43 | visibility: 0, 44 | wind: WeatherWind.emptyInit(), 45 | clouds: WeatherClouds.emptyInit(), 46 | date: 0, 47 | sys: CurrentWeatherSys.emptyInit(), 48 | code: 0 49 | ) 50 | } 51 | 52 | func description() -> String { 53 | var result = "Today: " 54 | if let weatherElement = elements.first { 55 | result += "\(weatherElement.weatherDescription.capitalizingFirstLetter()) currently. " 56 | } 57 | result += "It's \(mainValue.temp)°." 58 | return result 59 | } 60 | 61 | func getForecastWeather() -> ForecastWeather { 62 | var result = ForecastWeather.emptyInit() 63 | 64 | result.date = self.date 65 | result.mainValue.tempMin = self.mainValue.tempMin 66 | result.mainValue.tempMax = self.mainValue.tempMax 67 | 68 | if let weatherElement = elements.first { 69 | result.elements.append(weatherElement) 70 | } 71 | 72 | return result 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /Weather/Model/CurrentWeatherMainValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentWeatherMainValue.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct CurrentWeatherMainValue: Codable { 12 | let temp, feelsLike, tempMin, tempMax: Double 13 | let pressure, humidity: Int 14 | 15 | enum CodingKeys: String, CodingKey { 16 | case temp 17 | case feelsLike = "feels_like" 18 | case tempMin = "temp_min" 19 | case tempMax = "temp_max" 20 | case pressure, humidity 21 | } 22 | 23 | static func emptyInit() -> CurrentWeatherMainValue { 24 | return CurrentWeatherMainValue( 25 | temp: 0.0, 26 | feelsLike: 0.0, 27 | tempMin: 0, 28 | tempMax: 0, 29 | pressure: 0, 30 | humidity: 0 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Weather/Model/CurrentWeatherSys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CurrentWeatherSys.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct CurrentWeatherSys: Codable { 12 | let type, id: Int 13 | let country: String 14 | let sunrise, sunset: Int 15 | 16 | static func emptyInit() -> CurrentWeatherSys { 17 | return CurrentWeatherSys( 18 | type: 0, 19 | id: 0, 20 | country: "", 21 | sunrise: 0, 22 | sunset: 0 23 | ) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Weather/Model/ForecastWeather.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastWeather.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 04/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ForecastWeather: Codable { 12 | var date: Int 13 | var mainValue: ForecastWeatherMainValue 14 | var elements: [WeatherElement] 15 | let clouds: WeatherClouds 16 | let wind: WeatherWind 17 | 18 | enum CodingKeys: String, CodingKey { 19 | case clouds, wind 20 | case mainValue = "main" 21 | case date = "dt" 22 | case elements = "weather" 23 | } 24 | 25 | static func emptyInit() -> ForecastWeather { 26 | return ForecastWeather( 27 | date: 0, 28 | mainValue: ForecastWeatherMainValue.emptyInit(), 29 | elements: [], 30 | clouds: WeatherClouds.emptyInit(), 31 | wind: WeatherWind.emptyInit() 32 | ) 33 | } 34 | } 35 | 36 | extension ForecastWeather: Identifiable { 37 | var id: String { "\(date)" } 38 | } 39 | -------------------------------------------------------------------------------- /Weather/Model/ForecastWeatherCity.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastWeatherCity.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ForecastWeatherCity: Codable { 12 | let id: Int 13 | let name: String 14 | let coordinate: Coordinate 15 | let country: String 16 | let timezone, sunrise, sunset: Int 17 | 18 | enum CodingKeys: String, CodingKey { 19 | case id, name 20 | case coordinate = "coord" 21 | case country, timezone, sunrise, sunset 22 | } 23 | 24 | static func emptyInit() -> ForecastWeatherCity { 25 | return ForecastWeatherCity( 26 | id: 0, 27 | name: "", 28 | coordinate: Coordinate.emptyInit(), 29 | country: "", 30 | timezone: 0, 31 | sunrise: 0, 32 | sunset: 0 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Weather/Model/ForecastWeatherMainValue.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastWeatherMainValue.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 04/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ForecastWeatherMainValue: Codable { 12 | let temp, feelsLike: Double 13 | var tempMin, tempMax: Double 14 | let pressure, seaLevel, grndLevel, humidity: Int 15 | let tempKf: Double 16 | 17 | enum CodingKeys: String, CodingKey { 18 | case temp 19 | case feelsLike = "feels_like" 20 | case tempMin = "temp_min" 21 | case tempMax = "temp_max" 22 | case pressure 23 | case seaLevel = "sea_level" 24 | case grndLevel = "grnd_level" 25 | case humidity 26 | case tempKf = "temp_kf" 27 | } 28 | 29 | static func emptyInit() -> ForecastWeatherMainValue { 30 | return ForecastWeatherMainValue( 31 | temp: 0.0, 32 | feelsLike: 0.0, 33 | tempMin: 0.0, 34 | tempMax: 0.9, 35 | pressure: 0, 36 | seaLevel: 0, 37 | grndLevel: 0, 38 | humidity: 0, 39 | tempKf: 0 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Weather/Model/ForecastWeatherResponse.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastWeatherResponse.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ForecastWeatherResponse: Codable { 12 | let code: String 13 | let message, count: Int 14 | let list: [ForecastWeather] 15 | let city: ForecastWeatherCity 16 | 17 | enum CodingKeys: String, CodingKey { 18 | case code = "cod" 19 | case message 20 | case count = "cnt" 21 | case list, city 22 | } 23 | 24 | static func emptyInit() -> ForecastWeatherResponse { 25 | return ForecastWeatherResponse( 26 | code: "", 27 | message: 0, 28 | count: 0, 29 | list: [], 30 | city: ForecastWeatherCity.emptyInit() 31 | ) 32 | } 33 | 34 | var dailyList: [ForecastWeather] { 35 | var result: [ForecastWeather] = [] 36 | guard var before = list.first else { 37 | return result 38 | } 39 | 40 | if before.date.dateFromMilliseconds().dayWord() != Date().dayWord() { 41 | result.append(before) 42 | } 43 | 44 | for weather in list { 45 | if weather.date.dateFromMilliseconds().dayWord() != before.date.dateFromMilliseconds().dayWord() { 46 | result.append(weather) 47 | } 48 | before = weather 49 | } 50 | 51 | return result 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Weather/Model/StateView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StateView.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum StateView { 12 | case loading 13 | case success 14 | case failed 15 | } 16 | -------------------------------------------------------------------------------- /Weather/Model/WeatherClouds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherClouds.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 04/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct WeatherClouds: Codable { 12 | let all: Int 13 | 14 | static func emptyInit() -> WeatherClouds { 15 | return WeatherClouds(all: 0) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Weather/Model/WeatherElement.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherElement.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 04/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct WeatherElement: Codable { 12 | let id: Int 13 | let main, weatherDescription, icon: String 14 | 15 | enum CodingKeys: String, CodingKey { 16 | case id, main 17 | case weatherDescription = "description" 18 | case icon 19 | } 20 | 21 | static func emptyInit() -> WeatherElement { 22 | return WeatherElement( 23 | id: 0, 24 | main: "", 25 | weatherDescription: "", 26 | icon: "" 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Weather/Model/WeatherWind.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherWind.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 04/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct WeatherWind: Codable { 12 | let speed: Double 13 | let deg: Int? 14 | 15 | static func emptyInit() -> WeatherWind { 16 | return WeatherWind(speed: 0.0, deg: nil) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Weather/Network/OpenweatherAPIClient.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenweatherAPIClient.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class OpenweatherAPIClient { 12 | typealias CurrentWeatherCompletionHandler = (CurrentWeather?, Error?) -> Void 13 | typealias ForecastWeatherCompletionHandler = (ForecastWeatherResponse?, Error?) -> Void 14 | 15 | private let apiKey = "YOURAPIKEY" 16 | private let decoder = JSONDecoder() 17 | private let session: URLSession 18 | 19 | private enum SuffixURL: String { 20 | case forecastWeather = "forecast" 21 | case currentWeather = "weather" 22 | } 23 | 24 | private func baseUrl(_ suffixURL: SuffixURL, param: String) -> URL { 25 | return URL(string: "https://api.openweathermap.org/data/2.5/\(suffixURL.rawValue)?APPID=\(self.apiKey)&units=metric\(param)")! 26 | } 27 | 28 | init(configuration: URLSessionConfiguration) { 29 | self.session = URLSession(configuration: configuration) 30 | } 31 | 32 | convenience init() { 33 | self.init(configuration: .default) 34 | } 35 | 36 | private func getBaseRequest(at cityId: String, 37 | suffixURL: SuffixURL, 38 | completionHandler completion: @escaping (_ object: T?,_ error: Error?) -> ()) { 39 | 40 | let url = baseUrl(suffixURL, param: "&id=\(cityId)") 41 | let request = URLRequest(url: url) 42 | 43 | let task = session.dataTask(with: request) { data, response, error in 44 | DispatchQueue.main.async { 45 | if let data = data { 46 | guard let httpResponse = response as? HTTPURLResponse else { 47 | completion(nil, ResponseError.requestFailed) 48 | return 49 | } 50 | 51 | if httpResponse.statusCode == 200 { 52 | do { 53 | let weather = try self.decoder.decode(T.self, from: data) 54 | completion(weather, nil) 55 | } catch let error { 56 | completion(nil, error) 57 | } 58 | } else { 59 | completion(nil, ResponseError.invalidData) 60 | } 61 | } else if let error = error { 62 | completion(nil, error) 63 | } 64 | } 65 | } 66 | 67 | task.resume() 68 | } 69 | 70 | func getCurrentWeather(at cityId: String, completionHandler completion: @escaping CurrentWeatherCompletionHandler) { 71 | getBaseRequest(at: cityId, suffixURL: .currentWeather) { (weather: CurrentWeather?, error) in 72 | completion(weather, error) 73 | } 74 | } 75 | 76 | func getForecastWeather(at cityId: String, completionHandler completion: @escaping ForecastWeatherCompletionHandler) { 77 | getBaseRequest(at: cityId, suffixURL: .forecastWeather) { (weather: ForecastWeatherResponse?, error) in 78 | completion(weather, error) 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Weather/Network/ResponseError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ResponseError.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ResponseError: Error { 12 | case requestFailed 13 | case responseUnsuccessful(statusCode: Int) 14 | case invalidData 15 | case jsonParsingFailure 16 | case invalidURL 17 | } 18 | -------------------------------------------------------------------------------- /Weather/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Weather/Preview Content/Preview Assets.xcassets/WeatherIcon.imageset/20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Preview Content/Preview Assets.xcassets/WeatherIcon.imageset/20.png -------------------------------------------------------------------------------- /Weather/Preview Content/Preview Assets.xcassets/WeatherIcon.imageset/40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Preview Content/Preview Assets.xcassets/WeatherIcon.imageset/40.png -------------------------------------------------------------------------------- /Weather/Preview Content/Preview Assets.xcassets/WeatherIcon.imageset/60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/irmusyafa/ios-weatherapp-swiftui/3ca5b20dcaf362c687498eb86090de69cc4d2d0c/Weather/Preview Content/Preview Assets.xcassets/WeatherIcon.imageset/60.png -------------------------------------------------------------------------------- /Weather/Preview Content/Preview Assets.xcassets/WeatherIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "20.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "40.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "60.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /Weather/Screen/MainScreenView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 25/03/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | @ObservedObject var weatherViewModel = WeatherViewModel() 13 | 14 | var body: some View { 15 | ZStack { 16 | BackgroundView() 17 | 18 | VStack { 19 | if weatherViewModel.stateView == .loading { 20 | ActivityIndicatorView(isAnimating: true).configure { 21 | $0.color = .white 22 | } 23 | } 24 | 25 | if weatherViewModel.stateView == .success { 26 | LocationAndTemperatureHeaderView(data: weatherViewModel.currentWeather) 27 | Spacer() 28 | 29 | ScrollView(.vertical, showsIndicators: false) { 30 | VStack { 31 | DailyWeatherCellView(data: weatherViewModel.todayWeather) 32 | Rectangle().frame(height: CGFloat(1)) 33 | 34 | HourlyWeatherView(data: weatherViewModel.hourlyWeathers) 35 | Rectangle().frame(height: CGFloat(1)) 36 | 37 | DailyWeatherView(data: weatherViewModel.dailyWeathers) 38 | Rectangle().frame(height: CGFloat(1)) 39 | 40 | Text(weatherViewModel.currentDescription) 41 | .frame(maxWidth: .infinity, alignment: .leading) 42 | .padding( 43 | .init(arrayLiteral:.leading,.trailing), 44 | 24 45 | ) 46 | Rectangle().frame(height: CGFloat(1)) 47 | 48 | DetailsCurrentWeatherView(data: weatherViewModel.currentWeather) 49 | Rectangle().frame(height: CGFloat(1)) 50 | 51 | } 52 | } 53 | Spacer() 54 | } 55 | 56 | if weatherViewModel.stateView == .failed { 57 | Button(action: { 58 | self.weatherViewModel.retry() 59 | }) { 60 | Text("Failed get data, retry?") 61 | .foregroundColor(.white) 62 | } 63 | } 64 | } 65 | }.colorScheme(.dark) 66 | } 67 | } 68 | 69 | struct ContentView_Previews: PreviewProvider { 70 | static var previews: some View { 71 | ContentView() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Weather/Utilities/extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // extension.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Int { 12 | func dateFromMilliseconds() -> Date { 13 | return Date(timeIntervalSince1970: TimeInterval(self)) 14 | } 15 | } 16 | 17 | extension Date { 18 | func dayWord() -> String { 19 | let dateFormatter = DateFormatter() 20 | dateFormatter.dateFormat = "EEEE" 21 | return dateFormatter.string(from: self) 22 | } 23 | 24 | func dayMonthly() -> String { 25 | let dateFormatter = DateFormatter() 26 | dateFormatter.dateFormat = "dd" 27 | return dateFormatter.string(from: self) 28 | } 29 | 30 | func hour() -> String { 31 | let dateFormatter = DateFormatter() 32 | dateFormatter.dateFormat = "HH" 33 | return dateFormatter.string(from: self) 34 | } 35 | 36 | func hourMinute() -> String { 37 | let dateFormatter = DateFormatter() 38 | dateFormatter.dateFormat = "HH.mm" 39 | return dateFormatter.string(from: self) 40 | } 41 | 42 | } 43 | 44 | extension String { 45 | func capitalizingFirstLetter() -> String { 46 | return prefix(1).capitalized + dropFirst() 47 | } 48 | 49 | mutating func capitalizeFirstLetter() { 50 | self = self.capitalizingFirstLetter() 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Weather/View/ActivityIndicatorView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ActivityIndicatorView.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ActivityIndicatorView: UIViewRepresentable { 12 | 13 | typealias UIView = UIActivityIndicatorView 14 | var isAnimating: Bool 15 | var configuration = { (indicator: UIView) in } 16 | 17 | func makeUIView(context: UIViewRepresentableContext) -> UIView { UIView() } 18 | func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext) { 19 | isAnimating ? uiView.startAnimating() : uiView.stopAnimating() 20 | configuration(uiView) 21 | } 22 | } 23 | 24 | extension View where Self == ActivityIndicatorView { 25 | func configure(_ configuration: @escaping (Self.UIView)->Void) -> Self { 26 | Self.init(isAnimating: self.isAnimating, configuration: configuration) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Weather/View/BackgroundView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BackgroundView.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 01/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct BackgroundView: View { 12 | var body: some View { 13 | let clodyScheme = [Color(red: 41/255, green: 128/255, blue: 184/255), 14 | Color(red: 109/255, green: 212/255, blue: 250/255), 15 | Color(red: 109/255, green: 212/255, blue: 250/255), 16 | Color(red: 109/255, green: 212/255, blue: 250/255), 17 | Color.white] 18 | 19 | // let defaultScheme = [Color.black, 20 | // Color(red: 20/255, green: 31/255, blue: 78/255), 21 | // Color(red: 141/255, green: 87/255, blue: 151/255)] 22 | 23 | let colorScheme : [Color] = clodyScheme 24 | 25 | let gradient = Gradient(colors: colorScheme) 26 | let linearGradient = LinearGradient(gradient: gradient, startPoint: .top, endPoint: .bottom) 27 | 28 | let background = Rectangle() 29 | .fill(linearGradient) 30 | .blur(radius: 200, opaque: true) 31 | .edgesIgnoringSafeArea(.all) 32 | 33 | return background 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Weather/View/DailyWeatherCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DailyWeatherCellView.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 01/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DailyWeatherCellView: View { 12 | let data: ForecastWeather 13 | 14 | var day: String { 15 | return data.date.dateFromMilliseconds().dayWord() 16 | } 17 | var temperatureMax: String { 18 | return "\(Int(data.mainValue.tempMax))°" 19 | } 20 | 21 | var temperatureMin: String { 22 | return "\(Int(data.mainValue.tempMin))°" 23 | } 24 | 25 | var icon: String { 26 | var image = "WeatherIcon" 27 | if let weather = data.elements.first { 28 | image = weather.icon 29 | } 30 | return image 31 | } 32 | 33 | var body: some View { 34 | HStack { 35 | Text(day) 36 | .frame(width: 150, alignment: .leading) 37 | 38 | Image(icon) 39 | .resizable() 40 | .aspectRatio(UIImage(named: icon)!.size, contentMode: .fit) 41 | .frame(width: 30, height: 30) 42 | 43 | Spacer() 44 | Text(temperatureMax) 45 | Spacer().frame(width: 34) 46 | Text(temperatureMin) 47 | }.padding(.horizontal, 24) 48 | } 49 | } 50 | 51 | struct DailyWeatherCellView_Previews: PreviewProvider { 52 | static var previews: some View { 53 | DailyWeatherCellView(data: ForecastWeather.emptyInit()) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Weather/View/DailyWeatherView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DailyWeatherView.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 01/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DailyWeatherView: View { 12 | let data: [ForecastWeather] 13 | 14 | var body: some View { 15 | VStack { 16 | ForEach(data, id: \.date) { data in 17 | DailyWeatherCellView(data: data) 18 | } 19 | } 20 | } 21 | } 22 | 23 | struct DailyWeatherView_Previews: PreviewProvider { 24 | static var previews: some View { 25 | DailyWeatherView(data: [ForecastWeather.emptyInit()]) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Weather/View/DetailsCurrentWeatherCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsCurrentWeatherCellView.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DetailsCurrentWeatherCellView: View { 12 | let firstData: (String, String) 13 | let secondData: (String, String) 14 | 15 | var body: some View { 16 | VStack(spacing: 0) { 17 | HStack(spacing: 0) { 18 | Text(firstData.0) 19 | .font(.caption) 20 | .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) 21 | Text(secondData.0) 22 | .font(.caption) 23 | .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) 24 | } 25 | HStack(spacing: 0) { 26 | Text(firstData.1).font(.title).padding(0) 27 | .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) 28 | Text(secondData.1).font(.title).padding(0) 29 | .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) 30 | } 31 | } 32 | } 33 | } 34 | 35 | struct DetailsCurrentWeatherCellView_Previews: PreviewProvider { 36 | static var previews: some View { 37 | DetailsCurrentWeatherView(data: CurrentWeather.emptyInit()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Weather/View/DetailsCurrentWeatherView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsCurrentWeatherView.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct DetailsCurrentWeatherView: View { 12 | let data: CurrentWeather 13 | 14 | var sunrise: String { 15 | return data.sys.sunrise.dateFromMilliseconds().hourMinute() 16 | } 17 | 18 | var sunset: String { 19 | return data.sys.sunset.dateFromMilliseconds().hourMinute() 20 | } 21 | 22 | var temperatureMax: String { 23 | return "\(Int(data.mainValue.tempMax))°" 24 | } 25 | 26 | var temperatureMin: String { 27 | return "\(Int(data.mainValue.tempMin))°" 28 | } 29 | 30 | var visibility: String { 31 | return "\(Float(data.visibility/1000)) Km" 32 | } 33 | 34 | var feelsLike: String { 35 | return "\(data.mainValue.feelsLike)°" 36 | } 37 | 38 | var pressure: String { 39 | return "\(data.mainValue.pressure) hPa" 40 | } 41 | 42 | var humidity: String { 43 | return "\(data.mainValue.humidity)%" 44 | } 45 | 46 | var body: some View { 47 | VStack(spacing: 0) { 48 | DetailsCurrentWeatherCellView( 49 | firstData: ("SUNRISE", sunrise), 50 | secondData: ("SUNSET", sunset) 51 | ) 52 | Rectangle().frame(height: CGFloat(1)).padding(.vertical, 8) 53 | 54 | DetailsCurrentWeatherCellView( 55 | firstData: ("PRESSURE", pressure), 56 | secondData: ("HUMIDITY", humidity) 57 | ) 58 | Rectangle().frame(height: CGFloat(1)).padding(.vertical, 8) 59 | 60 | DetailsCurrentWeatherCellView( 61 | firstData: ("VISIBILITY", visibility), 62 | secondData: ("FEELS LIKE", feelsLike) 63 | ) 64 | Rectangle().frame(height: CGFloat(1)).padding(.vertical, 8) 65 | 66 | DetailsCurrentWeatherCellView( 67 | firstData: ("HIGH TEMP", temperatureMax), 68 | secondData: ("LOW TEMP", temperatureMin) 69 | ) 70 | Spacer() 71 | }.padding(.horizontal, 24) 72 | } 73 | } 74 | 75 | struct DetailsCurrentWeatherView_Previews: PreviewProvider { 76 | static var previews: some View { 77 | DetailsCurrentWeatherView(data: CurrentWeather.emptyInit()) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Weather/View/HourlyWeatherCellView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HourlyWeatherCellView.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 01/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct HourlyWeatherCellView: View { 12 | var data: ForecastWeather 13 | 14 | var hour: String { 15 | return data.date.dateFromMilliseconds().hour() 16 | } 17 | 18 | var temperature: String { 19 | return "\(Int(data.mainValue.temp))°" 20 | } 21 | 22 | var icon: String { 23 | var image = "WeatherIcon" 24 | if let weather = data.elements.first { 25 | image = weather.icon 26 | } 27 | return image 28 | } 29 | 30 | var body: some View { 31 | VStack { 32 | Text(hour) 33 | Text("\(data.mainValue.humidity)%") 34 | .font(.system(size: 12)) 35 | .foregroundColor( 36 | .init(red: 127/255, 37 | green: 1, 38 | blue: 212/255) 39 | ) 40 | Image(icon) 41 | .resizable() 42 | .aspectRatio(UIImage(named: icon)!.size, contentMode: .fit) 43 | .frame(width: 30, height: 30) 44 | Text(temperature) 45 | }.padding(.all, 0) 46 | } 47 | } 48 | 49 | struct HourlyWeatherCellView_Previews: PreviewProvider { 50 | static var previews: some View { 51 | HourlyWeatherCellView(data: ForecastWeather.emptyInit()) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Weather/View/HourlyWeatherView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HourlyWeatherView.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 01/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct HourlyWeatherView: View { 12 | let data: [ForecastWeather] 13 | 14 | var body: some View { 15 | ScrollView(.horizontal, showsIndicators: false) { 16 | HStack { 17 | ForEach(data) { data in 18 | HourlyWeatherCellView(data: data) 19 | Spacer().frame(width: 24) 20 | } 21 | }.padding(.horizontal, 24) 22 | } 23 | } 24 | } 25 | 26 | struct HourlyWeatherView_Previews: PreviewProvider { 27 | static var previews: some View { 28 | HourlyWeatherView(data: [ForecastWeather.emptyInit()]) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Weather/View/LocationAndTemperatureHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LocationAndTemperatureHeaderView.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 01/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct LocationAndTemperatureHeaderView: View { 12 | let data: CurrentWeather 13 | 14 | var weatherName: String { 15 | var result = "" 16 | if let weather = data.elements.first { 17 | result = weather.main 18 | } 19 | return result 20 | } 21 | 22 | var temperature: String { 23 | return "\(Int(data.mainValue.temp))°" 24 | } 25 | 26 | var body: some View { 27 | VStack { 28 | Text(data.name) 29 | .font(.largeTitle) 30 | .fontWeight(.medium) 31 | Text(weatherName) 32 | .font(.body) 33 | .fontWeight(.light) 34 | .padding(.bottom, 4) 35 | Text(temperature) 36 | .font(.system(size: 86)) 37 | .fontWeight(.thin) 38 | } 39 | .padding(.vertical, 24) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Weather/ViewModel/WeatherViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherViewModel.swift 3 | // Weather 4 | // 5 | // Created by Irfan Rafii Musyafa on 05/04/20. 6 | // Copyright © 2020 Irmusyafa. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | import Combine 11 | 12 | class WeatherViewModel: ObservableObject { 13 | let client = OpenweatherAPIClient() 14 | 15 | var stateView: StateView = StateView.loading { 16 | willSet { 17 | objectWillChange.send() 18 | } 19 | } 20 | 21 | var currentWeather = CurrentWeather.emptyInit() { 22 | willSet { 23 | objectWillChange.send() 24 | } 25 | } 26 | 27 | var todayWeather = ForecastWeather.emptyInit() { 28 | willSet { 29 | objectWillChange.send() 30 | } 31 | } 32 | 33 | var hourlyWeathers: [ForecastWeather] = [] { 34 | willSet { 35 | objectWillChange.send() 36 | } 37 | } 38 | 39 | var dailyWeathers: [ForecastWeather] = [] { 40 | willSet { 41 | objectWillChange.send() 42 | } 43 | } 44 | 45 | var currentDescription = "" { 46 | willSet { 47 | objectWillChange.send() 48 | } 49 | } 50 | 51 | private var stateCurrentWeather = StateView.loading 52 | private var stateForecastWeather = StateView.loading 53 | private let cityId = "1627459" // Serpong City Id 54 | 55 | init() { 56 | getData() 57 | } 58 | 59 | func retry() { 60 | stateView = .loading 61 | stateCurrentWeather = .loading 62 | stateForecastWeather = .loading 63 | 64 | getData() 65 | } 66 | 67 | private func getData() { 68 | client.getCurrentWeather(at: cityId) { [weak self] currentWeather, error in 69 | guard let ws = self else { return } 70 | if let currentWeather = currentWeather { 71 | ws.currentWeather = currentWeather 72 | ws.todayWeather = currentWeather.getForecastWeather() 73 | ws.currentDescription = currentWeather.description() 74 | ws.stateCurrentWeather = .success 75 | } else { 76 | ws.stateCurrentWeather = .failed 77 | } 78 | ws.updateStateView() 79 | } 80 | 81 | client.getForecastWeather(at: cityId) { [weak self] forecastWeatherResponse, error in 82 | guard let ws = self else { return } 83 | if let forecastWeatherResponse = forecastWeatherResponse { 84 | ws.hourlyWeathers = forecastWeatherResponse.list 85 | ws.dailyWeathers = forecastWeatherResponse.dailyList 86 | ws.stateForecastWeather = .success 87 | } else { 88 | ws.stateForecastWeather = .failed 89 | } 90 | ws.updateStateView() 91 | } 92 | } 93 | 94 | private func updateStateView() { 95 | if stateCurrentWeather == .success, stateForecastWeather == .success { 96 | stateView = .success 97 | } 98 | 99 | if stateCurrentWeather == .failed, stateForecastWeather == .failed { 100 | stateView = .failed 101 | } 102 | } 103 | } 104 | --------------------------------------------------------------------------------