├── 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 | ![](Temperatura.jpg) 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 | --------------------------------------------------------------------------------