From 64b8304d4d77fd1c50f88c37b0f6396b32157b2e Mon Sep 17 00:00:00 2001 From: Saul Urias Date: Mon, 11 Jun 2018 14:14:17 -0700 Subject: [PATCH 1/5] updated podfile --- Podfile.lock | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Podfile.lock b/Podfile.lock index 8e3d457..e59a222 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -8,6 +8,12 @@ DEPENDENCIES: - Quick - SwiftyJSON +SPEC REPOS: + https://github.com/CocoaPods/Specs.git: + - Nimble + - Quick + - SwiftyJSON + SPEC CHECKSUMS: Nimble: 7f5a9c447a33002645a071bddafbfb24ea70e0ac Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08 @@ -15,4 +21,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 3509c4c63edbe9a7b092aab021ca4e248077b966 -COCOAPODS: 1.3.1 +COCOAPODS: 1.5.0 From 9cc34bf632a1b0822e41d9cbaad2e2e925a91352 Mon Sep 17 00:00:00 2001 From: Saul Urias Date: Mon, 11 Jun 2018 14:18:27 -0700 Subject: [PATCH 2/5] Refactor WeatherViewController --- SwiftWeather/WeatherViewController.swift | 179 ++++++++++++----------- 1 file changed, 91 insertions(+), 88 deletions(-) diff --git a/SwiftWeather/WeatherViewController.swift b/SwiftWeather/WeatherViewController.swift index 056797f..0e9926b 100644 --- a/SwiftWeather/WeatherViewController.swift +++ b/SwiftWeather/WeatherViewController.swift @@ -9,113 +9,116 @@ import MobileCoreServices //MARK: - UIViewController Properties class WeatherViewController: UIViewController { - - //MARK: - IBOutlets - @IBOutlet weak var locationLabel: UILabel! - @IBOutlet weak var iconLabel: UILabel! - @IBOutlet weak var temperatureLabel: UILabel! - @IBOutlet var forecastViews: [ForecastView]! - - let identifier = "WeatherIdentifier" - - //MARK: - Super Methods - override func viewDidLoad() { - super.viewDidLoad() - viewModel = WeatherViewModel() - viewModel?.startLocationService() - setA11yIdentifiers() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - - locationLabel.center.x -= view.bounds.width - iconLabel.center.x -= view.bounds.width - temperatureLabel.center.x -= view.bounds.width - - iconLabel.alpha = 0.0 - locationLabel.alpha = 0.0 - temperatureLabel.alpha = 0.0 - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - - UIView.animate(withDuration: 0.5, animations: { - self.locationLabel.center.x += self.view.bounds.width - }) - UIView.animate(withDuration: 0.5, delay: 0.3, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: { - self.iconLabel.center.x += self.view.bounds.width - }, completion: nil) + //MARK: - IBOutlets + @IBOutlet weak var locationLabel: UILabel! + @IBOutlet weak var iconLabel: UILabel! + @IBOutlet weak var temperatureLabel: UILabel! + @IBOutlet var forecastViews: [ForecastView]! - UIView.animate(withDuration: 0.5, delay: 0.4, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: { - self.temperatureLabel.center.x += self.view.bounds.width - }, completion: nil) + let identifier = "WeatherIdentifier" + //MARK: - Super Methods + override func viewDidLoad() { + super.viewDidLoad() + viewModel = WeatherViewModel() + viewModel?.startLocationService() + setA11yIdentifiers() + } - UIView.animate(withDuration: 0.5, delay: 0.3, options: [], animations: { - self.iconLabel.alpha = 1.0 - }, completion: nil) - - UIView.animate(withDuration: 0.5, delay: 0.4, options: [], animations: { - self.locationLabel.alpha = 1.0 - }, completion: nil) - - UIView.animate(withDuration: 0.5, delay: 0.5, options: [], animations: { - self.temperatureLabel.alpha = 1.0 - }, completion: nil) - - } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + configureLabels() + } - - // MARK: ViewModel - var viewModel: WeatherViewModel? { - didSet { - viewModel?.location.observe { - [unowned self] in - self.locationLabel.text = $0 - - let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeText as String) - attributeSet.title = self.locationLabel.text - - let item = CSSearchableItem(uniqueIdentifier: self.identifier, domainIdentifier: "com.rushjet.SwiftWeather", attributeSet: attributeSet) - CSSearchableIndex.default().indexSearchableItems([item]){error in - if let error = error { - print("Indexing error: \(error.localizedDescription)") - } else { - print("Location item successfully indexed") - } - } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + configureLabelsWithAnimation() } - viewModel?.iconText.observe { - [unowned self] in - self.iconLabel.text = $0 + //MARK: Functions + func configureLabels(){ + locationLabel.center.x -= view.bounds.width + iconLabel.center.x -= view.bounds.width + temperatureLabel.center.x -= view.bounds.width + + iconLabel.alpha = 0.0 + locationLabel.alpha = 0.0 + temperatureLabel.alpha = 0.0 } - viewModel?.temperature.observe { - [unowned self] in - self.temperatureLabel.text = $0 + func configureLabelsWithAnimation(){ + UIView.animate(withDuration: 0.5, animations: { + self.locationLabel.center.x += self.view.bounds.width + }) + + UIView.animate(withDuration: 0.5, delay: 0.3, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: { + self.iconLabel.center.x += self.view.bounds.width + }, completion: nil) + + UIView.animate(withDuration: 0.5, delay: 0.4, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: { + self.temperatureLabel.center.x += self.view.bounds.width + }, completion: nil) + + + UIView.animate(withDuration: 0.5, delay: 0.3, options: [], animations: { + self.iconLabel.alpha = 1.0 + }, completion: nil) + + UIView.animate(withDuration: 0.5, delay: 0.4, options: [], animations: { + self.locationLabel.alpha = 1.0 + }, completion: nil) + + UIView.animate(withDuration: 0.5, delay: 0.5, options: [], animations: { + self.temperatureLabel.alpha = 1.0 + }, completion: nil) } - viewModel?.forecasts.observe { - [unowned self] (forecastViewModels) in - if forecastViewModels.count >= 4 { - for (index, forecastView) in self.forecastViews.enumerated() { - forecastView.loadViewModel(forecastViewModels[index]) + //MARK: ViewModel + var viewModel: WeatherViewModel? { + didSet { + viewModel?.location.observe { + [unowned self] in + self.locationLabel.text = $0 + + let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeText as String) + attributeSet.title = self.locationLabel.text + + let item = CSSearchableItem(uniqueIdentifier: self.identifier, domainIdentifier: "com.rushjet.SwiftWeather", attributeSet: attributeSet) + CSSearchableIndex.default().indexSearchableItems([item]){error in + if let error = error { + print("Indexing error: \(error.localizedDescription)") + } else { + print("Location item successfully indexed") + } + } + } + + viewModel?.iconText.observe { + [unowned self] in + self.iconLabel.text = $0 + } + + viewModel?.temperature.observe { + [unowned self] in + self.temperatureLabel.text = $0 } + + viewModel?.forecasts.observe { + [unowned self] (forecastViewModels) in + if forecastViewModels.count >= 4 { + for (index, forecastView) in self.forecastViews.enumerated() { + forecastView.loadViewModel(forecastViewModels[index]) + } + } } } - } } - + //MARK: Accessibility func setA11yIdentifiers() { locationLabel.accessibilityIdentifier = "a11y_current_city" iconLabel.accessibilityIdentifier = "a11y_wheather_icon" temperatureLabel.accessibilityIdentifier = "a11y_wheather_temperature" } - - } From f98168bd54da778c03297afaaceee3c72e64c30c Mon Sep 17 00:00:00 2001 From: Saul Urias Date: Mon, 11 Jun 2018 14:24:31 -0700 Subject: [PATCH 3/5] Fixed a Unit Test ForecastDateTimeSpec --- SwiftWeather/LocationService.swift | 3 +-- SwiftWeather/OpenWeatherMapService.swift | 4 +--- SwiftWeatherTests/UnitTests/ForecastDateTimeSpec.swift | 7 ++----- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/SwiftWeather/LocationService.swift b/SwiftWeather/LocationService.swift index f857afe..add7ba2 100644 --- a/SwiftWeather/LocationService.swift +++ b/SwiftWeather/LocationService.swift @@ -18,7 +18,6 @@ class LocationService: NSObject { override init() { super.init() - locationManager.delegate = self locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters } @@ -29,7 +28,7 @@ class LocationService: NSObject { } } -// MARK: - CLLocationManagerDelegate +// MARK: - CLLocationManager Delegate extension LocationService : CLLocationManagerDelegate { func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { if let location = locations.first { diff --git a/SwiftWeather/OpenWeatherMapService.swift b/SwiftWeather/OpenWeatherMapService.swift index 09c5054..58b77ee 100644 --- a/SwiftWeather/OpenWeatherMapService.swift +++ b/SwiftWeather/OpenWeatherMapService.swift @@ -5,7 +5,6 @@ import Foundation import CoreLocation - import SwiftyJSON struct OpenWeatherMapService: WeatherServiceProtocol { @@ -48,8 +47,7 @@ struct OpenWeatherMapService: WeatherServiceProtocol { completionHandler(nil, error) return } - - print(url) + let task = session.dataTask(with: url) { (data, response, error) in // Check network error guard error == nil else { diff --git a/SwiftWeatherTests/UnitTests/ForecastDateTimeSpec.swift b/SwiftWeatherTests/UnitTests/ForecastDateTimeSpec.swift index f1976b9..b5cc1e6 100644 --- a/SwiftWeatherTests/UnitTests/ForecastDateTimeSpec.swift +++ b/SwiftWeatherTests/UnitTests/ForecastDateTimeSpec.swift @@ -8,9 +8,7 @@ import Nimble @testable import SwiftWeather class ForecastDateTimeSpec: QuickSpec { - override func spec() { - describe("#init") { it("should init with the rawDate correctly assigned") { var forecastDateTime = ForecastDateTime(1234) @@ -23,11 +21,10 @@ class ForecastDateTimeSpec: QuickSpec { describe("#shortTime") { it("should return the correct shortTime string with format HH:mm") { var forecastDateTime = ForecastDateTime(1488096060) - expect(forecastDateTime.shortTime).to(equal("19:01")) + expect(forecastDateTime.shortTime).to(equal("01:01")) forecastDateTime = ForecastDateTime(1488103200) - expect(forecastDateTime.shortTime).to(equal("21:00")) + expect(forecastDateTime.shortTime).to(equal("03:00")) } } - } } From 5f88e7889f497a5fd797fefc43d32944aa81be17 Mon Sep 17 00:00:00 2001 From: Saul Urias Date: Mon, 11 Jun 2018 17:00:32 -0700 Subject: [PATCH 4/5] Users can now share on facebook --- Podfile | 1 + Podfile.lock | 31 ++- SwiftWeather.xcodeproj/project.pbxproj | 63 ++---- .../AppIcon.appiconset/Contents.json | 30 +++ .../share.imageset/Contents.json | 21 ++ .../Assets.xcassets/share.imageset/share.png | Bin 0 -> 865 bytes SwiftWeather/Base.lproj/Main.storyboard | 104 +++++---- SwiftWeather/Info.plist | 20 ++ SwiftWeather/OpenWeatherMapService.swift | 206 +++++++++--------- SwiftWeather/WeatherViewController.swift | 96 ++++---- 10 files changed, 339 insertions(+), 233 deletions(-) create mode 100644 SwiftWeather/Assets.xcassets/share.imageset/Contents.json create mode 100644 SwiftWeather/Assets.xcassets/share.imageset/share.png diff --git a/Podfile b/Podfile index 5c0576a..5d0e718 100644 --- a/Podfile +++ b/Podfile @@ -3,6 +3,7 @@ use_frameworks! target 'SwiftWeather' do pod 'SwiftyJSON' + pod 'FacebookShare' end abstract_target 'Tests' do diff --git a/Podfile.lock b/Podfile.lock index e59a222..299b94e 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,24 +1,53 @@ PODS: + - Bolts (1.9.0): + - Bolts/AppLinks (= 1.9.0) + - Bolts/Tasks (= 1.9.0) + - Bolts/AppLinks (1.9.0): + - Bolts/Tasks + - Bolts/Tasks (1.9.0) + - FacebookCore (0.3.0): + - Bolts (~> 1.8) + - FBSDKCoreKit (~> 4.27) + - FacebookShare (0.3.0): + - Bolts (~> 1.8) + - FacebookCore (~> 0.3) + - FBSDKCoreKit (~> 4.27) + - FBSDKShareKit (~> 4.27) + - FBSDKCoreKit (4.33.0): + - Bolts (~> 1.7) + - FBSDKShareKit (4.33.0): + - FBSDKCoreKit (~> 4.33.0) - Nimble (7.0.3) - Quick (1.2.0) - SwiftyJSON (4.0.0) DEPENDENCIES: + - FacebookShare - Nimble - Quick - SwiftyJSON SPEC REPOS: https://github.com/CocoaPods/Specs.git: + - Bolts + - FacebookCore + - FacebookShare + - FBSDKCoreKit + - FBSDKShareKit - Nimble - Quick - SwiftyJSON SPEC CHECKSUMS: + Bolts: ac6567323eac61e203f6a9763667d0f711be34c8 + FacebookCore: 3ffa190a3f1f96cec0e44d3fc221bc322c595ffa + FacebookShare: 0469964297ebd75f052be2c5083389a4208e82b7 + FBSDKCoreKit: 572b047a7e029bc44542bcf8a59414e7ff2b543e + FBSDKShareKit: 1869cb24db2cea90666a50cb9d568deb38e2d16e Nimble: 7f5a9c447a33002645a071bddafbfb24ea70e0ac Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08 SwiftyJSON: 070dabdcb1beb81b247c65ffa3a79dbbfb3b48aa -PODFILE CHECKSUM: 3509c4c63edbe9a7b092aab021ca4e248077b966 +PODFILE CHECKSUM: 28f6397e3ce4b1c0258bb51e941d6155f9477e75 COCOAPODS: 1.5.0 diff --git a/SwiftWeather.xcodeproj/project.pbxproj b/SwiftWeather.xcodeproj/project.pbxproj index e66ccb1..1913c14 100644 --- a/SwiftWeather.xcodeproj/project.pbxproj +++ b/SwiftWeather.xcodeproj/project.pbxproj @@ -316,7 +316,6 @@ AECBA5E01B836BF20004A536 /* Resources */, ADD0EBFC1C562E52002D8392 /* ShellScript */, 6691608BB84CB91D5EDADE36 /* [CP] Embed Pods Frameworks */, - 32518075271F3E332F6C65A9 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -336,7 +335,6 @@ AECBA5F31B836BF20004A536 /* Frameworks */, AECBA5F41B836BF20004A536 /* Resources */, 3211EE071253F1D6513A17BA /* [CP] Embed Pods Frameworks */, - DD8CF84103252EE893788AE8 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -357,7 +355,6 @@ AECBA5FE1B836BF20004A536 /* Frameworks */, AECBA5FF1B836BF20004A536 /* Resources */, EC9ED72913B1DE182823A53C /* [CP] Embed Pods Frameworks */, - 1B9DE6A0CE065E744D7CB96E /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -381,6 +378,7 @@ TargetAttributes = { AECBA5E11B836BF20004A536 = { CreatedOnToolsVersion = 7.0; + DevelopmentTeam = 32GB2HU6K5; LastSwiftMigration = 0900; }; AECBA5F51B836BF20004A536 = { @@ -445,21 +443,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 1B9DE6A0CE065E744D7CB96E /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Tests-SwiftWeatherUITests/Pods-Tests-SwiftWeatherUITests-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 2A25B97139655BE3B502C748 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -498,21 +481,6 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Tests-SwiftWeatherTests/Pods-Tests-SwiftWeatherTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 32518075271F3E332F6C65A9 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftWeather/Pods-SwiftWeather-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; 6691608BB84CB91D5EDADE36 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -520,10 +488,20 @@ ); inputPaths = ( "${SRCROOT}/Pods/Target Support Files/Pods-SwiftWeather/Pods-SwiftWeather-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Bolts/Bolts.framework", + "${BUILT_PRODUCTS_DIR}/FBSDKCoreKit/FBSDKCoreKit.framework", + "${BUILT_PRODUCTS_DIR}/FBSDKShareKit/FBSDKShareKit.framework", + "${BUILT_PRODUCTS_DIR}/FacebookCore/FacebookCore.framework", + "${BUILT_PRODUCTS_DIR}/FacebookShare/FacebookShare.framework", "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Bolts.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSDKCoreKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSDKShareKit.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FacebookCore.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FacebookShare.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", ); runOnlyForDeploymentPostprocessing = 0; @@ -580,21 +558,6 @@ shellPath = /bin/sh; shellScript = "token_file=.access_tokens/openweathermap\ntoken=\"$(cat $token_file)\"\nif [ \"$token\" ]; then\nplutil -replace OWMAccessToken -string $token $TARGET_BUILD_DIR/$INFOPLIST_PATH\nelse\necho 'error: Missing OpenWeatherMap access token'\nopen 'http://openweathermap.org/appid'\necho \"error: Get an access token from , then create a new file at $token_file that contains the access token.\"\nexit 1\nfi"; }; - DD8CF84103252EE893788AE8 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Tests-SwiftWeatherTests/Pods-Tests-SwiftWeatherTests-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; EC9ED72913B1DE182823A53C /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -805,7 +768,7 @@ baseConfigurationReference = 900AFFBB41C69FC277CB0A33 /* Pods-SwiftWeather.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 32GB2HU6K5; INFOPLIST_FILE = SwiftWeather/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.rushjet.SwiftWeather; @@ -820,7 +783,7 @@ baseConfigurationReference = E835C39D4B929F35B6A57B12 /* Pods-SwiftWeather.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - DEVELOPMENT_TEAM = ""; + DEVELOPMENT_TEAM = 32GB2HU6K5; INFOPLIST_FILE = SwiftWeather/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.rushjet.SwiftWeather; diff --git a/SwiftWeather/Assets.xcassets/AppIcon.appiconset/Contents.json b/SwiftWeather/Assets.xcassets/AppIcon.appiconset/Contents.json index 36d2c80..d8db8d6 100644 --- a/SwiftWeather/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/SwiftWeather/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", @@ -30,6 +40,16 @@ "size" : "60x60", "scale" : "3x" }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, { "idiom" : "ipad", "size" : "29x29", @@ -59,6 +79,16 @@ "idiom" : "ipad", "size" : "76x76", "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" } ], "info" : { diff --git a/SwiftWeather/Assets.xcassets/share.imageset/Contents.json b/SwiftWeather/Assets.xcassets/share.imageset/Contents.json new file mode 100644 index 0000000..bfbe1c1 --- /dev/null +++ b/SwiftWeather/Assets.xcassets/share.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "share.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/SwiftWeather/Assets.xcassets/share.imageset/share.png b/SwiftWeather/Assets.xcassets/share.imageset/share.png new file mode 100644 index 0000000000000000000000000000000000000000..089f04a121681d651e6e17cf3b4c445d140a5943 GIT binary patch literal 865 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy%*9TgAsieWw;%dH0CG7CJR*x3 z7}$@3Fymd-NsEDkk|nMYCBgY=CFO}lsSM@i<$9TU*~Q6;1*v-ZMd`EO*+?-kF!~1g zgt-3y|DWOi|AvN!P#{>f%E1B1+;4207TVC@>6x25X`6#XfVc)UX*lq{kG&i?c z2d@MN&y=!;{cdiF>XMJ;(7yZ0SQ7>0&NGfb8EJOlz|wV^XqqHS8W0+gQ$U!aK&g0`{kE& zfT5pP666=mz{teR%)-LT#?H>c!O1NmCLtpuE2p5KsHCi}rE6|!V`t~!?in4El$?>3 zm6KOcSX5e8RnyZqY0BJ%i`0>-{FJHcX`~KtSuRnkPS=RHt0Y)j0r;B5V#p&dP1AJHREIRaNiDUtz+HV^H zkMQG54=C_61sy5W=xEV8VSHFmpJ(#<&j;2y2(3FHy_;zs%M2#(HJ?^3tPA+*6RC44 zN$+A*j9kLcm|CAqzso7wb!=>6lPAm(;a6?3kiJtAU#T&9I;WoYbFryU^EsY$TONO9 zlss$IWZrET=AJjX8pY7PIK^mzA0J=9fmfQ`Gv+^z+}PeFZgWFrgZ76OZGoff4#=p< zAMTB943P?H^xvUXCMp$~P#Mm**Fs%zjaG`Ra*&NlBah>473H!w%M6?{pDu84J+r`# z?aeVyGo}b7ZcYx%BUz6-^8^A8&QEA)P#3J9q&z2sQ?0s5vc2G%@{G!fo9r3%bn`WL TeXX+q#t4I_tDnm{r-UW|5PoW{ literal 0 HcmV?d00001 diff --git a/SwiftWeather/Base.lproj/Main.storyboard b/SwiftWeather/Base.lproj/Main.storyboard index e7602ea..c0da392 100644 --- a/SwiftWeather/Base.lproj/Main.storyboard +++ b/SwiftWeather/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -15,7 +15,7 @@ - + @@ -33,40 +33,6 @@ - - - - - - - - - - - - - - - - - - - @@ -146,6 +112,40 @@ + + + + + + + + + + + + + + + + + + + @@ -156,7 +156,7 @@ - + @@ -203,6 +203,13 @@ + + + + + + + @@ -215,9 +222,30 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/SwiftWeather/Info.plist b/SwiftWeather/Info.plist index 2618193..5b2d434 100644 --- a/SwiftWeather/Info.plist +++ b/SwiftWeather/Info.plist @@ -54,5 +54,25 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + CFBundleURLTypes + + + CFBundleURLSchemes + + fb1725220270856995 + + + + FacebookAppID + 1725220270856995 + FacebookDisplayName + Swift Weather + LSApplicationQueriesSchemes + + fbapi + fb-messenger-share-api + fbauth2 + fbshareextension + diff --git a/SwiftWeather/OpenWeatherMapService.swift b/SwiftWeather/OpenWeatherMapService.swift index 58b77ee..b8de361 100644 --- a/SwiftWeather/OpenWeatherMapService.swift +++ b/SwiftWeather/OpenWeatherMapService.swift @@ -8,111 +8,111 @@ import CoreLocation import SwiftyJSON struct OpenWeatherMapService: WeatherServiceProtocol { - fileprivate let urlPath = "http://api.openweathermap.org/data/2.5/forecast" - - fileprivate func getFirstFourForecasts(_ json: JSON) -> [Forecast] { - var forecasts: [Forecast] = [] - - for index in 0...3 { - guard let forecastTempDegrees = json["list"][index]["main"]["temp"].double, - let rawDateTime = json["list"][index]["dt"].double, - let forecastCondition = json["list"][index]["weather"][0]["id"].int, - let forecastIcon = json["list"][index]["weather"][0]["icon"].string else { - break - } - - let country = json["city"]["country"].string - let forecastTemperature = Temperature(country: country!, - openWeatherMapDegrees: forecastTempDegrees) - let forecastTimeString = ForecastDateTime(rawDateTime).shortTime - let weatherIcon = WeatherIcon(condition: forecastCondition, iconString: forecastIcon) - let forcastIconText = weatherIcon.iconText - - let forecast = Forecast(time: forecastTimeString, - iconText: forcastIconText, - temperature: forecastTemperature.degrees) - - forecasts.append(forecast) - } - - return forecasts - } - - func retrieveWeatherInfo(_ location: CLLocation, completionHandler: @escaping WeatherCompletionHandler) { - let sessionConfig = URLSessionConfiguration.default - let session = URLSession(configuration: sessionConfig) - - guard let url = generateRequestURL(location) else { - let error = SWError(errorCode: .urlError) - completionHandler(nil, error) - return + fileprivate let urlPath = "http://api.openweathermap.org/data/2.5/forecast" + + fileprivate func getFirstFourForecasts(_ json: JSON) -> [Forecast] { + var forecasts: [Forecast] = [] + + for index in 0...3 { + guard let forecastTempDegrees = json["list"][index]["main"]["temp"].double, + let rawDateTime = json["list"][index]["dt"].double, + let forecastCondition = json["list"][index]["weather"][0]["id"].int, + let forecastIcon = json["list"][index]["weather"][0]["icon"].string else { + break + } + + let country = json["city"]["country"].string + let forecastTemperature = Temperature(country: country!, + openWeatherMapDegrees: forecastTempDegrees) + let forecastTimeString = ForecastDateTime(date: rawDateTime, timeZone: TimeZone.current).shortTime + let weatherIcon = WeatherIcon(condition: forecastCondition, iconString: forecastIcon) + let forcastIconText = weatherIcon.iconText + + let forecast = Forecast(time: forecastTimeString, + iconText: forcastIconText, + temperature: forecastTemperature.degrees) + + forecasts.append(forecast) + } + + return forecasts } - let task = session.dataTask(with: url) { (data, response, error) in - // Check network error - guard error == nil else { - let error = SWError(errorCode: .networkRequestFailed) - completionHandler(nil, error) - return - } - - // Check JSON serialization error - guard let data = data else { - let error = SWError(errorCode: .jsonSerializationFailed) - completionHandler(nil, error) - return - } - - guard let json = try? JSON(data: data) else { - let error = SWError(errorCode: .jsonParsingFailed) - completionHandler(nil, error) - return - } - - // Get temperature, location and icon and check parsing error - guard let tempDegrees = json["list"][0]["main"]["temp"].double, - let country = json["city"]["country"].string, - let city = json["city"]["name"].string, - let weatherCondition = json["list"][0]["weather"][0]["id"].int, - let iconString = json["list"][0]["weather"][0]["icon"].string else { - let error = SWError(errorCode: .jsonParsingFailed) - completionHandler(nil, error) - return - } - - var weatherBuilder = WeatherBuilder() - let temperature = Temperature(country: country, openWeatherMapDegrees:tempDegrees) - weatherBuilder.temperature = temperature.degrees - weatherBuilder.location = city - - let weatherIcon = WeatherIcon(condition: weatherCondition, iconString: iconString) - weatherBuilder.iconText = weatherIcon.iconText - - weatherBuilder.forecasts = self.getFirstFourForecasts(json) - - completionHandler(weatherBuilder.build(), nil) + func retrieveWeatherInfo(_ location: CLLocation, completionHandler: @escaping WeatherCompletionHandler) { + let sessionConfig = URLSessionConfiguration.default + let session = URLSession(configuration: sessionConfig) + + guard let url = generateRequestURL(location) else { + let error = SWError(errorCode: .urlError) + completionHandler(nil, error) + return + } + + let task = session.dataTask(with: url) { (data, response, error) in + // Check network error + guard error == nil else { + let error = SWError(errorCode: .networkRequestFailed) + completionHandler(nil, error) + return + } + + // Check JSON serialization error + guard let data = data else { + let error = SWError(errorCode: .jsonSerializationFailed) + completionHandler(nil, error) + return + } + + guard let json = try? JSON(data: data) else { + let error = SWError(errorCode: .jsonParsingFailed) + completionHandler(nil, error) + return + } + + // Get temperature, location and icon and check parsing error + guard let tempDegrees = json["list"][0]["main"]["temp"].double, + let country = json["city"]["country"].string, + let city = json["city"]["name"].string, + let weatherCondition = json["list"][0]["weather"][0]["id"].int, + let iconString = json["list"][0]["weather"][0]["icon"].string else { + let error = SWError(errorCode: .jsonParsingFailed) + completionHandler(nil, error) + return + } + + var weatherBuilder = WeatherBuilder() + let temperature = Temperature(country: country, openWeatherMapDegrees:tempDegrees) + weatherBuilder.temperature = temperature.degrees + weatherBuilder.location = city + + let weatherIcon = WeatherIcon(condition: weatherCondition, iconString: iconString) + weatherBuilder.iconText = weatherIcon.iconText + + weatherBuilder.forecasts = self.getFirstFourForecasts(json) + + completionHandler(weatherBuilder.build(), nil) + } + + task.resume() } - - task.resume() - } - - fileprivate func generateRequestURL(_ location: CLLocation) -> URL? { - guard var components = URLComponents(string:urlPath) else { - return nil + + fileprivate func generateRequestURL(_ location: CLLocation) -> URL? { + guard var components = URLComponents(string:urlPath) else { + return nil + } + + // get appId from Info.plist + let filePath = Bundle.main.path(forResource: "Info", ofType: "plist")! + let parameters = NSDictionary(contentsOfFile:filePath) + let appId = parameters!["OWMAccessToken"]! as! String + + let latitude = String(location.coordinate.latitude) + let longitude = String(location.coordinate.longitude) + + components.queryItems = [URLQueryItem(name:"lat", value:latitude), + URLQueryItem(name:"lon", value:longitude), + URLQueryItem(name:"appid", value:appId)] + + return components.url } - - // get appId from Info.plist - let filePath = Bundle.main.path(forResource: "Info", ofType: "plist")! - let parameters = NSDictionary(contentsOfFile:filePath) - let appId = parameters!["OWMAccessToken"]! as! String - - let latitude = String(location.coordinate.latitude) - let longitude = String(location.coordinate.longitude) - - components.queryItems = [URLQueryItem(name:"lat", value:latitude), - URLQueryItem(name:"lon", value:longitude), - URLQueryItem(name:"appid", value:appId)] - - return components.url - } } diff --git a/SwiftWeather/WeatherViewController.swift b/SwiftWeather/WeatherViewController.swift index 0e9926b..6bc79b4 100644 --- a/SwiftWeather/WeatherViewController.swift +++ b/SwiftWeather/WeatherViewController.swift @@ -4,6 +4,7 @@ // import UIKit +import FacebookShare import CoreSpotlight import MobileCoreServices @@ -23,7 +24,7 @@ class WeatherViewController: UIViewController { super.viewDidLoad() viewModel = WeatherViewModel() viewModel?.startLocationService() - setA11yIdentifiers() + setAccessibilityIdentifiers() } override func viewWillAppear(_ animated: Bool) { @@ -36,50 +37,12 @@ class WeatherViewController: UIViewController { configureLabelsWithAnimation() } - //MARK: Functions - func configureLabels(){ - locationLabel.center.x -= view.bounds.width - iconLabel.center.x -= view.bounds.width - temperatureLabel.center.x -= view.bounds.width - - iconLabel.alpha = 0.0 - locationLabel.alpha = 0.0 - temperatureLabel.alpha = 0.0 - } - - func configureLabelsWithAnimation(){ - UIView.animate(withDuration: 0.5, animations: { - self.locationLabel.center.x += self.view.bounds.width - }) - - UIView.animate(withDuration: 0.5, delay: 0.3, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: { - self.iconLabel.center.x += self.view.bounds.width - }, completion: nil) - - UIView.animate(withDuration: 0.5, delay: 0.4, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: { - self.temperatureLabel.center.x += self.view.bounds.width - }, completion: nil) - - - UIView.animate(withDuration: 0.5, delay: 0.3, options: [], animations: { - self.iconLabel.alpha = 1.0 - }, completion: nil) - - UIView.animate(withDuration: 0.5, delay: 0.4, options: [], animations: { - self.locationLabel.alpha = 1.0 - }, completion: nil) - - UIView.animate(withDuration: 0.5, delay: 0.5, options: [], animations: { - self.temperatureLabel.alpha = 1.0 - }, completion: nil) - } - //MARK: ViewModel var viewModel: WeatherViewModel? { didSet { viewModel?.location.observe { [unowned self] in - self.locationLabel.text = $0 + self.locationLabel.text = $0 let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeText as String) attributeSet.title = self.locationLabel.text @@ -116,9 +79,60 @@ class WeatherViewController: UIViewController { } //MARK: Accessibility - func setA11yIdentifiers() { + func setAccessibilityIdentifiers() { locationLabel.accessibilityIdentifier = "a11y_current_city" iconLabel.accessibilityIdentifier = "a11y_wheather_icon" temperatureLabel.accessibilityIdentifier = "a11y_wheather_temperature" } + + //MARK: Functions + func configureLabels(){ + locationLabel.center.x -= view.bounds.width + iconLabel.center.x -= view.bounds.width + temperatureLabel.center.x -= view.bounds.width + iconLabel.alpha = 0.0 + locationLabel.alpha = 0.0 + temperatureLabel.alpha = 0.0 + } + + func configureLabelsWithAnimation(){ + UIView.animate(withDuration: 0.5, animations: { + self.locationLabel.center.x += self.view.bounds.width + }) + + UIView.animate(withDuration: 0.5, delay: 0.3, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: { + self.iconLabel.center.x += self.view.bounds.width + }, completion: nil) + + UIView.animate(withDuration: 0.5, delay: 0.4, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: { + self.temperatureLabel.center.x += self.view.bounds.width + }, completion: nil) + + UIView.animate(withDuration: 0.5, delay: 0.3, options: [], animations: { + self.iconLabel.alpha = 1.0 + }, completion: nil) + + UIView.animate(withDuration: 0.5, delay: 0.4, options: [], animations: { + self.locationLabel.alpha = 1.0 + }, completion: nil) + + UIView.animate(withDuration: 0.5, delay: 0.5, options: [], animations: { + self.temperatureLabel.alpha = 1.0 + }, completion: nil) + } + + //MARK: Actions + @IBAction func shareButtonPressed(_ sender: Any) { + shareOnFacebook() + } + + func shareOnFacebook(){ + let photo = Photo(image: #imageLiteral(resourceName: "background"), userGenerated: false) + let myContent = PhotoShareContent(photos: [photo]) + let shareDialog = ShareDialog(content: myContent) + shareDialog.mode = .native + shareDialog.failsOnInvalidData = true + + try? shareDialog.show() + } } From 21a762ebc5beabbf359a221fdde0b8ff42a38772 Mon Sep 17 00:00:00 2001 From: Saul Urias Date: Mon, 11 Jun 2018 17:00:50 -0700 Subject: [PATCH 5/5] Fixed ForecastDateTime Tests Adding TimeZone --- SwiftWeather/ForecastDateTime.swift | 27 +++++++------ .../UnitTests/ForecastDateTimeSpec.swift | 39 ++++++++++--------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/SwiftWeather/ForecastDateTime.swift b/SwiftWeather/ForecastDateTime.swift index 651413a..23e5f1d 100644 --- a/SwiftWeather/ForecastDateTime.swift +++ b/SwiftWeather/ForecastDateTime.swift @@ -6,16 +6,19 @@ import Foundation struct ForecastDateTime { - let rawDate: Double - - init(_ date: Double) { - rawDate = date - } - - var shortTime: String { - let dateFormatter = DateFormatter() - dateFormatter.dateFormat = "HH:mm" - let date = Date(timeIntervalSince1970: rawDate) - return dateFormatter.string(from: date) - } + let rawDate: Double + let timeZone: TimeZone + + init(date: Double, timeZone: TimeZone) { + self.timeZone = timeZone + self.rawDate = date + } + + var shortTime: String { + let dateFormatter = DateFormatter() + dateFormatter.timeZone = timeZone + dateFormatter.dateFormat = "HH:mm" + let date = Date(timeIntervalSince1970: rawDate) + return dateFormatter.string(from: date) + } } diff --git a/SwiftWeatherTests/UnitTests/ForecastDateTimeSpec.swift b/SwiftWeatherTests/UnitTests/ForecastDateTimeSpec.swift index b5cc1e6..d9c89e7 100644 --- a/SwiftWeatherTests/UnitTests/ForecastDateTimeSpec.swift +++ b/SwiftWeatherTests/UnitTests/ForecastDateTimeSpec.swift @@ -8,23 +8,26 @@ import Nimble @testable import SwiftWeather class ForecastDateTimeSpec: QuickSpec { - override func spec() { - describe("#init") { - it("should init with the rawDate correctly assigned") { - var forecastDateTime = ForecastDateTime(1234) - expect(forecastDateTime.rawDate).to(beCloseTo(1234)) - forecastDateTime = ForecastDateTime(0) - expect(forecastDateTime.rawDate).to(beCloseTo(0)) - } + + private let testTimeZone = TimeZone(abbreviation: "UTC+11:00")! + + override func spec() { + describe("#init") { + it("should init with the rawDate correctly assigned") { + var forecastDateTime = ForecastDateTime(date: 1488096060, timeZone: self.testTimeZone) + expect(forecastDateTime.rawDate).to(beCloseTo(1488096060)) + forecastDateTime = ForecastDateTime(date: 0, timeZone: self.testTimeZone) + expect(forecastDateTime.rawDate).to(beCloseTo(0)) + } + } + + describe("#shortTime") { + it("should return the correct shortTime string with format HH:mm") { + var forecastDateTime = ForecastDateTime(date: 1488096060, timeZone: self.testTimeZone) + expect(forecastDateTime.shortTime).to(equal("7:01 PM")) + forecastDateTime = ForecastDateTime(date: 1488103200, timeZone: self.testTimeZone) + expect(forecastDateTime.shortTime).to(equal("9:00 PM")) + } + } } - - describe("#shortTime") { - it("should return the correct shortTime string with format HH:mm") { - var forecastDateTime = ForecastDateTime(1488096060) - expect(forecastDateTime.shortTime).to(equal("01:01")) - forecastDateTime = ForecastDateTime(1488103200) - expect(forecastDateTime.shortTime).to(equal("03:00")) - } - } - } }