├── Temperatura.jpg
├── Temperatura
├── Assets.xcassets
│ ├── Contents.json
│ ├── 10d.imageset
│ │ ├── 10d@2x.png
│ │ └── Contents.json
│ ├── mist.imageset
│ │ ├── mist.png
│ │ ├── mist@2x.png
│ │ ├── mist@3x.png
│ │ └── Contents.json
│ ├── snow.imageset
│ │ ├── snow.png
│ │ ├── snow@2x.png
│ │ ├── snow@3x.png
│ │ └── Contents.json
│ ├── night.imageset
│ │ ├── night.png
│ │ ├── night@2x.png
│ │ ├── night@3x.png
│ │ └── Contents.json
│ ├── sunny.imageset
│ │ ├── sunny.png
│ │ ├── sunny@2x.png
│ │ ├── sunny@3x.png
│ │ └── Contents.json
│ ├── rain_day.imageset
│ │ ├── rain_day.png
│ │ ├── rain_day@2x.png
│ │ ├── rain_day@3x.png
│ │ └── Contents.json
│ ├── clear_sky_day.imageset
│ │ ├── sunny.png
│ │ ├── sunny@2x.png
│ │ ├── sunny@3x.png
│ │ └── Contents.json
│ ├── clear_sky_night.imageset
│ │ ├── moon.png
│ │ ├── moon@2x.png
│ │ ├── moon@3x.png
│ │ └── Contents.json
│ ├── rain_night.imageset
│ │ ├── rain_night.png
│ │ ├── rain_night@2x.png
│ │ ├── rain_night@3x.png
│ │ └── Contents.json
│ ├── shower_rain.imageset
│ │ ├── shower_rain.png
│ │ ├── shower_rain@2x.png
│ │ ├── shower_rain@3x.png
│ │ └── Contents.json
│ ├── few_clouds_day.imageset
│ │ ├── cloudy_day.png
│ │ ├── cloudy_day@2x.png
│ │ ├── cloudy_day@3x.png
│ │ └── Contents.json
│ ├── broken_clouds.imageset
│ │ ├── broken_clouds.png
│ │ ├── broken_clouds@2x.png
│ │ ├── broken_clouds@3x.png
│ │ └── Contents.json
│ ├── few_clouds_night.imageset
│ │ ├── cloudy_night.png
│ │ ├── cloudy_night@2x.png
│ │ ├── cloudy_night@3x.png
│ │ └── Contents.json
│ ├── scattered_clouds.imageset
│ │ ├── scattered_clouds.png
│ │ ├── scattered_clouds@2x.png
│ │ ├── scattered_clouds@3x.png
│ │ └── Contents.json
│ ├── thunderstorm_day.imageset
│ │ ├── thunderstorm_day.png
│ │ ├── thunderstorm_day@2x.png
│ │ ├── thunderstorm_day@3x.png
│ │ └── Contents.json
│ ├── thunderstorm_night.imageset
│ │ ├── thunderstorm_night.png
│ │ ├── thunderstorm_night@2x.png
│ │ ├── thunderstorm_night@3x.png
│ │ └── Contents.json
│ └── AppIcon.appiconset
│ │ └── Contents.json
├── Preview Content
│ └── Preview Assets.xcassets
│ │ └── Contents.json
├── Temperatura.entitlements
├── Helpers
│ ├── EnvironmentValues+ImageCache.swift
│ ├── Constants.swift
│ ├── ImageCache.swift
│ ├── ActivityIndicator.swift
│ ├── ImageUrl.swift
│ ├── AsyncImage.swift
│ └── ImageLoader.swift
├── Models
│ ├── Weather.swift
│ ├── WeatherModel.swift
│ └── ForecastModel.swift
├── HomeTab.swift
├── Partials
│ ├── Humidity.swift
│ ├── GridView.swift
│ ├── OtherDetails.swift
│ ├── ListView.swift
│ └── WeatherPartials.swift
├── AppDelegate.swift
├── Main.swift
├── Managers
│ └── LocationManager.swift
├── Services
│ ├── ForecastService.swift
│ └── WeatherService.swift
├── Info.plist
├── SceneDelegate.swift
├── Base.lproj
│ └── LaunchScreen.storyboard
├── Screens
│ ├── Forecast.swift
│ ├── ForecastDetails.swift
│ └── Home.swift
└── View Models
│ ├── ForecastViewModel.swift
│ ├── TemperaturaViewModel.swift
│ └── WeatherViewModel.swift
├── Temperatura.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ ├── WorkspaceSettings.xcsettings
│ │ └── IDEWorkspaceChecks.plist
│ └── xcuserdata
│ │ └── glennraya.xcuserdatad
│ │ └── WorkspaceSettings.xcsettings
├── xcuserdata
│ └── glennraya.xcuserdatad
│ │ ├── xcschemes
│ │ └── xcschememanagement.plist
│ │ └── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
└── project.pbxproj
├── README.md
├── TemperaturaTests
├── Info.plist
└── TemperaturaTests.swift
└── TemperaturaUITests
├── Info.plist
└── TemperaturaUITests.swift
/Temperatura.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura.jpg
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Temperatura/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/10d.imageset/10d@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/10d.imageset/10d@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/mist.imageset/mist.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/mist.imageset/mist.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/snow.imageset/snow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/snow.imageset/snow.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/mist.imageset/mist@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/mist.imageset/mist@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/mist.imageset/mist@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/mist.imageset/mist@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/night.imageset/night.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/night.imageset/night.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/snow.imageset/snow@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/snow.imageset/snow@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/snow.imageset/snow@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/snow.imageset/snow@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/sunny.imageset/sunny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/sunny.imageset/sunny.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/night.imageset/night@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/night.imageset/night@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/night.imageset/night@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/night.imageset/night@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/sunny.imageset/sunny@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/sunny.imageset/sunny@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/sunny.imageset/sunny@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/sunny.imageset/sunny@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/rain_day.imageset/rain_day.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/rain_day.imageset/rain_day.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/clear_sky_day.imageset/sunny.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/clear_sky_day.imageset/sunny.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/clear_sky_night.imageset/moon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/clear_sky_night.imageset/moon.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/rain_day.imageset/rain_day@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/rain_day.imageset/rain_day@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/rain_day.imageset/rain_day@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/rain_day.imageset/rain_day@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/clear_sky_day.imageset/sunny@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/clear_sky_day.imageset/sunny@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/clear_sky_day.imageset/sunny@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/clear_sky_day.imageset/sunny@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/clear_sky_night.imageset/moon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/clear_sky_night.imageset/moon@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/clear_sky_night.imageset/moon@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/clear_sky_night.imageset/moon@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/rain_night.imageset/rain_night.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/rain_night.imageset/rain_night.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/shower_rain.imageset/shower_rain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/shower_rain.imageset/shower_rain.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/few_clouds_day.imageset/cloudy_day.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/few_clouds_day.imageset/cloudy_day.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/rain_night.imageset/rain_night@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/rain_night.imageset/rain_night@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/rain_night.imageset/rain_night@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/rain_night.imageset/rain_night@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/broken_clouds.imageset/broken_clouds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/broken_clouds.imageset/broken_clouds.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/few_clouds_day.imageset/cloudy_day@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/few_clouds_day.imageset/cloudy_day@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/few_clouds_day.imageset/cloudy_day@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/few_clouds_day.imageset/cloudy_day@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/shower_rain.imageset/shower_rain@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/shower_rain.imageset/shower_rain@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/shower_rain.imageset/shower_rain@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/shower_rain.imageset/shower_rain@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/broken_clouds.imageset/broken_clouds@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/broken_clouds.imageset/broken_clouds@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/broken_clouds.imageset/broken_clouds@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/broken_clouds.imageset/broken_clouds@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/few_clouds_night.imageset/cloudy_night.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/few_clouds_night.imageset/cloudy_night.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/few_clouds_night.imageset/cloudy_night@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/few_clouds_night.imageset/cloudy_night@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/few_clouds_night.imageset/cloudy_night@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/few_clouds_night.imageset/cloudy_night@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/scattered_clouds.imageset/scattered_clouds.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/scattered_clouds.imageset/scattered_clouds.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/thunderstorm_day.imageset/thunderstorm_day.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/thunderstorm_day.imageset/thunderstorm_day.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/scattered_clouds.imageset/scattered_clouds@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/scattered_clouds.imageset/scattered_clouds@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/scattered_clouds.imageset/scattered_clouds@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/scattered_clouds.imageset/scattered_clouds@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/thunderstorm_day.imageset/thunderstorm_day@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/thunderstorm_day.imageset/thunderstorm_day@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/thunderstorm_day.imageset/thunderstorm_day@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/thunderstorm_day.imageset/thunderstorm_day@3x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/thunderstorm_night.imageset/thunderstorm_night.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/thunderstorm_night.imageset/thunderstorm_night.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/thunderstorm_night.imageset/thunderstorm_night@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/thunderstorm_night.imageset/thunderstorm_night@2x.png
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/thunderstorm_night.imageset/thunderstorm_night@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glennraya/temperatura-swiftui/HEAD/Temperatura/Assets.xcassets/thunderstorm_night.imageset/thunderstorm_night@3x.png
--------------------------------------------------------------------------------
/Temperatura.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Temperatura/Temperatura.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/Temperatura.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Temperatura.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftUI Simple Weather App
2 | > A simple iOS app built on Apple's SwiftUI framework.
3 |
4 | This is a simple weather app for iOS built on SwiftUI, Apple's newest framework to build user interfaces for iOS.
5 |
6 | 
7 |
8 | This is just for fun, permission is granted for anyone to modify this, you can use this for educational purposes or even as a starter files for your project.
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/10d.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "scale" : "1x"
6 | },
7 | {
8 | "filename" : "10d@2x.png",
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Temperatura.xcodeproj/xcuserdata/glennraya.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | Temperatura.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 2
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/mist.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "mist.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "mist@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "mist@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/snow.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "snow.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "snow@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "snow@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/night.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "night.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "night@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "night@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/sunny.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "sunny.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "sunny@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "sunny@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/clear_sky_day.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "sunny.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "sunny@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "sunny@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/clear_sky_night.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "moon.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "moon@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "moon@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/rain_day.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "rain_day.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "rain_day@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "rain_day@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/rain_night.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "rain_night.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "rain_night@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "rain_night@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/few_clouds_day.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "cloudy_day.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "cloudy_day@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "cloudy_day@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/shower_rain.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "shower_rain.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "shower_rain@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "shower_rain@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/broken_clouds.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "broken_clouds.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "broken_clouds@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "broken_clouds@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/few_clouds_night.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "cloudy_night.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "cloudy_night@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "cloudy_night@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/scattered_clouds.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "scattered_clouds.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "scattered_clouds@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "scattered_clouds@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/thunderstorm_day.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "thunderstorm_day.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "thunderstorm_day@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "thunderstorm_day@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/thunderstorm_night.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "thunderstorm_night.png",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "filename" : "thunderstorm_night@2x.png",
10 | "idiom" : "universal",
11 | "scale" : "2x"
12 | },
13 | {
14 | "filename" : "thunderstorm_night@3x.png",
15 | "idiom" : "universal",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "author" : "xcode",
21 | "version" : 1
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Temperatura/Helpers/EnvironmentValues+ImageCache.swift:
--------------------------------------------------------------------------------
1 | //
2 | // EnvironmentValues+ImageCache.swift
3 | // AsyncImage
4 | //
5 | // Created by Vadim Bulavin on 3/24/20.
6 | // Copyright © 2020 Vadym Bulavin. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ImageCacheKey: EnvironmentKey {
12 | static let defaultValue: ImageCache = TemporaryImageCache()
13 | }
14 |
15 | extension EnvironmentValues {
16 | var imageCache: ImageCache {
17 | get { self[ImageCacheKey.self] }
18 | set { self[ImageCacheKey.self] = newValue }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Temperatura/Helpers/Constants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constants.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/8/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct Constants {
12 | static let APIKey = "72851dda65e1b81e5af962c62d81ebd5"
13 | static let coordsUrl = "https://api.openweathermap.org/data/2.5/weather?"
14 | static let cityUrl = "https://api.openweathermap.org/data/2.5/weather?"
15 | static let weatherIconUrl = "https://openweathermap.org/img/wn/"
16 | static let defaultWeatherIcon = "https://openweathermap.org/img/wn/02d@2x.png"
17 | }
18 |
--------------------------------------------------------------------------------
/Temperatura/Helpers/ImageCache.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageCache.swift
3 | // AsyncImage
4 | //
5 | // Created by Vadym Bulavin on 2/19/20.
6 | // Copyright © 2020 Vadym Bulavin. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol ImageCache {
12 | subscript(_ url: URL) -> UIImage? { get set }
13 | }
14 |
15 | struct TemporaryImageCache: ImageCache {
16 | private let cache = NSCache()
17 |
18 | subscript(_ key: URL) -> UIImage? {
19 | get { cache.object(forKey: key as NSURL) }
20 | set { newValue == nil ? cache.removeObject(forKey: key as NSURL) : cache.setObject(newValue!, forKey: key as NSURL) }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/Temperatura.xcodeproj/project.xcworkspace/xcuserdata/glennraya.xcuserdatad/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildLocationStyle
6 | UseAppPreferences
7 | CustomBuildLocationType
8 | RelativeToDerivedData
9 | DerivedDataLocationStyle
10 | Default
11 | IssueFilterStyle
12 | ShowActiveSchemeOnly
13 | LiveSourceIssuesEnabled
14 |
15 | ShowSharedSchemesAutomaticallyEnabled
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Temperatura/Models/Weather.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Weather.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/2/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | /// Weather Model
10 | import Foundation
11 |
12 | /// The weather response structure.
13 | struct WeatherResponse: Codable {
14 | let name: String
15 | let main: Main
16 | let wind: Wind
17 | }
18 |
19 | /// The 'main' weather object in the API response.
20 | struct Main: Codable {
21 | var temp: Double?
22 | var humidity: Double?
23 | var pressure: Double?
24 | }
25 |
26 | /// The 'wind' object in the API response.
27 | struct Wind: Codable {
28 | var speed: Double?
29 | }
30 |
31 | /// The 'weather' object in the API response.
32 | struct
33 |
--------------------------------------------------------------------------------
/Temperatura/HomeTab.swift:
--------------------------------------------------------------------------------
1 | //
2 | // HomeTab.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/8/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct HomeTab: View {
12 | var body: some View {
13 | TabView {
14 | Home().tabItem {
15 | Image(systemName: "sun.max.fill")
16 | Text("Current Weather")
17 | }
18 |
19 | Forecast().tabItem {
20 | Image(systemName: "calendar")
21 | Text("5 day Forecast")
22 | }
23 | }
24 | }
25 | }
26 |
27 | struct HomeTab_Previews: PreviewProvider {
28 | static var previews: some View {
29 | HomeTab()
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/TemperaturaTests/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 |
22 |
23 |
--------------------------------------------------------------------------------
/TemperaturaUITests/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 |
22 |
23 |
--------------------------------------------------------------------------------
/Temperatura/Helpers/ActivityIndicator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ActivityIndicator.swift
3 | // Temperatura
4 | //
5 | // This class will use the UIActivityIndicator for UIKit
6 | // since SwiftUI doesn't have the activity indicator.
7 | //
8 | // Created by Glenn Raya on 5/2/20.
9 | // Copyright © 2020 Glenn Raya. All rights reserved.
10 | //
11 |
12 | import Foundation
13 | import SwiftUI
14 |
15 | struct ActivityIndicator: UIViewRepresentable {
16 |
17 | @Binding var isAnimating: Bool
18 | let style: UIActivityIndicatorView.Style
19 |
20 | func makeUIView(context: UIViewRepresentableContext) -> UIActivityIndicatorView {
21 | return UIActivityIndicatorView(style: style)
22 | }
23 |
24 | func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext) {
25 | isAnimating ? uiView.startAnimating() : uiView.stopAnimating()
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Temperatura/Helpers/ImageUrl.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageUrl.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/9/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import Combine
11 | import UIKit
12 |
13 | class ImageUrl: ObservableObject {
14 | private var cancellable: AnyCancellable?
15 | let objectWillChange = PassthroughSubject()
16 |
17 | func load(url: URL) {
18 | self.cancellable = URLSession.shared
19 | .dataTaskPublisher(for: url)
20 | .map({ $0.data })
21 | .eraseToAnyPublisher()
22 | .receive(on: RunLoop.main)
23 | .map({ UIImage(data: $0) })
24 | .replaceError(with: nil)
25 | .sink(receiveValue: { image in
26 | self.objectWillChange.send(image)
27 | })
28 | }
29 |
30 | func cancel() {
31 | cancellable?.cancel()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Temperatura.xcodeproj/xcuserdata/glennraya.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/TemperaturaTests/TemperaturaTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TemperaturaTests.swift
3 | // TemperaturaTests
4 | //
5 | // Created by Glenn Raya on 5/2/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Temperatura
11 |
12 | class TemperaturaTests: XCTestCase {
13 |
14 | override func setUpWithError() throws {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDownWithError() throws {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testExample() throws {
23 | // This is an example of a functional test case.
24 | // Use XCTAssert and related functions to verify your tests produce the correct results.
25 | }
26 |
27 | func testPerformanceExample() throws {
28 | // This is an example of a performance test case.
29 | self.measure {
30 | // Put the code you want to measure the time of here.
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/Temperatura/Helpers/AsyncImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AsyncImage.swift
3 | // AsyncImage
4 | //
5 | // Created by Vadym Bulavin on 2/13/20.
6 | // Copyright © 2020 Vadym Bulavin. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct AsyncImage: View {
12 | @ObservedObject private var loader: ImageLoader
13 | private let placeholder: Placeholder?
14 | private let configuration: (Image) -> Image
15 |
16 | init(url: URL, cache: ImageCache? = nil, placeholder: Placeholder? = nil, configuration: @escaping (Image) -> Image = { $0 }) {
17 | loader = ImageLoader(url: url, cache: cache)
18 | self.placeholder = placeholder
19 | self.configuration = configuration
20 | }
21 |
22 | var body: some View {
23 | image
24 | .onAppear(perform: loader.load)
25 | .onDisappear(perform: loader.cancel)
26 | }
27 |
28 | private var image: some View {
29 | Group {
30 | if loader.image != nil {
31 | configuration(Image(uiImage: loader.image!).resizable())
32 | } else {
33 | placeholder
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/Temperatura/Models/WeatherModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Weather.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/2/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | /// Weather Model
10 | import Foundation
11 |
12 | /// The weather response structure.
13 | struct WeatherResponse: Codable {
14 | var name: String
15 | var dt: Int
16 | var timezone: Int
17 | var main: Main
18 | var wind: Wind
19 | var weather: [Weather]
20 | var sys: Sys
21 | }
22 |
23 | /// The 'main' weather object in the API response.
24 | struct Main: Codable {
25 | var temp: Double?
26 | var humidity: Double?
27 | var temp_min: Double?
28 | var temp_max: Double?
29 | var pressure: Int?
30 | var feels_like: Double?
31 | }
32 |
33 | /// The 'wind' object in the API response.
34 | struct Wind: Codable {
35 | var speed: Double?
36 | }
37 |
38 | /// The 'weather' object in the API response.
39 | struct Weather: Codable {
40 | var id: Int?
41 | var main: String?
42 | var description: String?
43 | var icon: String?
44 | }
45 |
46 | /// The 'sys' object in the API response.
47 | struct Sys: Codable {
48 | var country: String?
49 | var sunrise: Int?
50 | var sunset: Int?
51 | }
52 |
--------------------------------------------------------------------------------
/Temperatura/Partials/Humidity.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Humidity.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/5/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct WeatherPartials: View {
12 | var humidity: String? = ""
13 |
14 | var body: some View {
15 | HStack(spacing: 20.0) {
16 | Image(systemName: "thermometer")
17 | .resizable()
18 | .aspectRatio(contentMode: .fit)
19 | .frame(width: 32, height: 38)
20 | .foregroundColor(Color(#colorLiteral(red: 0.7254902124, green: 0.4784313738, blue: 0.09803921729, alpha: 1)))
21 | VStack(spacing: 10) {
22 | Text("Humidity")
23 | .font(.caption)
24 | .foregroundColor(.secondary)
25 | Text("\((humidity != "" ? humidity : "-") ?? "") \(humidity != "" ? "%" : "")")
26 | .font(.title).bold()
27 | }
28 | }
29 | .padding()
30 | .background(Color.white)
31 | .clipShape(RoundedRectangle(cornerRadius: 12.0))
32 | .shadow(color: Color.black.opacity(0.12), radius: 10, x: 0, y: 10)
33 | }
34 | }
35 |
36 | struct Humidity_Previews: PreviewProvider {
37 | static var previews: some View {
38 | WeatherPartials()
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Temperatura/Partials/GridView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GridView.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/14/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct GridView: View {
12 | @EnvironmentObject var weatherData: WeatherViewModel
13 |
14 | var body: some View {
15 | ScrollView(showsIndicators: false) {
16 | VStack {
17 | HStack(spacing: 15) {
18 | WeatherPartials(dataType: "humidity", humidity: weatherData.humidity)
19 | WeatherPartials(dataType: "windSpeed", windSpeed: weatherData.wind_speed)
20 | }
21 |
22 | HStack(spacing: 15) {
23 | WeatherPartials(dataType: "min_temp", temp_min: weatherData.temp_min)
24 | WeatherPartials(dataType: "max_temp", temp_max: weatherData.temp_max)
25 | }
26 |
27 | HStack(spacing: 15) {
28 | WeatherPartials(dataType: "sunrise", sunrise: weatherData.sunrise)
29 | WeatherPartials(dataType: "sunset", sunset: weatherData.sunset)
30 | }
31 | }
32 | .padding(.horizontal)
33 | .padding(.bottom)
34 | .padding(.top, 0)
35 | }
36 | }
37 | }
38 |
39 | struct GridView_Previews: PreviewProvider {
40 | static var previews: some View {
41 | GridView().environmentObject(WeatherViewModel())
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Temperatura/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/2/20.
6 | // Copyright © 2020 Glenn Raya. 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 |
--------------------------------------------------------------------------------
/Temperatura/Main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ContentView.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/2/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | let screen = UIScreen.main.bounds
12 |
13 | struct Home: View {
14 | @ObservedObject var temperaturaVM = TemperaturaViewModel()
15 |
16 | /// Initialize the temperaturaVM.
17 | init() {
18 | self.temperaturaVM = TemperaturaViewModel()
19 | }
20 |
21 | var body: some View {
22 | VStack {
23 | WeatherMain()
24 | List {
25 | Text("")
26 | }
27 | }
28 | }
29 | }
30 |
31 | /// Weather main detail section.
32 | struct WeatherMain: View {
33 | var body: some View {
34 | ZStack {
35 | LinearGradient(gradient: Gradient(colors: [Color(#colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1)), Color(#colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1))]), startPoint: .bottom, endPoint: .top)
36 | VStack {
37 | Text("Balanga City")
38 | .foregroundColor(.white)
39 | Text("32°")
40 | .font(.system(size: 72.0)).bold()
41 | .foregroundColor(.white)
42 | }
43 | }
44 | .edgesIgnoringSafeArea(.all)
45 | .frame(maxWidth: .infinity, maxHeight: screen.height * 0.50)
46 | }
47 | }
48 |
49 | /// Preview
50 | struct ContentView_Previews: PreviewProvider {
51 | static var previews: some View {
52 | Home()
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/TemperaturaUITests/TemperaturaUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TemperaturaUITests.swift
3 | // TemperaturaUITests
4 | //
5 | // Created by Glenn Raya on 5/2/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class TemperaturaUITests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 |
16 | // In UI tests it is usually best to stop immediately when a failure occurs.
17 | continueAfterFailure = false
18 |
19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
20 | }
21 |
22 | override func tearDownWithError() throws {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testExample() throws {
27 | // UI tests must launch the application that they test.
28 | let app = XCUIApplication()
29 | app.launch()
30 |
31 | // Use recording to get started writing UI tests.
32 | // Use XCTAssert and related functions to verify your tests produce the correct results.
33 | }
34 |
35 | func testLaunchPerformance() throws {
36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
37 | // This measures how long it takes to launch your application.
38 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) {
39 | XCUIApplication().launch()
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Temperatura/Models/ForecastModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ForecastModel.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/5/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct ForecastResponse: Codable {
12 | var city: City
13 | var list: [ForecastList]
14 | }
15 |
16 | /// 'city' object for the forecast model.
17 | struct City: Codable {
18 | var name: String?
19 | var country: String?
20 | var sunrise: Int?
21 | var sunset: Int?
22 | var timezone: Int?
23 | var coord: Coordinates?
24 | }
25 |
26 | /// 'list' object in forecast model.
27 | struct ForecastList: Codable {
28 | var dt: Int?
29 | var main: MainForecast?
30 | var weather: [WeatherForecast]?
31 | var clouds: Clouds?
32 | var wind: WindForecast?
33 | var dt_txt: String?
34 | }
35 |
36 | /// 'main' object inside the 'list' object in forecast model.
37 | struct MainForecast: Codable {
38 | var temp: Double?
39 | var feels_like: Double?
40 | var temp_min: Double?
41 | var temp_max: Double?
42 | var sea_level: Int?
43 | var grnd_level: Int?
44 | var humidity: Int?
45 | }
46 |
47 | /// 'weather' array of objects inside the 'list' object in forecast model.
48 | struct WeatherForecast: Codable {
49 | var id: Int?
50 | var main: String?
51 | var description: String?
52 | var icon: String?
53 | }
54 |
55 | /// 'clouds' object inside the 'list' object in forecast model.
56 | struct Clouds: Codable {
57 | var clouds: Int?
58 | }
59 |
60 |
61 | /// 'wind' object inside the 'list' object in forecast model.
62 | struct WindForecast: Codable {
63 | var speed: Double?
64 | var deg: Int?
65 | }
66 |
67 | /// 'coord' object insde the 'city' object in forecast model.
68 | struct Coordinates: Codable {
69 | var lat: Double?
70 | var lon: Double?
71 | }
72 |
--------------------------------------------------------------------------------
/Temperatura/Helpers/ImageLoader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageLoader.swift
3 | // AsyncImage
4 | //
5 | // Created by Vadym Bulavin on 2/13/20.
6 | // Copyright © 2020 Vadym Bulavin. All rights reserved.
7 | //
8 |
9 | import Combine
10 | import UIKit
11 |
12 | class ImageLoader: ObservableObject {
13 | @Published var image: UIImage?
14 |
15 | private(set) var isLoading = false
16 |
17 | private let url: URL
18 | private var cache: ImageCache?
19 | private var cancellable: AnyCancellable?
20 |
21 | private static let imageProcessingQueue = DispatchQueue(label: "image-processing")
22 |
23 | init(url: URL, cache: ImageCache? = nil) {
24 | self.url = url
25 | self.cache = cache
26 | }
27 |
28 | deinit {
29 | cancellable?.cancel()
30 | }
31 |
32 | func load() {
33 | guard !isLoading else { return }
34 |
35 | if let image = cache?[url] {
36 | self.image = image
37 | return
38 | }
39 |
40 | cancellable = URLSession.shared.dataTaskPublisher(for: url)
41 | .map { UIImage(data: $0.data) }
42 | .replaceError(with: nil)
43 | .handleEvents(receiveSubscription: { [weak self] _ in self?.onStart() },
44 | receiveOutput: { [weak self] in self?.cache($0) },
45 | receiveCompletion: { [weak self] _ in self?.onFinish() },
46 | receiveCancel: { [weak self] in self?.onFinish() })
47 | .subscribe(on: Self.imageProcessingQueue)
48 | .receive(on: DispatchQueue.main)
49 | .assign(to: \.image, on: self)
50 | }
51 |
52 | func cancel() {
53 | cancellable?.cancel()
54 | }
55 |
56 | private func onStart() {
57 | isLoading = true
58 | }
59 |
60 | private func onFinish() {
61 | isLoading = false
62 | }
63 |
64 | private func cache(_ image: UIImage?) {
65 | image.map { cache?[url] = $0 }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/Temperatura/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/Temperatura/Managers/LocationManager.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LocationManager.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/8/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import CoreLocation
11 | import Combine
12 |
13 | class LocationManager: NSObject, ObservableObject {
14 | private let locationManager = CLLocationManager()
15 | let objectWillChange = PassthroughSubject()
16 | private let geocoder = CLGeocoder()
17 |
18 | @Published var status: CLAuthorizationStatus? {
19 | willSet { objectWillChange.send() }
20 | }
21 |
22 | @Published var location: CLLocation? {
23 | willSet { objectWillChange.send() }
24 | }
25 |
26 | @Published var placemark: CLPlacemark? {
27 | willSet { objectWillChange.send() }
28 | }
29 |
30 | override init() {
31 | super.init()
32 |
33 | self.locationManager.delegate = self
34 | self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
35 | self.locationManager.requestWhenInUseAuthorization()
36 | self.locationManager.startUpdatingLocation()
37 | }
38 |
39 | private func geocode() {
40 | guard let location = self.location else { return }
41 | geocoder.reverseGeocodeLocation(location, completionHandler: { (places, error) in
42 | if error == nil {
43 | self.placemark = places?[0]
44 | } else {
45 | self.placemark = nil
46 | }
47 | })
48 | }
49 | }
50 |
51 | extension LocationManager: CLLocationManagerDelegate {
52 | func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
53 | self.status = status
54 | }
55 |
56 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
57 | guard let location = locations.last else { return }
58 | self.location = location
59 | self.geocode()
60 | }
61 | }
62 |
63 | extension CLLocation {
64 | var latitude: Double {
65 | return self.coordinate.latitude
66 | }
67 |
68 | var longitude: Double {
69 | return self.coordinate.longitude
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/Temperatura/Services/ForecastService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ForecastService.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/14/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | class ForecastService {
13 | /// Get the 5 day weather forecast for a given city.
14 | func getForecast(city: String, completion: @escaping (ForecastResponse?) -> ()) {
15 | guard let url = URL(string: "https://api.openweathermap.org/data/2.5/forecast?q=\(city)&appid=72851dda65e1b81e5af962c62d81ebd5&units=metric") else {
16 | completion(nil)
17 | return
18 | }
19 |
20 | URLSession.shared.dataTask(with: url) { data, response, error in
21 | guard let data = data, error == nil else {
22 | completion(nil)
23 | return
24 | }
25 |
26 | let forecastResponse = try? JSONDecoder().decode(ForecastResponse.self, from: data)
27 |
28 | if let forecastResponse = forecastResponse {
29 | let forecastData = forecastResponse
30 | completion(forecastData)
31 |
32 | } else {
33 | completion(nil)
34 | }
35 |
36 | }.resume()
37 | }
38 |
39 | /// Get the 5 day weather forecast using zip and country code.
40 | func getForecastByZipCode(zip: String, country_code: String, completion: @escaping (ForecastResponse?) -> ()) {
41 | guard let url = URL(string: "https://api.openweathermap.org/data/2.5/forecast?zip=\(zip),\(country_code)&appid=72851dda65e1b81e5af962c62d81ebd5&units=metric") else {
42 | completion(nil)
43 | return
44 | }
45 |
46 | URLSession.shared.dataTask(with: url) { data, response, error in
47 | guard let data = data, error == nil else {
48 | completion(nil)
49 | return
50 | }
51 |
52 | let forecastResponse = try? JSONDecoder().decode(ForecastResponse.self, from: data)
53 |
54 | if let forecastResponse = forecastResponse {
55 | let forecastData = forecastResponse
56 | completion(forecastData)
57 |
58 | } else {
59 | completion(nil)
60 | }
61 |
62 | }.resume()
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/Temperatura/Partials/OtherDetails.swift:
--------------------------------------------------------------------------------
1 | //
2 | // OtherDetails.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/5/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | /// Other weather details embedded in ScrollView view.
12 | struct OtherDetails: View {
13 | // @ObservedObject var weatherData = TemperaturaViewModel()
14 | @EnvironmentObject var weatherData: WeatherViewModel
15 | @State var showList: Bool = false
16 | @State var showGrid: Bool = true
17 |
18 | var body: some View {
19 | ZStack {
20 | Color.init(#colorLiteral(red: 0.9672107618, green: 0.9672107618, blue: 0.9672107618, alpha: 1))
21 | // Color.init(#colorLiteral(red: 0.9999960065, green: 1, blue: 1, alpha: 1))
22 | VStack {
23 | HStack(alignment: .center) {
24 | Text("Weather Details")
25 | .font(.headline)
26 | Spacer()
27 | HStack(spacing: 16.0) {
28 | Button(action: { self.showGrid = true; self.showList = false }) {
29 | Image(systemName: "rectangle.grid.2x2")
30 | .font(.system(size: 24, weight: .light))
31 | }
32 | .foregroundColor(self.showGrid ? Color.blue : Color.secondary)
33 |
34 | Button(action: { self.showList = true; self.showGrid = false }) {
35 | Image(systemName: "list.dash")
36 | .font(.system(size: 24, weight: .light))
37 | }
38 | .foregroundColor(showList ? Color.blue : Color.secondary)
39 | }
40 | }.padding()
41 |
42 | if showGrid {
43 | /// Show the grid view when grid view option is selected.
44 | GridView()
45 | } else {
46 | /// Show the list view when the list view option is selected.
47 | ListView()
48 | }
49 | }
50 | }
51 | }
52 | }
53 |
54 | struct OtherDetails_Previews: PreviewProvider {
55 | static var previews: some View {
56 | Group {
57 | OtherDetails().environmentObject(WeatherViewModel())
58 | OtherDetails().previewLayout(.fixed(width: 700, height: 323)).environmentObject(WeatherViewModel())
59 | }
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/Temperatura/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | NSLocationUsageDescription
6 | Temperatura requires your permission to access your location.
7 | NSLocationWhenInUseUsageDescription
8 | Temperatura requires your permission to access your location.
9 | LSApplicationCategoryType
10 |
11 | NSAppTransportSecurity
12 |
13 | NSAllowsArbitraryLoads
14 |
15 |
16 | CFBundleDevelopmentRegion
17 | $(DEVELOPMENT_LANGUAGE)
18 | CFBundleExecutable
19 | $(EXECUTABLE_NAME)
20 | CFBundleIdentifier
21 | $(PRODUCT_BUNDLE_IDENTIFIER)
22 | CFBundleInfoDictionaryVersion
23 | 6.0
24 | CFBundleName
25 | $(PRODUCT_NAME)
26 | CFBundlePackageType
27 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
28 | CFBundleShortVersionString
29 | 1.0
30 | CFBundleVersion
31 | 1
32 | LSRequiresIPhoneOS
33 |
34 | UIApplicationSceneManifest
35 |
36 | UIApplicationSupportsMultipleScenes
37 |
38 | UISceneConfigurations
39 |
40 | UIWindowSceneSessionRoleApplication
41 |
42 |
43 | UISceneConfigurationName
44 | Default Configuration
45 | UISceneDelegateClassName
46 | $(PRODUCT_MODULE_NAME).SceneDelegate
47 |
48 |
49 |
50 |
51 | UILaunchStoryboardName
52 | LaunchScreen
53 | UIRequiredDeviceCapabilities
54 |
55 | armv7
56 |
57 | UISupportedInterfaceOrientations
58 |
59 | UIInterfaceOrientationPortrait
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 | UISupportedInterfaceOrientations~ipad
64 |
65 | UIInterfaceOrientationPortrait
66 | UIInterfaceOrientationPortraitUpsideDown
67 | UIInterfaceOrientationLandscapeLeft
68 | UIInterfaceOrientationLandscapeRight
69 |
70 |
71 |
72 |
--------------------------------------------------------------------------------
/Temperatura/Services/WeatherService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Services.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/2/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | /// Defined services
10 | import Foundation
11 | import SwiftUI
12 |
13 | class WeatherService {
14 | /// Get the current weather forecast for a given city.
15 | func getWeather(city: String, byCoordinates: Bool, lat: Double, long: Double, completion: @escaping (WeatherResponse?) -> ()) {
16 |
17 | let coordsUrl = Constants.coordsUrl + "lat=\(lat)&lon=\(long)" + "&appid=\(Constants.APIKey)" + "&units=metric"
18 | let cityUrl = Constants.cityUrl + "q=\(city)" + "&appid=\(Constants.APIKey)" + "&units=metric"
19 |
20 | guard let url = byCoordinates ? URL(string: coordsUrl) : URL(string: cityUrl) else {
21 | completion(nil)
22 | return
23 | }
24 |
25 | URLSession.shared.dataTask(with: url) { data, response, error in
26 | guard let data = data, error == nil else {
27 | completion(nil)
28 | return
29 | }
30 |
31 | let weatherResponse = try? JSONDecoder().decode(WeatherResponse.self, from: data)
32 |
33 | if let weatherResponse = weatherResponse {
34 | let weatherData = weatherResponse
35 | completion(weatherData)
36 |
37 | } else {
38 | completion(nil)
39 | }
40 |
41 | }.resume()
42 | }
43 |
44 | /// Get the 5 day weather forecast using zip and country code.
45 | func getWeatherByZipCode(zip: String, country_code: String, completion: @escaping (WeatherResponse?) -> ()) {
46 | guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?zip=\(zip),\(country_code)&appid=72851dda65e1b81e5af962c62d81ebd5&units=metric") else {
47 | completion(nil)
48 | return
49 | }
50 |
51 | URLSession.shared.dataTask(with: url) { data, response, error in
52 | guard let data = data, error == nil else {
53 | completion(nil)
54 | return
55 | }
56 |
57 | let weatherResponse = try? JSONDecoder().decode(WeatherResponse.self, from: data)
58 |
59 | if let weatherResponse = weatherResponse {
60 | let weatherData = weatherResponse
61 | completion(weatherData)
62 |
63 | } else {
64 | completion(nil)
65 | }
66 |
67 | }.resume()
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Temperatura/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/2/20.
6 | // Copyright © 2020 Glenn Raya. 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 = HomeTab()
24 |
25 | // Use a UIHostingController as window root view controller.
26 | if let windowScene = scene as? UIWindowScene {
27 | let window = UIWindow(windowScene: windowScene)
28 |
29 | let temperaturaVM = WeatherViewModel()
30 |
31 | window.rootViewController = UIHostingController(rootView: contentView.environmentObject(temperaturaVM))
32 | self.window = window
33 | window.makeKeyAndVisible()
34 | }
35 | }
36 |
37 | func sceneDidDisconnect(_ scene: UIScene) {
38 | // Called as the scene is being released by the system.
39 | // This occurs shortly after the scene enters the background, or when its session is discarded.
40 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
41 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
42 | }
43 |
44 | func sceneDidBecomeActive(_ scene: UIScene) {
45 | // Called when the scene has moved from an inactive state to an active state.
46 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
47 | }
48 |
49 | func sceneWillResignActive(_ scene: UIScene) {
50 | // Called when the scene will move from an active state to an inactive state.
51 | // This may occur due to temporary interruptions (ex. an incoming phone call).
52 | }
53 |
54 | func sceneWillEnterForeground(_ scene: UIScene) {
55 | // Called as the scene transitions from the background to the foreground.
56 | // Use this method to undo the changes made on entering the background.
57 | }
58 |
59 | func sceneDidEnterBackground(_ scene: UIScene) {
60 | // Called as the scene transitions from the foreground to the background.
61 | // Use this method to save data, release shared resources, and store enough scene-specific state information
62 | // to restore the scene back to its current state.
63 | }
64 |
65 |
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/Temperatura/Partials/ListView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ListView.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/13/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ListView: View {
12 | @EnvironmentObject var weatherData: WeatherViewModel
13 |
14 | var dataType: String? = "sunrise"
15 | var humidity: String? = ""
16 | var windSpeed: String? = ""
17 | var temp_min: String? = ""
18 | var temp_max: String? = ""
19 | var sunrise: String? = "5:55 AM"
20 | var sunset: String? = ""
21 |
22 | var body: some View {
23 | List {
24 | DetailRow(icon: "thermometer", icon_color: Color(#colorLiteral(red: 0.9254902005, green: 0.2352941185, blue: 0.1019607857, alpha: 1)), title: "Humidity", data: weatherData.humidity, data_unit: "%")
25 | DetailRow(icon: "tornado",icon_color: Color(#colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1)), title: "Wind Speed", data: weatherData.wind_speed, data_unit: "km/hr")
26 | DetailRow(icon: "arrow.down", icon_color: Color(#colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1)), title: "Min temp", data: weatherData.temp_min, data_unit: "°C")
27 | DetailRow(icon: "arrow.up", icon_color: Color(#colorLiteral(red: 0.8078431487, green: 0.02745098062, blue: 0.3333333433, alpha: 1)), title: "Max Temp", data: weatherData.temp_max, data_unit: "°C")
28 | DetailRow(icon: "sunrise.fill", icon_color: Color(#colorLiteral(red: 0.9686274529, green: 0.78039217, blue: 0.3450980484, alpha: 1)), title: "Sunrise", data: weatherData.sunrise, data_unit: "am")
29 | DetailRow(icon: "sunset.fill", icon_color: Color(#colorLiteral(red: 0.9411764741, green: 0.4980392158, blue: 0.3529411852, alpha: 1)), title: "Sunset", data: weatherData.sunset, data_unit: "pm")
30 | }
31 | }
32 | }
33 |
34 | struct ListView_Previews: PreviewProvider {
35 | static var previews: some View {
36 | ListView().environmentObject(WeatherViewModel())
37 | }
38 | }
39 |
40 | struct DetailRow: View {
41 | var icon: String = ""
42 | var icon_color: Color = Color(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 1))
43 | var title: String = ""
44 | var data: String = ""
45 | var data_unit: String = ""
46 |
47 | var body: some View {
48 | HStack(spacing: 20) {
49 | ZStack {
50 | Image(systemName: icon)
51 | .font(.system(size: 24, weight: .light))
52 | .foregroundColor(icon_color)
53 | .imageScale(.medium)
54 | }
55 | .frame(width: 24, height: 24)
56 |
57 | Text(title)
58 | .font(.body)
59 | .foregroundColor(Color.secondary)
60 | Spacer()
61 | HStack {
62 | Text(data).font(.callout).bold()
63 | Text(data_unit)
64 | .font(.footnote)
65 | .foregroundColor(Color.secondary)
66 | }
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/Temperatura/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/Temperatura/Screens/Forecast.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Forecast.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/8/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct Forecast: View {
12 | @ObservedObject var forecastVM = ForecastViewModel()
13 | @ObservedObject var weatherVM = WeatherViewModel()
14 | @ObservedObject var lm = LocationManager()
15 |
16 | /// This will cache the weather icon from the openweather api.
17 | @Environment(\.imageCache) var cache: ImageCache
18 |
19 | /// Location details
20 | var latitude: Double { return lm.location?.latitude ?? 0 }
21 | var longitude: Double { return lm.location?.longitude ?? 0 }
22 | var zip: String { return lm.placemark?.postalCode ?? "2100" }
23 | var country_code: String { return lm.placemark?.isoCountryCode ?? "PH" }
24 | var status: String { return("\(String(describing: lm.status))") }
25 |
26 | var body: some View {
27 | NavigationView {
28 | List(self.forecastVM.forecastResponse.list, id: \.dt) { forecast in
29 |
30 | /// Navigate to the forecast details screen for more details.
31 | NavigationLink(destination: ForecastDetails(city: self.forecastVM.city + ", " + self.country_code, forecast: forecast, time: self.forecastVM.getTime(timeStamp: forecast.dt!), latitude: self.forecastVM.latitude, longitude: self.forecastVM.longitude)) {
32 | HStack {
33 | VStack(alignment: .leading) {
34 | Text("\(self.forecastVM.dateFormatter(timeStamp: forecast.dt!))").font(.footnote)
35 | Text("\(self.forecastVM.getTime(timeStamp: forecast.dt!))")
36 | .font(.footnote)
37 | .foregroundColor(Color.secondary)
38 | Text("\(self.forecastVM.city), \(self.country_code)")
39 | .font(.footnote).foregroundColor(Color.gray)
40 | Text("\(forecast.weather?[0].description ?? "")".capitalized)
41 | .font(.caption)
42 | .bold()
43 | .foregroundColor(Color.blue)
44 | .padding(.top, 20)
45 | }
46 | Spacer()
47 | VStack(alignment: .trailing) {
48 | HStack {
49 | /// Use the AsyncImage class to load and cache remote images.
50 | // AsyncImage(url: URL(string: "\(Constants.weatherIconUrl)\(forecast.weather?[0].icon ?? "02d")@2x.png")!,
51 | // cache: self.cache,
52 | // placeholder: ActivityIndicator(isAnimating: .constant(true), style: .large)
53 | // )
54 | // .frame(width: 50, height: 50)
55 | // .aspectRatio(contentMode: .fit)
56 | Image("\(self.forecastVM.getWeatherIcon(icon_name: (forecast.weather?[0].icon)!))")
57 | .resizable()
58 | .frame(width: 50, height: 50)
59 | .aspectRatio(contentMode: .fit)
60 | Text("\(self.forecastVM.formatDouble(temp: (forecast.main?.temp) ?? 0.0))°C")
61 | }
62 | }
63 | }
64 | .padding(.vertical, 10)
65 | }
66 | }
67 | .onAppear() {
68 | /// When the list view appears, get the weather forecast by zip and country code.
69 | /// The zip and country code are returned by the location manager.
70 | self.forecastVM.getForecastByZip(by: self.zip, country_code: self.country_code)
71 | }
72 | .navigationBarTitle("Next 5 Days")
73 | .navigationBarItems(trailing: Button(action: {
74 | /// Reload the weather forecast.
75 | self.forecastVM.getForecastByZip(by: self.zip, country_code: self.country_code)
76 | }) {
77 | Image(systemName: "arrow.clockwise")
78 | })
79 | }
80 | }
81 | }
82 |
83 | struct Forecast_Previews: PreviewProvider {
84 | static var previews: some View {
85 | Forecast()
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Temperatura/View Models/ForecastViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ForecastViewModel.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/14/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class ForecastViewModel: ObservableObject {
12 | private var forecastService: ForecastService!
13 | @Published var forecastResponse = ForecastResponse.init(city: City(), list: [])
14 |
15 | init() {
16 | self.forecastService = ForecastService()
17 | }
18 |
19 | /// Format the date properly (e.g. Monday, May 11, 2020)
20 | public func dateFormatter(timeStamp: Int) -> String {
21 | let formatter = DateFormatter()
22 | formatter.dateStyle = .full
23 | return formatter.string(from: Date(timeIntervalSince1970: TimeInterval(timeStamp)))
24 | }
25 |
26 | /// The the current time in 12-hour format with the right timezone with the am/pm (e.g. 5:52)
27 | public func getTime(timeStamp: Int) -> String {
28 | let formatter = DateFormatter()
29 | formatter.dateFormat = "h:mm a"
30 | formatter.timeZone = TimeZone(secondsFromGMT: self.forecastResponse.city.timezone ?? 0)
31 | return formatter.string(from: Date(timeIntervalSince1970: TimeInterval(timeStamp)))
32 | }
33 |
34 | /// Get the city name
35 | var city: String {
36 | if let city = forecastResponse.city.name {
37 | return city
38 | }
39 | return ""
40 | }
41 |
42 | /// Get the sunrise value
43 | var sunrise: Int {
44 | if let sunrise = forecastResponse.city.sunrise {
45 | return sunrise
46 | }
47 | return 0
48 | }
49 |
50 | /// Get the sunset value
51 | var sunset: Int {
52 | if let sunset = forecastResponse.city.sunset {
53 | return sunset
54 | }
55 | return 0
56 | }
57 |
58 | /// Get the latitude coordinate.
59 | var latitude: Double {
60 | if let latitude = forecastResponse.city.coord?.lat {
61 | return latitude
62 | }
63 | return 0.0
64 | }
65 |
66 | /// Get the longitude coordinate.
67 | var longitude: Double {
68 | if let longitude = forecastResponse.city.coord?.lon {
69 | return longitude
70 | }
71 | return 0.0
72 | }
73 |
74 | /// Get the weather condition icon.
75 | public func getWeatherIcon(icon_name: String) -> String {
76 | switch icon_name {
77 | case "01d":
78 | return "clear_sky_day"
79 | case "01n":
80 | return "clear_sky_night"
81 | case "02d":
82 | return "few_clouds_day"
83 | case "02n":
84 | return "few_clouds_night"
85 | case "03d":
86 | return "scattered_clouds"
87 | case "03n":
88 | return "scattered_clouds"
89 | case "04d":
90 | return "broken_clouds"
91 | case "04n":
92 | return "broken_clouds"
93 | case "09d":
94 | return "shower_rain"
95 | case "09n":
96 | return "shower_rain"
97 | case "10d":
98 | return "rain_day"
99 | case "10n":
100 | return "rain_night"
101 | case "11d":
102 | return "thunderstorm_day"
103 | case "11n":
104 | return "thunderstorm_night"
105 | case "13d":
106 | return "snow"
107 | case "13n":
108 | return "snow"
109 | case "50d":
110 | return "mist"
111 | case "50n":
112 | return "mist"
113 | default:
114 | return "clear_sky_day"
115 | }
116 | }
117 |
118 | public func reloadForecast() {
119 |
120 | }
121 |
122 | /// Search for city
123 | public func search(cityName: String) {
124 | /// You need to add the 'addingPercentEncoding' property so you can search for cities
125 | /// with space between words, otherwise it will only work on single word cities.
126 | print(cityName)
127 | if let city = cityName.addingPercentEncoding(withAllowedCharacters: .alphanumerics) {
128 | getForecast(by: city)
129 | }
130 | }
131 |
132 | public func formatDouble(temp: Double) -> String {
133 | return String(format: "%.1f", temp)
134 | }
135 |
136 | /// Get the weather forecast by city name
137 | public func getForecast(by city: String) {
138 | self.forecastService.getForecast(city: city) { forecast in
139 | if let forecast = forecast {
140 | DispatchQueue.main.async {
141 | self.forecastResponse = forecast
142 | }
143 | }
144 | }
145 | }
146 |
147 | /// Get the 5 day weather forecast using zip and country code.
148 | public func getForecastByZip(by zip: String, country_code: String) {
149 | self.forecastService.getForecastByZipCode(zip: zip, country_code: country_code) { forecast in
150 | if let forecast = forecast {
151 | DispatchQueue.main.async {
152 | self.forecastResponse = forecast
153 | print(self.forecastResponse)
154 | }
155 | }
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/Temperatura/Partials/WeatherPartials.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Humidity.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/5/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct WeatherPartials: View {
12 | var dataType: String? = "sunrise"
13 | var humidity: String? = ""
14 | var windSpeed: String? = ""
15 | var temp_min: String? = ""
16 | var temp_max: String? = ""
17 | var sunrise: String? = "5:55 AM"
18 | var sunset: String? = ""
19 |
20 | var body: some View {
21 | HStack(spacing: 20.0) {
22 | if dataType == "humidity" {
23 | Image(systemName: "thermometer")
24 | .font(.system(.title))
25 | .foregroundColor(Color(#colorLiteral(red: 0.7254902124, green: 0.4784313738, blue: 0.09803921729, alpha: 1)))
26 | }
27 |
28 | if dataType == "windSpeed" {
29 | Image(systemName: "tornado")
30 | .font(.system(.title))
31 | .foregroundColor(Color(#colorLiteral(red: 0.5568627715, green: 0.3529411852, blue: 0.9686274529, alpha: 1)))
32 | }
33 |
34 | if dataType == "min_temp" {
35 | Image(systemName: "arrow.down")
36 | .font(.system(.title))
37 | .foregroundColor(Color(#colorLiteral(red: 0.3411764801, green: 0.6235294342, blue: 0.1686274558, alpha: 1)))
38 | }
39 |
40 | if dataType == "max_temp" {
41 | Image(systemName: "arrow.up")
42 | .font(.system(.title))
43 | .foregroundColor(Color(#colorLiteral(red: 0.521568656, green: 0.1098039225, blue: 0.05098039284, alpha: 1)))
44 | }
45 |
46 | if dataType == "sunrise" {
47 | Image(systemName: "sunrise.fill")
48 | .font(.system(.title))
49 | .foregroundColor(Color(#colorLiteral(red: 0.9529411793, green: 0.6862745285, blue: 0.1333333403, alpha: 1)))
50 | }
51 |
52 | if dataType == "sunset" {
53 | Image(systemName: "sunset.fill")
54 | .font(.system(.title))
55 | .foregroundColor(Color(#colorLiteral(red: 0.7254902124, green: 0.4784313738, blue: 0.09803921729, alpha: 1)))
56 | }
57 |
58 | VStack(spacing: 10) {
59 | if dataType == "humidity" {
60 | Text("Humidity")
61 | .font(.caption)
62 | .foregroundColor(.secondary)
63 | HStack {
64 | Text("\((humidity != "" ? humidity : "-") ?? "")")
65 | .font(.callout).bold()
66 |
67 | Text("\(humidity != "" ? "%" : "")")
68 | .font(.footnote).foregroundColor(Color.secondary)
69 | }
70 | }
71 |
72 | if dataType == "windSpeed" {
73 | Text("Wind Speed")
74 | .font(.caption)
75 | .foregroundColor(.secondary)
76 | HStack {
77 | Text("\((windSpeed != "" ? windSpeed : "-") ?? "")")
78 | .font(.callout).bold()
79 |
80 | Text("\(windSpeed != "" ? "km/hr" : "")")
81 | .font(.footnote).foregroundColor(Color.secondary)
82 | }
83 | }
84 |
85 | if dataType == "min_temp" {
86 | Text("Min Temp")
87 | .font(.caption)
88 | .foregroundColor(.secondary)
89 | HStack {
90 | Text("\((temp_min != "" ? temp_min : "-") ?? "")")
91 | .font(.callout).bold()
92 |
93 | Text("\(temp_min != "" ? "°C" : "")")
94 | .font(.footnote).foregroundColor(Color.secondary)
95 | }
96 | }
97 |
98 | if dataType == "max_temp" {
99 | Text("Max Temp")
100 | .font(.caption)
101 | .foregroundColor(.secondary)
102 | HStack {
103 | Text("\((temp_max != "" ? temp_max : "-") ?? "")")
104 | .font(.callout).bold()
105 |
106 | Text("\(temp_max != "" ? "°C" : "")")
107 | .font(.footnote).foregroundColor(Color.secondary)
108 | }
109 | }
110 |
111 | if dataType == "sunrise" {
112 | Text("Sunrise")
113 | .font(.caption)
114 | .foregroundColor(.secondary)
115 | HStack {
116 | Text("\((sunrise != "" ? sunrise?.lowercased().replacingOccurrences(of: "am", with: "") : "-") ?? "")")
117 | .font(.callout).bold()
118 | Text("am")
119 | .font(.footnote).foregroundColor(Color.secondary)
120 | }
121 | }
122 |
123 | if dataType == "sunset" {
124 | Text("Sunset")
125 | .font(.caption)
126 | .foregroundColor(.secondary)
127 | HStack {
128 | Text("\((sunset != "" ? sunset?.lowercased().replacingOccurrences(of: "pm", with: "") : "-") ?? "")")
129 | .font(.callout).bold()
130 | Text("pm")
131 | .font(.footnote).foregroundColor(Color.secondary)
132 | }
133 | }
134 | }
135 | }
136 | .frame(maxWidth: .infinity)
137 | .padding()
138 | .background(Color.white)
139 | .clipShape(RoundedRectangle(cornerRadius: 12.0))
140 | .shadow(color: Color.black.opacity(0.05), radius: 5, x: 0, y: 3)
141 | }
142 | }
143 |
144 | struct WeatherPartials_Previews: PreviewProvider {
145 | static var previews: some View {
146 | WeatherPartials()
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/Temperatura/View Models/TemperaturaViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TemperaturaViewModel.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/2/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | /// ViewModel Class
10 | import Foundation
11 | import Combine
12 |
13 | class WeatherViewModel: ObservableObject {
14 | private var temperatureService: WeatherService!
15 | @Published var city_name: String = ""
16 | @Published var weatherResponse = WeatherResponse.init(name: "", dt: 0, timezone: 0, main: Main(), wind: Wind(), weather: [], sys: Sys())
17 | var weatherDate: Int = 0
18 |
19 | /// Initialize the WeatherService
20 | init() {
21 | self.temperatureService = WeatherService()
22 | weatherDate = self.weatherResponse.dt
23 | }
24 |
25 | /// Format the date properly (e.g. Monday, May 11, 2020)
26 | private func dateFormatter(timeStamp: Int) -> String {
27 | let formatter = DateFormatter()
28 | formatter.dateStyle = .full
29 | return formatter.string(from: Date(timeIntervalSince1970: TimeInterval(timeStamp)))
30 | }
31 |
32 | /// The the current time in 12-hour format with the right timezone (e.g. 5:52 AM)
33 | private func getTime(timeStamp: Int) -> String {
34 | let formatter = DateFormatter()
35 | formatter.dateFormat = "h:mm"
36 | formatter.timeZone = TimeZone(secondsFromGMT: self.weatherResponse.timezone)
37 | return formatter.string(from: Date(timeIntervalSince1970: TimeInterval(timeStamp)))
38 | }
39 |
40 | /// Get the date returned by the API
41 | var date: String {
42 | return self.dateFormatter(timeStamp: self.weatherResponse.dt)
43 | }
44 |
45 | /// Get the sunrise date
46 | var sunrise: String {
47 | if let sunrise = self.weatherResponse.sys.sunrise {
48 | return self.getTime(timeStamp: sunrise)
49 | }
50 | return ""
51 | }
52 |
53 | /// Get the sunset date
54 | var sunset: String {
55 | if let sunset = self.weatherResponse.sys.sunset {
56 | return self.getTime(timeStamp: sunset)
57 | }
58 | return ""
59 | }
60 |
61 | /// Get the temperature
62 | var temperature: String {
63 | if let temp = self.weatherResponse.main.temp {
64 | return String(format: "%.1f", temp)
65 | } else {
66 | return ""
67 | }
68 | }
69 |
70 | /// Get the min temp.
71 | var temp_min: String {
72 | if let temp_min = self.weatherResponse.main.temp_min {
73 | return String(format: "%.1f", temp_min)
74 | } else {
75 | return ""
76 | }
77 | }
78 |
79 | /// Get the max temp
80 | var temp_max: String {
81 | if let temp_max = self.weatherResponse.main.temp_max {
82 | return String(format: "%.1f", temp_max)
83 | } else {
84 | return ""
85 | }
86 | }
87 |
88 | /// Get the humidity
89 | var humidity: String {
90 | if let humidity = self.weatherResponse.main.humidity {
91 | return String(format: "%.1f", humidity)
92 | } else {
93 | return ""
94 | }
95 | }
96 |
97 | /// Get the wind speed.
98 | var wind_speed: String {
99 | if let wind_speed = self.weatherResponse.wind.speed {
100 | return String(format: "%.1f", wind_speed)
101 | } else {
102 | return ""
103 | }
104 | }
105 |
106 | /// Get the country code
107 | var country_code: String {
108 | if let country_code = self.weatherResponse.sys.country {
109 | return country_code
110 | } else {
111 | return ""
112 | }
113 | }
114 |
115 | /// Get the weather condition icon.
116 | var weatherIcon: String {
117 | if self.weatherResponse.weather.count != 0 {
118 | if let weatherIcon: String = self.weatherResponse.weather[0].icon {
119 | return "https://openweathermap.org/img/wn/\(weatherIcon)@2x.png"
120 | }
121 | }
122 | return "https://openweathermap.org/img/wn/02d@2x.png"
123 | }
124 |
125 | /// Get the weather description
126 | var description: String {
127 | if self.weatherResponse.weather.count != 0 {
128 | if let description: String = self.weatherResponse.weather[0].description {
129 | return description
130 | }
131 | }
132 | return ""
133 |
134 | }
135 |
136 | /// Determine the background image to be loaded based on whether it's night or day time.
137 | var loadBackgroundImage: String {
138 | if let sunset = self.weatherResponse.sys.sunset {
139 | if self.weatherResponse.dt >= sunset {
140 | return "night"
141 | } else {
142 | return "sunny"
143 | }
144 | }
145 | return "sunny"
146 |
147 | }
148 |
149 | /// Concatenate the city and country code (City of Balanga, PH).
150 | var city_country: String {
151 | if self.weatherResponse.name != "" && country_code != "" {
152 | return self.weatherResponse.name + ", " + self.country_code
153 | } else {
154 | return ""
155 | }
156 | }
157 |
158 | /// City name
159 | var cityName: String = ""
160 | var cityNameOnLoad: String = ""
161 |
162 | /// Search for city
163 | public func search() {
164 | /// You need to add the 'addingPercentEncoding' property so you can search for cities
165 | /// with space between words, otherwise it will only work on single word cities.
166 | if let city = self.cityName.addingPercentEncoding(withAllowedCharacters: .alphanumerics) {
167 | fetchWeather(by: city, byCoordinates: false, lat: 0.0, long: 0.0)
168 | }
169 | }
170 |
171 | /// Search for weather in a city upon the app loads.
172 | public func searchOnLoad(city: String, lat: Double, long: Double) {
173 | if let city = city.addingPercentEncoding(withAllowedCharacters: .alphanumerics) {
174 |
175 | /// TODO: Insert the location coordinates here...
176 | fetchWeather(by: city, byCoordinates: true, lat: lat, long: long)
177 | }
178 | }
179 |
180 | /// Fetch the weather by city
181 | private func fetchWeather(by city: String, byCoordinates: Bool, lat: Double, long: Double) {
182 | /// Trigger the getWeather service from the WeatherService.swift
183 | self.temperatureService.getWeather(city: city, byCoordinates: byCoordinates, lat: lat, long: long) { weather in
184 |
185 | if let weather = weather {
186 | DispatchQueue.main.async {
187 | self.weatherResponse = weather
188 | print(self.weatherResponse)
189 | }
190 | }
191 | }
192 | }
193 | }
194 |
--------------------------------------------------------------------------------
/Temperatura/Screens/ForecastDetails.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ForecastDetails.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/15/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | struct ForecastDetails: View {
12 | @ObservedObject var forecastVM = ForecastViewModel()
13 | var city: String = "City of Balanga"
14 | var forecast = ForecastList()
15 | var time: String = ""
16 | var latitude: Double = 0.0
17 | var longitude: Double = 0.0
18 |
19 | var body: some View {
20 | ScrollView(showsIndicators: false) {
21 | VStack {
22 | VStack(alignment: .trailing) {
23 | HStack(alignment: .center) {
24 | // AsyncImage(url: URL(string: "\(Constants.weatherIconUrl)\(self.forecast.weather![0].icon ?? "02d")@2x.png")!, placeholder: ActivityIndicator(isAnimating: .constant(true), style: .large))
25 | // .frame(width: 82, height: 82)
26 | // .aspectRatio(contentMode: .fit)
27 | Image("\(self.forecastVM.getWeatherIcon(icon_name: (forecast.weather?[0].icon)!))")
28 | .resizable()
29 | .frame(width: 82, height: 82)
30 | .aspectRatio(contentMode: .fit)
31 | .padding(.trailing, 32)
32 | Text("\(self.forecastVM.formatDouble(temp: self.forecast.main?.temp ?? 0.0))")
33 | .font(.system(size: 72.0, weight: .bold))
34 | .bold()
35 | Text("°C")
36 | .font(.title)
37 | .foregroundColor(.secondary)
38 | }
39 |
40 | HStack(spacing: 20.0) {
41 | VStack(alignment: .center, spacing: 20.0) {
42 | VStack(spacing: 5.0) {
43 | Text("Latitude")
44 | .font(.footnote)
45 | .foregroundColor(.secondary)
46 | Text("\(String(format: "%.4f", latitude))")
47 | }
48 | VStack(spacing: 5.0) {
49 | Text("Longitude")
50 | .font(.footnote)
51 | .foregroundColor(.secondary)
52 | Text("\(String(format: "%.4f", longitude))")
53 | }
54 | }
55 |
56 | Spacer()
57 |
58 | VStack(alignment: .trailing, spacing: 5) {
59 | Text("\(self.forecast.weather![0].description ?? "")".capitalized)
60 | .font(.title)
61 | Text("\(self.city)")
62 | Text("\(self.forecastVM.dateFormatter(timeStamp: self.forecast.dt!))")
63 | .font(.footnote)
64 | .foregroundColor(.secondary)
65 | Text("\(self.time)")
66 | .font(.footnote)
67 | .foregroundColor(.secondary)
68 | }
69 | }
70 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
71 | }
72 | .padding(.bottom, 30)
73 | .padding(.horizontal, 16)
74 |
75 | VStack {
76 | HStack(spacing: 10) {
77 | DetailGrid(icon: "arrow.down", icon_color: Color.green, title: "Min Temp", data_type: "temp_min", data: self.forecast)
78 | Divider()
79 | DetailGrid(icon: "arrow.up", icon_color: Color.purple, title: "Max Temp", data_type: "temp_max", data: self.forecast)
80 | }
81 | Divider()
82 | HStack(spacing: 10) {
83 | DetailGrid(icon: "tornado", icon_color: Color.orange, title: "Wind Speed", data_type: "wind", data: self.forecast)
84 | Divider()
85 | DetailGrid(icon: "thermometer", icon_color: Color.red, title: "Humidity", data_type: "humidity", data: self.forecast)
86 | }
87 | Divider()
88 | HStack(spacing: 10) {
89 | DetailGrid(icon: "heart", icon_color: Color.pink, title: "Feels Like", data_type: "feels_like", data: self.forecast)
90 | Divider()
91 | DetailGrid(icon: "wind", icon_color: Color.blue, title: "Sea Level", data_type: "sea_level", data: self.forecast)
92 | }
93 |
94 | }.padding()
95 | }.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
96 | .onAppear() {
97 | print(self.forecast)
98 | }
99 | }
100 | }
101 | }
102 |
103 | /// Detail grid cell.
104 | struct DetailGrid: View {
105 | @ObservedObject var forecastVM = ForecastViewModel()
106 | var icon: String = ""
107 | var icon_color: Color = Color.green
108 | var title: String = "Min Temp"
109 | var data_type: String = ""
110 | var data = ForecastList()
111 |
112 | var body: some View {
113 | HStack(spacing: 15.0) {
114 | Image(systemName: icon)
115 | .font(.title)
116 | .foregroundColor(icon_color)
117 | VStack(alignment: .leading) {
118 | Text(title)
119 | .font(.caption)
120 | .foregroundColor(.secondary)
121 | HStack {
122 | if data_type == "temp_min" {
123 | Text("\(self.forecastVM.formatDouble(temp: self.data.main?.temp_min ?? 0.0))")
124 | .bold()
125 | .lineLimit(1)
126 | Text("°C")
127 | .font(.footnote)
128 | .foregroundColor(.secondary)
129 | }
130 |
131 | if data_type == "temp_max" {
132 | Text("\(self.forecastVM.formatDouble(temp: self.data.main?.temp_max ?? 0.0))")
133 | .bold()
134 | .lineLimit(1)
135 | Text("°C")
136 | .font(.footnote)
137 | .foregroundColor(.secondary)
138 | }
139 |
140 | if data_type == "wind" {
141 | Text("\(self.forecastVM.formatDouble(temp: self.data.wind?.speed ?? 0.0))")
142 | .bold()
143 | .lineLimit(1)
144 | Text("km/hr")
145 | .font(.footnote)
146 | .foregroundColor(.secondary)
147 | }
148 |
149 | if data_type == "humidity" {
150 | Text("\(self.data.main?.humidity ?? 0)")
151 | .bold()
152 | .lineLimit(1)
153 | Text("%")
154 | .font(.footnote)
155 | .foregroundColor(.secondary)
156 | }
157 |
158 | if data_type == "feels_like" {
159 | Text("\(self.forecastVM.formatDouble(temp: self.data.main?.feels_like ?? 0.0))")
160 | .bold()
161 | .lineLimit(1)
162 | Text("°C")
163 | .font(.footnote)
164 | .foregroundColor(.secondary)
165 | }
166 |
167 | if data_type == "sea_level" {
168 | Text("\(self.data.main?.sea_level ?? 0)")
169 | .bold()
170 | .lineLimit(1)
171 | Text("hPa")
172 | .font(.footnote)
173 | .foregroundColor(.secondary)
174 | }
175 | }
176 | }
177 | }
178 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
179 | .padding(.vertical, 10)
180 | }
181 | }
182 |
183 | struct ForecastDetails_Previews: PreviewProvider {
184 | static var previews: some View {
185 | ForecastDetails()
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/Temperatura/View Models/WeatherViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TemperaturaViewModel.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/2/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | /// ViewModel Class
10 | import Foundation
11 | import Combine
12 |
13 | class WeatherViewModel: ObservableObject {
14 | private var temperatureService: WeatherService!
15 | @Published var city_name: String = ""
16 | @Published var weatherResponse = WeatherResponse.init(name: "", dt: 0, timezone: 0, main: Main(), wind: Wind(), weather: [], sys: Sys())
17 | @Published var dayTime: Bool = true
18 | var weatherDate: Int = 0
19 |
20 | /// Initialize the WeatherService
21 | init() {
22 | self.temperatureService = WeatherService()
23 | weatherDate = self.weatherResponse.dt
24 | }
25 |
26 | /// Format the date properly (e.g. Monday, May 11, 2020)
27 | private func dateFormatter(timeStamp: Int) -> String {
28 | let formatter = DateFormatter()
29 | formatter.dateStyle = .full
30 | return formatter.string(from: Date(timeIntervalSince1970: TimeInterval(timeStamp)))
31 | }
32 |
33 | /// The the current time in 12-hour format with the right timezone (e.g. 5:52 AM)
34 | private func getTime(timeStamp: Int) -> String {
35 | let formatter = DateFormatter()
36 | formatter.dateFormat = "h:mm a"
37 | formatter.timeZone = TimeZone(secondsFromGMT: self.weatherResponse.timezone)
38 | return formatter.string(from: Date(timeIntervalSince1970: TimeInterval(timeStamp)))
39 | }
40 |
41 | /// Get the date returned by the API
42 | var date: String {
43 | return self.dateFormatter(timeStamp: self.weatherResponse.dt)
44 | }
45 |
46 | /// Get the sunrise date
47 | var sunrise: String {
48 | if let sunrise = self.weatherResponse.sys.sunrise {
49 | return self.getTime(timeStamp: sunrise)
50 | }
51 | return ""
52 | }
53 |
54 | /// Get the sunset date
55 | var sunset: String {
56 | if let sunset = self.weatherResponse.sys.sunset {
57 | return self.getTime(timeStamp: sunset)
58 | }
59 | return ""
60 | }
61 |
62 | /// Get the temperature
63 | var temperature: String {
64 | if let temp = self.weatherResponse.main.temp {
65 | return String(format: "%.1f", temp)
66 | } else {
67 | return "0.0"
68 | }
69 | }
70 |
71 | /// Get the min temp.
72 | var temp_min: String {
73 | if let temp_min = self.weatherResponse.main.temp_min {
74 | return String(format: "%.1f", temp_min)
75 | } else {
76 | return "0.0"
77 | }
78 | }
79 |
80 | /// Get the max temp
81 | var temp_max: String {
82 | if let temp_max = self.weatherResponse.main.temp_max {
83 | return String(format: "%.1f", temp_max)
84 | } else {
85 | return "0.0"
86 | }
87 | }
88 |
89 | /// Get the humidity
90 | var humidity: String {
91 | if let humidity = self.weatherResponse.main.humidity {
92 | return String(format: "%.1f", humidity)
93 | } else {
94 | return ""
95 | }
96 | }
97 |
98 | /// Get the wind speed.
99 | var wind_speed: String {
100 | if let wind_speed = self.weatherResponse.wind.speed {
101 | return String(format: "%.1f", wind_speed)
102 | } else {
103 | return "0.0"
104 | }
105 | }
106 |
107 | /// Get the country code
108 | var country_code: String {
109 | if let country_code = self.weatherResponse.sys.country {
110 | return country_code
111 | } else {
112 | return ""
113 | }
114 | }
115 |
116 | /// Get the weather condition icon.
117 | var weatherIcon: String {
118 | if self.weatherResponse.weather.count != 0 {
119 | if let weatherIcon: String = self.weatherResponse.weather[0].icon {
120 | switch weatherIcon {
121 | case "01d":
122 | return "clear_sky_day"
123 | case "01n":
124 | return "clear_sky_night"
125 | case "02d":
126 | return "few_clouds_day"
127 | case "02n":
128 | return "few_clouds_night"
129 | case "03d":
130 | return "scattered_clouds"
131 | case "03n":
132 | return "scattered_clouds"
133 | case "04d":
134 | return "broken_clouds"
135 | case "04n":
136 | return "broken_clouds"
137 | case "09d":
138 | return "shower_rain"
139 | case "09n":
140 | return "shower_rain"
141 | case "10d":
142 | return "rain_day"
143 | case "10n":
144 | return "rain_night"
145 | case "11d":
146 | return "thunderstorm_day"
147 | case "11n":
148 | return "thunderstorm_night"
149 | case "13d":
150 | return "snow"
151 | case "13n":
152 | return "snow"
153 | case "50d":
154 | return "mist"
155 | case "50n":
156 | return "mist"
157 | default:
158 | return "clear_sky_day"
159 | }
160 | }
161 | }
162 | return "clear_sky_day"
163 | }
164 |
165 | /// Get the weather description
166 | var description: String {
167 | if self.weatherResponse.weather.count != 0 {
168 | if let description: String = self.weatherResponse.weather[0].description {
169 | return description
170 | }
171 | }
172 | return ""
173 |
174 | }
175 |
176 | /// Determine the background image to be loaded based on whether it's night or day time.
177 | var loadBackgroundImage: Bool {
178 | if let sunset = self.weatherResponse.sys.sunset {
179 | if self.weatherResponse.dt >= sunset {
180 | return false
181 | } else {
182 | return true
183 | }
184 | }
185 | return true
186 |
187 | }
188 |
189 | /// Concatenate the city and country code (City of Balanga, PH).
190 | var city_country: String {
191 | if self.weatherResponse.name != "" && country_code != "" {
192 | return self.weatherResponse.name + ", " + self.country_code
193 | }
194 | return "-"
195 | }
196 |
197 | /// City name
198 | var cityName: String = ""
199 | var cityNameOnLoad: String = ""
200 |
201 | /// Search for city
202 | public func search(searchText: String) {
203 | /// You need to add the 'addingPercentEncoding' property so you can search for cities
204 | /// with space between words, otherwise it will only work on single word cities.
205 | // if let city = self.cityName.addingPercentEncoding(withAllowedCharacters: .alphanumerics) {
206 | // fetchWeather(by: city, byCoordinates: false, lat: 0.0, long: 0.0)
207 | // }
208 |
209 | if let city = searchText.addingPercentEncoding(withAllowedCharacters: .alphanumerics) {
210 | fetchWeather(by: city, byCoordinates: false, lat: 0.0, long: 0.0)
211 | }
212 | }
213 |
214 | /// Search for weather in a city upon the app loads.
215 | public func searchOnLoad(city: String, lat: Double, long: Double) {
216 | if let city = city.addingPercentEncoding(withAllowedCharacters: .alphanumerics) {
217 | fetchWeather(by: city, byCoordinates: true, lat: lat, long: long)
218 | }
219 | }
220 |
221 | /// Get the current weather by zip code.
222 | public func getWeatherByZipCode(by zip: String, country_code: String) {
223 | self.temperatureService.getWeatherByZipCode(zip: zip, country_code: country_code) { weather in
224 | if let weather = weather {
225 | DispatchQueue.main.async {
226 | self.weatherResponse = weather
227 | }
228 | }
229 | }
230 | }
231 |
232 | /// Fetch the weather by city
233 | private func fetchWeather(by city: String, byCoordinates: Bool, lat: Double, long: Double) {
234 | /// Trigger the getWeather service from the WeatherService.swift
235 | self.temperatureService.getWeather(city: city, byCoordinates: byCoordinates, lat: lat, long: long) { weather in
236 |
237 | if let weather = weather {
238 | DispatchQueue.main.async {
239 | self.weatherResponse = weather
240 | }
241 | }
242 | }
243 | }
244 | }
245 |
--------------------------------------------------------------------------------
/Temperatura/Screens/Home.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Home.swift
3 | // Temperatura
4 | //
5 | // Created by Glenn Raya on 5/17/20.
6 | // Copyright © 2020 Glenn Raya. All rights reserved.
7 | //
8 |
9 | import SwiftUI
10 |
11 | let screen = UIScreen.main.bounds
12 |
13 | struct Home: View {
14 | /// The TemperaturaViewModel has been moved to the environment object.
15 | /// To make all the data available to child components of this view.
16 | @EnvironmentObject var temperaturaVM: WeatherViewModel
17 |
18 | /// The location manager is the class that fetches the location of the user
19 | /// This requires 'Privacy' location proerties in the info.plist file.
20 | @ObservedObject var lm = LocationManager()
21 |
22 | /// Show the search city text field or not.
23 | @State var searchCity: Bool = false
24 |
25 | @State var iconScaleInitSize: CGFloat = 0.0
26 |
27 | @State var showAlert: Bool = false
28 |
29 | /// The city associated with the area.
30 | var placemark: String { return("\(lm.placemark?.locality ?? "")") }
31 |
32 | /// Additional city-level information for the area.
33 | var subLocality: String { return("\(lm.placemark?.subLocality ?? "")") }
34 |
35 | /// Administrative area could be state or province.
36 | var administrativeArea: String { return("\(lm.placemark?.administrativeArea ?? "")") }
37 |
38 | /// Latitude coordinate of the area.
39 | var latitude: Double { return lm.location?.latitude ?? 0 }
40 |
41 | /// Longitude coordinate of the area.
42 | var longitude: Double { return lm.location?.longitude ?? 0 }
43 |
44 | /// The zip code of the area.
45 | var zip: String { return lm.placemark?.postalCode ?? "2100" }
46 |
47 | /// The country code of the area.
48 | var country_code: String { return lm.placemark?.isoCountryCode ?? "PH" }
49 |
50 | /// The name of the country.
51 | var country_name: String { return lm.placemark?.country ?? "Philippines" }
52 |
53 | @State var searchField = ""
54 |
55 | var body: some View {
56 | ZStack {
57 | LinearGradient(gradient: Gradient(colors: self.temperaturaVM.loadBackgroundImage ? [Color(#colorLiteral(red: 0.09411764706, green: 0.4196078431, blue: 0.8431372549, alpha: 1)), Color(#colorLiteral(red: 0.5441120482, green: 0.5205187814, blue: 0.9921568627, alpha: 1))] : [Color(#colorLiteral(red: 0.1019607843, green: 0.168627451, blue: 0.262745098, alpha: 1)), Color(#colorLiteral(red: 0.3647058824, green: 0.5058823529, blue: 0.6549019608, alpha: 1))]), startPoint: .bottom, endPoint: .top)
58 | .edgesIgnoringSafeArea(.all)
59 |
60 | ScrollView(showsIndicators: false) {
61 | /// Top Info (weather condition, city, date and weather icon).
62 | VStack {
63 | HStack(spacing: 32) {
64 | /// City search text field.
65 | HStack {
66 | TextField("Enter city name", text: self.$searchField) {
67 | self.temperaturaVM.search(searchText: self.searchField)
68 | // self.temperaturaVM.cityName = ""
69 | }
70 | .padding()
71 |
72 | Button(action: { self.searchField = "" }) {
73 | Text("Clear")
74 | }
75 | .padding(.trailing)
76 | }
77 | .background(Color.white.opacity(0.30))
78 | .clipShape(RoundedRectangle(cornerRadius: 10))
79 |
80 | Button(action: {
81 | self.temperaturaVM.getWeatherByZipCode(by: self.zip, country_code: self.country_code)
82 | self.showAlert = true
83 | }) {
84 | Image(systemName: "location.fill")
85 | }
86 | .font(.system(size: 21))
87 | .foregroundColor(Color.white)
88 | .shadow(color: Color.black.opacity(0.20), radius: 5, x: 0, y: 6)
89 | .alert(isPresented: $showAlert) {
90 | Alert(title: Text("Your Location"), message: Text("You are currently located at \(self.placemark), \(self.administrativeArea) \(self.country_name)"), dismissButton: .default(Text("Got it!")))
91 | }
92 | }
93 | .padding(.top, 16)
94 | .padding(.horizontal, 16)
95 | .padding(.bottom, 16)
96 |
97 | /// Weather icon, description, place and date.
98 | HStack {
99 | Image("\(self.temperaturaVM.weatherIcon)")
100 | .resizable()
101 | .frame(width: 92, height: 92)
102 | .aspectRatio(contentMode: .fit)
103 | Spacer()
104 | VStack(alignment: .trailing, spacing: 5.0) {
105 | Text(self.temperaturaVM.description.capitalized)
106 | .font(.title)
107 | .bold()
108 | .foregroundColor(.white)
109 | .shadow(color: Color.black.opacity(0.20), radius: 4, x: 0, y: 3)
110 | Text(self.temperaturaVM.city_country)
111 | .foregroundColor(.white)
112 | .shadow(color: Color.black.opacity(0.20), radius: 4, x: 0, y: 3)
113 | Text(self.temperaturaVM.date)
114 | .font(.footnote)
115 | .foregroundColor(.white)
116 | .shadow(color: Color.black.opacity(0.20), radius: 4, x: 0, y: 3)
117 | }
118 | }
119 | .padding(.horizontal, 16)
120 |
121 | /// Temperature reading.
122 | Text("\(self.temperaturaVM.temperature)°")
123 | .font(.system(size: 72))
124 | .bold()
125 | .foregroundColor(.white)
126 | .shadow(color: Color.black.opacity(0.20), radius: 4, x: 0, y: 3)
127 |
128 | /// Sunrise and sunset.
129 | HStack(spacing: 40) {
130 | HStack {
131 | Image(systemName: "sunrise")
132 | Text("\(self.temperaturaVM.sunrise.replacingOccurrences(of: "AM", with: "")) am")
133 | }
134 |
135 | HStack {
136 | Image(systemName: "sunset")
137 | Text("\(self.temperaturaVM.sunset.replacingOccurrences(of: "PM", with: "")) pm")
138 | }
139 | }
140 | .foregroundColor(.white)
141 | .shadow(color: Color.black.opacity(0.20), radius: 4, x: 0, y: 3)
142 |
143 | /// Grid view of other weather details.
144 | VStack(spacing: 47) {
145 | HStack(spacing: 5) {
146 | DetailCell(icon: "thermometer.sun", title: "Humidity", data: self.temperaturaVM.temperature, unit: "%")
147 | Spacer()
148 | DetailCell(icon: "tornado", title: "Wind Speed", data: self.temperaturaVM.wind_speed, unit: "Km/hr")
149 | }
150 |
151 | HStack(spacing: 5) {
152 | DetailCell(icon: "arrow.down.circle", title: "Min Temp", data: self.temperaturaVM.temp_min, unit: "°C")
153 | Divider().frame(height: 50).background(Color.white)
154 | DetailCell(icon: "arrow.up.circle", title: "Max Temp", data: self.temperaturaVM.temp_max, unit: "°C")
155 | }
156 |
157 | HStack(spacing: 5) {
158 | DetailCell(icon: "heart", title: "Feels Like", data: "32.4", unit: "°C")
159 | Divider().frame(height: 50).background(Color.white)
160 | DetailCell(icon: "rectangle.compress.vertical", title: "Pressure", data: "1002", unit: "hPa")
161 | }
162 | }
163 | .padding(.vertical, 30)
164 | .background(Color.secondary.opacity(0.30))
165 | .clipShape(RoundedRectangle(cornerRadius: 10.0))
166 |
167 | }
168 |
169 | }
170 | .padding(.top)
171 | .padding(.horizontal)
172 | .onAppear() {
173 | self.temperaturaVM.getWeatherByZipCode(by: self.zip, country_code: self.country_code)
174 | }
175 | .edgesIgnoringSafeArea(.horizontal)
176 |
177 | }.background(Color.red)
178 |
179 |
180 | }
181 | }
182 |
183 | /// Refactored weather detail grid cell.
184 | struct DetailCell: View {
185 | var icon: String = "thermometer.sun"
186 | var title: String = "Humidity"
187 | var data: String = "30.0"
188 | var unit: String = "%"
189 |
190 | var body: some View {
191 | HStack(spacing: 16) {
192 | Image(systemName: icon)
193 | .font(.system(size: 33, weight: .thin))
194 | .foregroundColor(.white)
195 | VStack(alignment: .leading, spacing: 5.0) {
196 | Text(title)
197 | .foregroundColor(Color(#colorLiteral(red: 0.5607843137, green: 0.7411764706, blue: 0.9803921569, alpha: 1)))
198 | HStack {
199 | Text("\(data)")
200 | .font(.system(size: 21))
201 | .bold()
202 | .foregroundColor(.white)
203 | Text(unit)
204 | .font(.system(size: 14))
205 | .bold()
206 | .foregroundColor(.white)
207 | }
208 | }
209 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
210 |
211 | }
212 | .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
213 | .padding(.horizontal, 16)
214 | .shadow(color: Color.black.opacity(0.20), radius: 4, x: 0, y: 3)
215 | }
216 | }
217 |
218 | struct Home_Previews: PreviewProvider {
219 | static var previews: some View {
220 | Home().environmentObject(WeatherViewModel())
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/Temperatura.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | C311457A246BA0BE0000CA03 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3114579246BA0BE0000CA03 /* ListView.swift */; };
11 | C319D4602465389400D8C576 /* LocationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C319D45F2465389400D8C576 /* LocationManager.swift */; };
12 | C319D46224657A3400D8C576 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C319D46124657A3400D8C576 /* Constants.swift */; };
13 | C35ECD45246114C7001E3A65 /* ForecastModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35ECD44246114C7001E3A65 /* ForecastModel.swift */; };
14 | C35ECD4824614081001E3A65 /* WeatherPartials.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35ECD4724614081001E3A65 /* WeatherPartials.swift */; };
15 | C35ECD4C24615F21001E3A65 /* OtherDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C35ECD4B24615F21001E3A65 /* OtherDetails.swift */; };
16 | C37CB163246CCFD400F6537D /* GridView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C37CB162246CCFD400F6537D /* GridView.swift */; };
17 | C37CB165246D0C3F00F6537D /* ForecastService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C37CB164246D0C3F00F6537D /* ForecastService.swift */; };
18 | C37CB167246D0CF900F6537D /* ForecastViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C37CB166246D0CF900F6537D /* ForecastViewModel.swift */; };
19 | C37CEE7F245D65F000473301 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C37CEE7E245D65F000473301 /* AppDelegate.swift */; };
20 | C37CEE81245D65F000473301 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C37CEE80245D65F000473301 /* SceneDelegate.swift */; };
21 | C37CEE85245D65FA00473301 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C37CEE84245D65FA00473301 /* Assets.xcassets */; };
22 | C37CEE88245D65FA00473301 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C37CEE87245D65FA00473301 /* Preview Assets.xcassets */; };
23 | C37CEE8B245D65FA00473301 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C37CEE89245D65FA00473301 /* LaunchScreen.storyboard */; };
24 | C37CEE96245D65FB00473301 /* TemperaturaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C37CEE95245D65FB00473301 /* TemperaturaTests.swift */; };
25 | C37CEEA1245D65FB00473301 /* TemperaturaUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C37CEEA0245D65FB00473301 /* TemperaturaUITests.swift */; };
26 | C3901693246E77CB000086AD /* EnvironmentValues+ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C390168F246E77CA000086AD /* EnvironmentValues+ImageCache.swift */; };
27 | C3901694246E77CB000086AD /* ImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3901690246E77CA000086AD /* ImageLoader.swift */; };
28 | C3901695246E77CC000086AD /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3901691246E77CA000086AD /* ImageCache.swift */; };
29 | C3901696246E77CC000086AD /* AsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3901692246E77CB000086AD /* AsyncImage.swift */; };
30 | C3901698246E7B5D000086AD /* ImageUrl.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3901697246E7B5D000086AD /* ImageUrl.swift */; };
31 | C390169A246E8D53000086AD /* ForecastDetails.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3901699246E8D53000086AD /* ForecastDetails.swift */; };
32 | C3941A6C24710B480087FD2C /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3941A6B24710B480087FD2C /* Home.swift */; };
33 | C39DBC012464F6DE002659C4 /* HomeTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = C39DBC002464F6DE002659C4 /* HomeTab.swift */; };
34 | C39DBC032464F7EB002659C4 /* Forecast.swift in Sources */ = {isa = PBXBuildFile; fileRef = C39DBC022464F7EB002659C4 /* Forecast.swift */; };
35 | C3A08CD5245D8377002FC9FF /* WeatherModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A08CD4245D8377002FC9FF /* WeatherModel.swift */; };
36 | C3A08CD7245D9850002FC9FF /* WeatherViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A08CD6245D9850002FC9FF /* WeatherViewModel.swift */; };
37 | C3A08CD9245DCA4F002FC9FF /* WeatherService.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3A08CD8245DCA4F002FC9FF /* WeatherService.swift */; };
38 | C3CFBD98245FADFE00194D42 /* ActivityIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3CFBD95245FADFE00194D42 /* ActivityIndicator.swift */; };
39 | C3D72B242467F075006397A4 /* HealthKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C3D72B232467F074006397A4 /* HealthKit.framework */; };
40 | /* End PBXBuildFile section */
41 |
42 | /* Begin PBXContainerItemProxy section */
43 | C37CEE92245D65FB00473301 /* PBXContainerItemProxy */ = {
44 | isa = PBXContainerItemProxy;
45 | containerPortal = C37CEE73245D65F000473301 /* Project object */;
46 | proxyType = 1;
47 | remoteGlobalIDString = C37CEE7A245D65F000473301;
48 | remoteInfo = Temperatura;
49 | };
50 | C37CEE9D245D65FB00473301 /* PBXContainerItemProxy */ = {
51 | isa = PBXContainerItemProxy;
52 | containerPortal = C37CEE73245D65F000473301 /* Project object */;
53 | proxyType = 1;
54 | remoteGlobalIDString = C37CEE7A245D65F000473301;
55 | remoteInfo = Temperatura;
56 | };
57 | /* End PBXContainerItemProxy section */
58 |
59 | /* Begin PBXFileReference section */
60 | C3114579246BA0BE0000CA03 /* ListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListView.swift; sourceTree = ""; };
61 | C319D45F2465389400D8C576 /* LocationManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocationManager.swift; sourceTree = ""; };
62 | C319D46124657A3400D8C576 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
63 | C35ECD44246114C7001E3A65 /* ForecastModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastModel.swift; sourceTree = ""; };
64 | C35ECD4724614081001E3A65 /* WeatherPartials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherPartials.swift; sourceTree = ""; };
65 | C35ECD4B24615F21001E3A65 /* OtherDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherDetails.swift; sourceTree = ""; };
66 | C37CB162246CCFD400F6537D /* GridView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridView.swift; sourceTree = ""; };
67 | C37CB164246D0C3F00F6537D /* ForecastService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastService.swift; sourceTree = ""; };
68 | C37CB166246D0CF900F6537D /* ForecastViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastViewModel.swift; sourceTree = ""; };
69 | C37CEE7B245D65F000473301 /* Temperatura.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Temperatura.app; sourceTree = BUILT_PRODUCTS_DIR; };
70 | C37CEE7E245D65F000473301 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
71 | C37CEE80245D65F000473301 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
72 | C37CEE84245D65FA00473301 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
73 | C37CEE87245D65FA00473301 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; };
74 | C37CEE8A245D65FA00473301 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
75 | C37CEE8C245D65FA00473301 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
76 | C37CEE91245D65FB00473301 /* TemperaturaTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TemperaturaTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
77 | C37CEE95245D65FB00473301 /* TemperaturaTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperaturaTests.swift; sourceTree = ""; };
78 | C37CEE97245D65FB00473301 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
79 | C37CEE9C245D65FB00473301 /* TemperaturaUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TemperaturaUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
80 | C37CEEA0245D65FB00473301 /* TemperaturaUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemperaturaUITests.swift; sourceTree = ""; };
81 | C37CEEA2245D65FB00473301 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
82 | C390168F246E77CA000086AD /* EnvironmentValues+ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+ImageCache.swift"; sourceTree = ""; };
83 | C3901690246E77CA000086AD /* ImageLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageLoader.swift; sourceTree = ""; };
84 | C3901691246E77CA000086AD /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; };
85 | C3901692246E77CB000086AD /* AsyncImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncImage.swift; sourceTree = ""; };
86 | C3901697246E7B5D000086AD /* ImageUrl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageUrl.swift; sourceTree = ""; };
87 | C3901699246E8D53000086AD /* ForecastDetails.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForecastDetails.swift; sourceTree = ""; };
88 | C3941A6B24710B480087FD2C /* Home.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Home.swift; sourceTree = ""; };
89 | C39DBC002464F6DE002659C4 /* HomeTab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomeTab.swift; sourceTree = ""; };
90 | C39DBC022464F7EB002659C4 /* Forecast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Forecast.swift; sourceTree = ""; };
91 | C3A08CD4245D8377002FC9FF /* WeatherModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherModel.swift; sourceTree = ""; };
92 | C3A08CD6245D9850002FC9FF /* WeatherViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherViewModel.swift; sourceTree = ""; };
93 | C3A08CD8245DCA4F002FC9FF /* WeatherService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherService.swift; sourceTree = ""; };
94 | C3CFBD95245FADFE00194D42 /* ActivityIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityIndicator.swift; sourceTree = ""; };
95 | C3D72B212467F074006397A4 /* Temperatura.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Temperatura.entitlements; sourceTree = ""; };
96 | C3D72B232467F074006397A4 /* HealthKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = HealthKit.framework; path = System/Library/Frameworks/HealthKit.framework; sourceTree = SDKROOT; };
97 | /* End PBXFileReference section */
98 |
99 | /* Begin PBXFrameworksBuildPhase section */
100 | C37CEE78245D65F000473301 /* Frameworks */ = {
101 | isa = PBXFrameworksBuildPhase;
102 | buildActionMask = 2147483647;
103 | files = (
104 | C3D72B242467F075006397A4 /* HealthKit.framework in Frameworks */,
105 | );
106 | runOnlyForDeploymentPostprocessing = 0;
107 | };
108 | C37CEE8E245D65FB00473301 /* Frameworks */ = {
109 | isa = PBXFrameworksBuildPhase;
110 | buildActionMask = 2147483647;
111 | files = (
112 | );
113 | runOnlyForDeploymentPostprocessing = 0;
114 | };
115 | C37CEE99245D65FB00473301 /* Frameworks */ = {
116 | isa = PBXFrameworksBuildPhase;
117 | buildActionMask = 2147483647;
118 | files = (
119 | );
120 | runOnlyForDeploymentPostprocessing = 0;
121 | };
122 | /* End PBXFrameworksBuildPhase section */
123 |
124 | /* Begin PBXGroup section */
125 | C319D45E2465388700D8C576 /* Managers */ = {
126 | isa = PBXGroup;
127 | children = (
128 | C319D45F2465389400D8C576 /* LocationManager.swift */,
129 | );
130 | path = Managers;
131 | sourceTree = "";
132 | };
133 | C35ECD4624614060001E3A65 /* Partials */ = {
134 | isa = PBXGroup;
135 | children = (
136 | C35ECD4724614081001E3A65 /* WeatherPartials.swift */,
137 | C35ECD4B24615F21001E3A65 /* OtherDetails.swift */,
138 | C3114579246BA0BE0000CA03 /* ListView.swift */,
139 | C37CB162246CCFD400F6537D /* GridView.swift */,
140 | );
141 | path = Partials;
142 | sourceTree = "";
143 | };
144 | C35ECD4D2461B1BA001E3A65 /* Screens */ = {
145 | isa = PBXGroup;
146 | children = (
147 | C39DBC022464F7EB002659C4 /* Forecast.swift */,
148 | C3901699246E8D53000086AD /* ForecastDetails.swift */,
149 | C3941A6B24710B480087FD2C /* Home.swift */,
150 | );
151 | path = Screens;
152 | sourceTree = "";
153 | };
154 | C37CEE72245D65F000473301 = {
155 | isa = PBXGroup;
156 | children = (
157 | C37CEE7D245D65F000473301 /* Temperatura */,
158 | C37CEE94245D65FB00473301 /* TemperaturaTests */,
159 | C37CEE9F245D65FB00473301 /* TemperaturaUITests */,
160 | C37CEE7C245D65F000473301 /* Products */,
161 | C3D72B222467F074006397A4 /* Frameworks */,
162 | );
163 | sourceTree = "";
164 | };
165 | C37CEE7C245D65F000473301 /* Products */ = {
166 | isa = PBXGroup;
167 | children = (
168 | C37CEE7B245D65F000473301 /* Temperatura.app */,
169 | C37CEE91245D65FB00473301 /* TemperaturaTests.xctest */,
170 | C37CEE9C245D65FB00473301 /* TemperaturaUITests.xctest */,
171 | );
172 | name = Products;
173 | sourceTree = "";
174 | };
175 | C37CEE7D245D65F000473301 /* Temperatura */ = {
176 | isa = PBXGroup;
177 | children = (
178 | C3D72B212467F074006397A4 /* Temperatura.entitlements */,
179 | C319D45E2465388700D8C576 /* Managers */,
180 | C35ECD4D2461B1BA001E3A65 /* Screens */,
181 | C35ECD4624614060001E3A65 /* Partials */,
182 | C3CFBD91245FAD3000194D42 /* Helpers */,
183 | C3A08CD3245D8333002FC9FF /* Models */,
184 | C3A08CD2245D832B002FC9FF /* Services */,
185 | C3A08CD1245D8325002FC9FF /* View Models */,
186 | C37CEE7E245D65F000473301 /* AppDelegate.swift */,
187 | C37CEE80245D65F000473301 /* SceneDelegate.swift */,
188 | C39DBC002464F6DE002659C4 /* HomeTab.swift */,
189 | C37CEE84245D65FA00473301 /* Assets.xcassets */,
190 | C37CEE89245D65FA00473301 /* LaunchScreen.storyboard */,
191 | C37CEE8C245D65FA00473301 /* Info.plist */,
192 | C37CEE86245D65FA00473301 /* Preview Content */,
193 | );
194 | path = Temperatura;
195 | sourceTree = "";
196 | };
197 | C37CEE86245D65FA00473301 /* Preview Content */ = {
198 | isa = PBXGroup;
199 | children = (
200 | C37CEE87245D65FA00473301 /* Preview Assets.xcassets */,
201 | );
202 | path = "Preview Content";
203 | sourceTree = "";
204 | };
205 | C37CEE94245D65FB00473301 /* TemperaturaTests */ = {
206 | isa = PBXGroup;
207 | children = (
208 | C37CEE95245D65FB00473301 /* TemperaturaTests.swift */,
209 | C37CEE97245D65FB00473301 /* Info.plist */,
210 | );
211 | path = TemperaturaTests;
212 | sourceTree = "";
213 | };
214 | C37CEE9F245D65FB00473301 /* TemperaturaUITests */ = {
215 | isa = PBXGroup;
216 | children = (
217 | C37CEEA0245D65FB00473301 /* TemperaturaUITests.swift */,
218 | C37CEEA2245D65FB00473301 /* Info.plist */,
219 | );
220 | path = TemperaturaUITests;
221 | sourceTree = "";
222 | };
223 | C3A08CD1245D8325002FC9FF /* View Models */ = {
224 | isa = PBXGroup;
225 | children = (
226 | C3A08CD6245D9850002FC9FF /* WeatherViewModel.swift */,
227 | C37CB166246D0CF900F6537D /* ForecastViewModel.swift */,
228 | );
229 | path = "View Models";
230 | sourceTree = "";
231 | };
232 | C3A08CD2245D832B002FC9FF /* Services */ = {
233 | isa = PBXGroup;
234 | children = (
235 | C3A08CD8245DCA4F002FC9FF /* WeatherService.swift */,
236 | C37CB164246D0C3F00F6537D /* ForecastService.swift */,
237 | );
238 | path = Services;
239 | sourceTree = "";
240 | };
241 | C3A08CD3245D8333002FC9FF /* Models */ = {
242 | isa = PBXGroup;
243 | children = (
244 | C3A08CD4245D8377002FC9FF /* WeatherModel.swift */,
245 | C35ECD44246114C7001E3A65 /* ForecastModel.swift */,
246 | );
247 | path = Models;
248 | sourceTree = "";
249 | };
250 | C3CFBD91245FAD3000194D42 /* Helpers */ = {
251 | isa = PBXGroup;
252 | children = (
253 | C3901697246E7B5D000086AD /* ImageUrl.swift */,
254 | C3901692246E77CB000086AD /* AsyncImage.swift */,
255 | C390168F246E77CA000086AD /* EnvironmentValues+ImageCache.swift */,
256 | C3901691246E77CA000086AD /* ImageCache.swift */,
257 | C3901690246E77CA000086AD /* ImageLoader.swift */,
258 | C3CFBD95245FADFE00194D42 /* ActivityIndicator.swift */,
259 | C319D46124657A3400D8C576 /* Constants.swift */,
260 | );
261 | path = Helpers;
262 | sourceTree = "";
263 | };
264 | C3D72B222467F074006397A4 /* Frameworks */ = {
265 | isa = PBXGroup;
266 | children = (
267 | C3D72B232467F074006397A4 /* HealthKit.framework */,
268 | );
269 | name = Frameworks;
270 | sourceTree = "";
271 | };
272 | /* End PBXGroup section */
273 |
274 | /* Begin PBXNativeTarget section */
275 | C37CEE7A245D65F000473301 /* Temperatura */ = {
276 | isa = PBXNativeTarget;
277 | buildConfigurationList = C37CEEA5245D65FB00473301 /* Build configuration list for PBXNativeTarget "Temperatura" */;
278 | buildPhases = (
279 | C37CEE77245D65F000473301 /* Sources */,
280 | C37CEE78245D65F000473301 /* Frameworks */,
281 | C37CEE79245D65F000473301 /* Resources */,
282 | );
283 | buildRules = (
284 | );
285 | dependencies = (
286 | );
287 | name = Temperatura;
288 | packageProductDependencies = (
289 | );
290 | productName = Temperatura;
291 | productReference = C37CEE7B245D65F000473301 /* Temperatura.app */;
292 | productType = "com.apple.product-type.application";
293 | };
294 | C37CEE90245D65FB00473301 /* TemperaturaTests */ = {
295 | isa = PBXNativeTarget;
296 | buildConfigurationList = C37CEEA8245D65FB00473301 /* Build configuration list for PBXNativeTarget "TemperaturaTests" */;
297 | buildPhases = (
298 | C37CEE8D245D65FB00473301 /* Sources */,
299 | C37CEE8E245D65FB00473301 /* Frameworks */,
300 | C37CEE8F245D65FB00473301 /* Resources */,
301 | );
302 | buildRules = (
303 | );
304 | dependencies = (
305 | C37CEE93245D65FB00473301 /* PBXTargetDependency */,
306 | );
307 | name = TemperaturaTests;
308 | productName = TemperaturaTests;
309 | productReference = C37CEE91245D65FB00473301 /* TemperaturaTests.xctest */;
310 | productType = "com.apple.product-type.bundle.unit-test";
311 | };
312 | C37CEE9B245D65FB00473301 /* TemperaturaUITests */ = {
313 | isa = PBXNativeTarget;
314 | buildConfigurationList = C37CEEAB245D65FB00473301 /* Build configuration list for PBXNativeTarget "TemperaturaUITests" */;
315 | buildPhases = (
316 | C37CEE98245D65FB00473301 /* Sources */,
317 | C37CEE99245D65FB00473301 /* Frameworks */,
318 | C37CEE9A245D65FB00473301 /* Resources */,
319 | );
320 | buildRules = (
321 | );
322 | dependencies = (
323 | C37CEE9E245D65FB00473301 /* PBXTargetDependency */,
324 | );
325 | name = TemperaturaUITests;
326 | productName = TemperaturaUITests;
327 | productReference = C37CEE9C245D65FB00473301 /* TemperaturaUITests.xctest */;
328 | productType = "com.apple.product-type.bundle.ui-testing";
329 | };
330 | /* End PBXNativeTarget section */
331 |
332 | /* Begin PBXProject section */
333 | C37CEE73245D65F000473301 /* Project object */ = {
334 | isa = PBXProject;
335 | attributes = {
336 | LastSwiftUpdateCheck = 1140;
337 | LastUpgradeCheck = 1140;
338 | ORGANIZATIONNAME = "Glenn Raya";
339 | TargetAttributes = {
340 | C37CEE7A245D65F000473301 = {
341 | CreatedOnToolsVersion = 11.4.1;
342 | };
343 | C37CEE90245D65FB00473301 = {
344 | CreatedOnToolsVersion = 11.4.1;
345 | TestTargetID = C37CEE7A245D65F000473301;
346 | };
347 | C37CEE9B245D65FB00473301 = {
348 | CreatedOnToolsVersion = 11.4.1;
349 | TestTargetID = C37CEE7A245D65F000473301;
350 | };
351 | };
352 | };
353 | buildConfigurationList = C37CEE76245D65F000473301 /* Build configuration list for PBXProject "Temperatura" */;
354 | compatibilityVersion = "Xcode 9.3";
355 | developmentRegion = en;
356 | hasScannedForEncodings = 0;
357 | knownRegions = (
358 | en,
359 | Base,
360 | );
361 | mainGroup = C37CEE72245D65F000473301;
362 | packageReferences = (
363 | );
364 | productRefGroup = C37CEE7C245D65F000473301 /* Products */;
365 | projectDirPath = "";
366 | projectRoot = "";
367 | targets = (
368 | C37CEE7A245D65F000473301 /* Temperatura */,
369 | C37CEE90245D65FB00473301 /* TemperaturaTests */,
370 | C37CEE9B245D65FB00473301 /* TemperaturaUITests */,
371 | );
372 | };
373 | /* End PBXProject section */
374 |
375 | /* Begin PBXResourcesBuildPhase section */
376 | C37CEE79245D65F000473301 /* Resources */ = {
377 | isa = PBXResourcesBuildPhase;
378 | buildActionMask = 2147483647;
379 | files = (
380 | C37CEE8B245D65FA00473301 /* LaunchScreen.storyboard in Resources */,
381 | C37CEE88245D65FA00473301 /* Preview Assets.xcassets in Resources */,
382 | C37CEE85245D65FA00473301 /* Assets.xcassets in Resources */,
383 | );
384 | runOnlyForDeploymentPostprocessing = 0;
385 | };
386 | C37CEE8F245D65FB00473301 /* Resources */ = {
387 | isa = PBXResourcesBuildPhase;
388 | buildActionMask = 2147483647;
389 | files = (
390 | );
391 | runOnlyForDeploymentPostprocessing = 0;
392 | };
393 | C37CEE9A245D65FB00473301 /* Resources */ = {
394 | isa = PBXResourcesBuildPhase;
395 | buildActionMask = 2147483647;
396 | files = (
397 | );
398 | runOnlyForDeploymentPostprocessing = 0;
399 | };
400 | /* End PBXResourcesBuildPhase section */
401 |
402 | /* Begin PBXSourcesBuildPhase section */
403 | C37CEE77245D65F000473301 /* Sources */ = {
404 | isa = PBXSourcesBuildPhase;
405 | buildActionMask = 2147483647;
406 | files = (
407 | C3901695246E77CC000086AD /* ImageCache.swift in Sources */,
408 | C37CB163246CCFD400F6537D /* GridView.swift in Sources */,
409 | C3901696246E77CC000086AD /* AsyncImage.swift in Sources */,
410 | C3A08CD5245D8377002FC9FF /* WeatherModel.swift in Sources */,
411 | C35ECD4824614081001E3A65 /* WeatherPartials.swift in Sources */,
412 | C37CB167246D0CF900F6537D /* ForecastViewModel.swift in Sources */,
413 | C3901693246E77CB000086AD /* EnvironmentValues+ImageCache.swift in Sources */,
414 | C319D4602465389400D8C576 /* LocationManager.swift in Sources */,
415 | C39DBC032464F7EB002659C4 /* Forecast.swift in Sources */,
416 | C3A08CD9245DCA4F002FC9FF /* WeatherService.swift in Sources */,
417 | C39DBC012464F6DE002659C4 /* HomeTab.swift in Sources */,
418 | C37CB165246D0C3F00F6537D /* ForecastService.swift in Sources */,
419 | C3901698246E7B5D000086AD /* ImageUrl.swift in Sources */,
420 | C37CEE7F245D65F000473301 /* AppDelegate.swift in Sources */,
421 | C311457A246BA0BE0000CA03 /* ListView.swift in Sources */,
422 | C319D46224657A3400D8C576 /* Constants.swift in Sources */,
423 | C3901694246E77CB000086AD /* ImageLoader.swift in Sources */,
424 | C37CEE81245D65F000473301 /* SceneDelegate.swift in Sources */,
425 | C35ECD4C24615F21001E3A65 /* OtherDetails.swift in Sources */,
426 | C3CFBD98245FADFE00194D42 /* ActivityIndicator.swift in Sources */,
427 | C390169A246E8D53000086AD /* ForecastDetails.swift in Sources */,
428 | C35ECD45246114C7001E3A65 /* ForecastModel.swift in Sources */,
429 | C3941A6C24710B480087FD2C /* Home.swift in Sources */,
430 | C3A08CD7245D9850002FC9FF /* WeatherViewModel.swift in Sources */,
431 | );
432 | runOnlyForDeploymentPostprocessing = 0;
433 | };
434 | C37CEE8D245D65FB00473301 /* Sources */ = {
435 | isa = PBXSourcesBuildPhase;
436 | buildActionMask = 2147483647;
437 | files = (
438 | C37CEE96245D65FB00473301 /* TemperaturaTests.swift in Sources */,
439 | );
440 | runOnlyForDeploymentPostprocessing = 0;
441 | };
442 | C37CEE98245D65FB00473301 /* Sources */ = {
443 | isa = PBXSourcesBuildPhase;
444 | buildActionMask = 2147483647;
445 | files = (
446 | C37CEEA1245D65FB00473301 /* TemperaturaUITests.swift in Sources */,
447 | );
448 | runOnlyForDeploymentPostprocessing = 0;
449 | };
450 | /* End PBXSourcesBuildPhase section */
451 |
452 | /* Begin PBXTargetDependency section */
453 | C37CEE93245D65FB00473301 /* PBXTargetDependency */ = {
454 | isa = PBXTargetDependency;
455 | target = C37CEE7A245D65F000473301 /* Temperatura */;
456 | targetProxy = C37CEE92245D65FB00473301 /* PBXContainerItemProxy */;
457 | };
458 | C37CEE9E245D65FB00473301 /* PBXTargetDependency */ = {
459 | isa = PBXTargetDependency;
460 | target = C37CEE7A245D65F000473301 /* Temperatura */;
461 | targetProxy = C37CEE9D245D65FB00473301 /* PBXContainerItemProxy */;
462 | };
463 | /* End PBXTargetDependency section */
464 |
465 | /* Begin PBXVariantGroup section */
466 | C37CEE89245D65FA00473301 /* LaunchScreen.storyboard */ = {
467 | isa = PBXVariantGroup;
468 | children = (
469 | C37CEE8A245D65FA00473301 /* Base */,
470 | );
471 | name = LaunchScreen.storyboard;
472 | sourceTree = "";
473 | };
474 | /* End PBXVariantGroup section */
475 |
476 | /* Begin XCBuildConfiguration section */
477 | C37CEEA3245D65FB00473301 /* Debug */ = {
478 | isa = XCBuildConfiguration;
479 | buildSettings = {
480 | ALWAYS_SEARCH_USER_PATHS = NO;
481 | CLANG_ANALYZER_NONNULL = YES;
482 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
483 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
484 | CLANG_CXX_LIBRARY = "libc++";
485 | CLANG_ENABLE_MODULES = YES;
486 | CLANG_ENABLE_OBJC_ARC = YES;
487 | CLANG_ENABLE_OBJC_WEAK = YES;
488 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
489 | CLANG_WARN_BOOL_CONVERSION = YES;
490 | CLANG_WARN_COMMA = YES;
491 | CLANG_WARN_CONSTANT_CONVERSION = YES;
492 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
493 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
494 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
495 | CLANG_WARN_EMPTY_BODY = YES;
496 | CLANG_WARN_ENUM_CONVERSION = YES;
497 | CLANG_WARN_INFINITE_RECURSION = YES;
498 | CLANG_WARN_INT_CONVERSION = YES;
499 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
500 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
501 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
502 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
503 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
504 | CLANG_WARN_STRICT_PROTOTYPES = YES;
505 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
506 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
507 | CLANG_WARN_UNREACHABLE_CODE = YES;
508 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
509 | COPY_PHASE_STRIP = NO;
510 | DEBUG_INFORMATION_FORMAT = dwarf;
511 | ENABLE_STRICT_OBJC_MSGSEND = YES;
512 | ENABLE_TESTABILITY = YES;
513 | GCC_C_LANGUAGE_STANDARD = gnu11;
514 | GCC_DYNAMIC_NO_PIC = NO;
515 | GCC_NO_COMMON_BLOCKS = YES;
516 | GCC_OPTIMIZATION_LEVEL = 0;
517 | GCC_PREPROCESSOR_DEFINITIONS = (
518 | "DEBUG=1",
519 | "$(inherited)",
520 | );
521 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
522 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
523 | GCC_WARN_UNDECLARED_SELECTOR = YES;
524 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
525 | GCC_WARN_UNUSED_FUNCTION = YES;
526 | GCC_WARN_UNUSED_VARIABLE = YES;
527 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
528 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
529 | MTL_FAST_MATH = YES;
530 | ONLY_ACTIVE_ARCH = YES;
531 | SDKROOT = iphoneos;
532 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
533 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
534 | };
535 | name = Debug;
536 | };
537 | C37CEEA4245D65FB00473301 /* Release */ = {
538 | isa = XCBuildConfiguration;
539 | buildSettings = {
540 | ALWAYS_SEARCH_USER_PATHS = NO;
541 | CLANG_ANALYZER_NONNULL = YES;
542 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
543 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
544 | CLANG_CXX_LIBRARY = "libc++";
545 | CLANG_ENABLE_MODULES = YES;
546 | CLANG_ENABLE_OBJC_ARC = YES;
547 | CLANG_ENABLE_OBJC_WEAK = YES;
548 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
549 | CLANG_WARN_BOOL_CONVERSION = YES;
550 | CLANG_WARN_COMMA = YES;
551 | CLANG_WARN_CONSTANT_CONVERSION = YES;
552 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
553 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
554 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
555 | CLANG_WARN_EMPTY_BODY = YES;
556 | CLANG_WARN_ENUM_CONVERSION = YES;
557 | CLANG_WARN_INFINITE_RECURSION = YES;
558 | CLANG_WARN_INT_CONVERSION = YES;
559 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
560 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
561 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
562 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
563 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
564 | CLANG_WARN_STRICT_PROTOTYPES = YES;
565 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
566 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
567 | CLANG_WARN_UNREACHABLE_CODE = YES;
568 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
569 | COPY_PHASE_STRIP = NO;
570 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
571 | ENABLE_NS_ASSERTIONS = NO;
572 | ENABLE_STRICT_OBJC_MSGSEND = YES;
573 | GCC_C_LANGUAGE_STANDARD = gnu11;
574 | GCC_NO_COMMON_BLOCKS = YES;
575 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
576 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
577 | GCC_WARN_UNDECLARED_SELECTOR = YES;
578 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
579 | GCC_WARN_UNUSED_FUNCTION = YES;
580 | GCC_WARN_UNUSED_VARIABLE = YES;
581 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
582 | MTL_ENABLE_DEBUG_INFO = NO;
583 | MTL_FAST_MATH = YES;
584 | SDKROOT = iphoneos;
585 | SWIFT_COMPILATION_MODE = wholemodule;
586 | SWIFT_OPTIMIZATION_LEVEL = "-O";
587 | VALIDATE_PRODUCT = YES;
588 | };
589 | name = Release;
590 | };
591 | C37CEEA6245D65FB00473301 /* Debug */ = {
592 | isa = XCBuildConfiguration;
593 | buildSettings = {
594 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
595 | CODE_SIGN_ENTITLEMENTS = Temperatura/Temperatura.entitlements;
596 | CODE_SIGN_STYLE = Automatic;
597 | DEVELOPMENT_ASSET_PATHS = "\"Temperatura/Preview Content\"";
598 | DEVELOPMENT_TEAM = HWG7293UWD;
599 | ENABLE_PREVIEWS = YES;
600 | INFOPLIST_FILE = Temperatura/Info.plist;
601 | LD_RUNPATH_SEARCH_PATHS = (
602 | "$(inherited)",
603 | "@executable_path/Frameworks",
604 | );
605 | PRODUCT_BUNDLE_IDENTIFIER = com.toostrong.Temperatura;
606 | PRODUCT_NAME = "$(TARGET_NAME)";
607 | SWIFT_VERSION = 5.0;
608 | TARGETED_DEVICE_FAMILY = "1,2";
609 | };
610 | name = Debug;
611 | };
612 | C37CEEA7245D65FB00473301 /* Release */ = {
613 | isa = XCBuildConfiguration;
614 | buildSettings = {
615 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
616 | CODE_SIGN_ENTITLEMENTS = Temperatura/Temperatura.entitlements;
617 | CODE_SIGN_STYLE = Automatic;
618 | DEVELOPMENT_ASSET_PATHS = "\"Temperatura/Preview Content\"";
619 | DEVELOPMENT_TEAM = HWG7293UWD;
620 | ENABLE_PREVIEWS = YES;
621 | INFOPLIST_FILE = Temperatura/Info.plist;
622 | LD_RUNPATH_SEARCH_PATHS = (
623 | "$(inherited)",
624 | "@executable_path/Frameworks",
625 | );
626 | PRODUCT_BUNDLE_IDENTIFIER = com.toostrong.Temperatura;
627 | PRODUCT_NAME = "$(TARGET_NAME)";
628 | SWIFT_VERSION = 5.0;
629 | TARGETED_DEVICE_FAMILY = "1,2";
630 | };
631 | name = Release;
632 | };
633 | C37CEEA9245D65FB00473301 /* Debug */ = {
634 | isa = XCBuildConfiguration;
635 | buildSettings = {
636 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
637 | BUNDLE_LOADER = "$(TEST_HOST)";
638 | CODE_SIGN_STYLE = Automatic;
639 | DEVELOPMENT_TEAM = HWG7293UWD;
640 | INFOPLIST_FILE = TemperaturaTests/Info.plist;
641 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
642 | LD_RUNPATH_SEARCH_PATHS = (
643 | "$(inherited)",
644 | "@executable_path/Frameworks",
645 | "@loader_path/Frameworks",
646 | );
647 | PRODUCT_BUNDLE_IDENTIFIER = com.toostrong.TemperaturaTests;
648 | PRODUCT_NAME = "$(TARGET_NAME)";
649 | SWIFT_VERSION = 5.0;
650 | TARGETED_DEVICE_FAMILY = "1,2";
651 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Temperatura.app/Temperatura";
652 | };
653 | name = Debug;
654 | };
655 | C37CEEAA245D65FB00473301 /* Release */ = {
656 | isa = XCBuildConfiguration;
657 | buildSettings = {
658 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
659 | BUNDLE_LOADER = "$(TEST_HOST)";
660 | CODE_SIGN_STYLE = Automatic;
661 | DEVELOPMENT_TEAM = HWG7293UWD;
662 | INFOPLIST_FILE = TemperaturaTests/Info.plist;
663 | IPHONEOS_DEPLOYMENT_TARGET = 13.4;
664 | LD_RUNPATH_SEARCH_PATHS = (
665 | "$(inherited)",
666 | "@executable_path/Frameworks",
667 | "@loader_path/Frameworks",
668 | );
669 | PRODUCT_BUNDLE_IDENTIFIER = com.toostrong.TemperaturaTests;
670 | PRODUCT_NAME = "$(TARGET_NAME)";
671 | SWIFT_VERSION = 5.0;
672 | TARGETED_DEVICE_FAMILY = "1,2";
673 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Temperatura.app/Temperatura";
674 | };
675 | name = Release;
676 | };
677 | C37CEEAC245D65FB00473301 /* Debug */ = {
678 | isa = XCBuildConfiguration;
679 | buildSettings = {
680 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
681 | CODE_SIGN_STYLE = Automatic;
682 | DEVELOPMENT_TEAM = HWG7293UWD;
683 | INFOPLIST_FILE = TemperaturaUITests/Info.plist;
684 | LD_RUNPATH_SEARCH_PATHS = (
685 | "$(inherited)",
686 | "@executable_path/Frameworks",
687 | "@loader_path/Frameworks",
688 | );
689 | PRODUCT_BUNDLE_IDENTIFIER = com.toostrong.TemperaturaUITests;
690 | PRODUCT_NAME = "$(TARGET_NAME)";
691 | SWIFT_VERSION = 5.0;
692 | TARGETED_DEVICE_FAMILY = "1,2";
693 | TEST_TARGET_NAME = Temperatura;
694 | };
695 | name = Debug;
696 | };
697 | C37CEEAD245D65FB00473301 /* Release */ = {
698 | isa = XCBuildConfiguration;
699 | buildSettings = {
700 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
701 | CODE_SIGN_STYLE = Automatic;
702 | DEVELOPMENT_TEAM = HWG7293UWD;
703 | INFOPLIST_FILE = TemperaturaUITests/Info.plist;
704 | LD_RUNPATH_SEARCH_PATHS = (
705 | "$(inherited)",
706 | "@executable_path/Frameworks",
707 | "@loader_path/Frameworks",
708 | );
709 | PRODUCT_BUNDLE_IDENTIFIER = com.toostrong.TemperaturaUITests;
710 | PRODUCT_NAME = "$(TARGET_NAME)";
711 | SWIFT_VERSION = 5.0;
712 | TARGETED_DEVICE_FAMILY = "1,2";
713 | TEST_TARGET_NAME = Temperatura;
714 | };
715 | name = Release;
716 | };
717 | /* End XCBuildConfiguration section */
718 |
719 | /* Begin XCConfigurationList section */
720 | C37CEE76245D65F000473301 /* Build configuration list for PBXProject "Temperatura" */ = {
721 | isa = XCConfigurationList;
722 | buildConfigurations = (
723 | C37CEEA3245D65FB00473301 /* Debug */,
724 | C37CEEA4245D65FB00473301 /* Release */,
725 | );
726 | defaultConfigurationIsVisible = 0;
727 | defaultConfigurationName = Release;
728 | };
729 | C37CEEA5245D65FB00473301 /* Build configuration list for PBXNativeTarget "Temperatura" */ = {
730 | isa = XCConfigurationList;
731 | buildConfigurations = (
732 | C37CEEA6245D65FB00473301 /* Debug */,
733 | C37CEEA7245D65FB00473301 /* Release */,
734 | );
735 | defaultConfigurationIsVisible = 0;
736 | defaultConfigurationName = Release;
737 | };
738 | C37CEEA8245D65FB00473301 /* Build configuration list for PBXNativeTarget "TemperaturaTests" */ = {
739 | isa = XCConfigurationList;
740 | buildConfigurations = (
741 | C37CEEA9245D65FB00473301 /* Debug */,
742 | C37CEEAA245D65FB00473301 /* Release */,
743 | );
744 | defaultConfigurationIsVisible = 0;
745 | defaultConfigurationName = Release;
746 | };
747 | C37CEEAB245D65FB00473301 /* Build configuration list for PBXNativeTarget "TemperaturaUITests" */ = {
748 | isa = XCConfigurationList;
749 | buildConfigurations = (
750 | C37CEEAC245D65FB00473301 /* Debug */,
751 | C37CEEAD245D65FB00473301 /* Release */,
752 | );
753 | defaultConfigurationIsVisible = 0;
754 | defaultConfigurationName = Release;
755 | };
756 | /* End XCConfigurationList section */
757 | };
758 | rootObject = C37CEE73245D65F000473301 /* Project object */;
759 | }
760 |
--------------------------------------------------------------------------------