├── 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 |
--------------------------------------------------------------------------------