├── .DS_Store ├── .gitignore ├── .gitmodules ├── .swiftlint.yml ├── Design ├── SwiftWeather Iconpack for XCODE.sketch ├── SwiftWeather.sketch └── background.png ├── LICENSE ├── Podfile ├── Podfile.lock ├── README.md ├── README.v1.md ├── README.v2.1.md ├── README.v2.md ├── SwiftWeather.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── SwiftWeather.xcworkspace └── contents.xcworkspacedata ├── SwiftWeather ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── background.imageset │ │ ├── Contents.json │ │ └── background.png │ └── share.imageset │ │ ├── Contents.json │ │ └── share.png ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Error.swift ├── Forecast.swift ├── ForecastDateTime.swift ├── ForecastView.swift ├── ForecastView.xib ├── ForecastViewModel.swift ├── Info.plist ├── LocationService.swift ├── Observable.swift ├── OpenWeatherMapService.swift ├── Temperature.swift ├── TemperatureConverter.swift ├── Weather.swift ├── WeatherBuilder.swift ├── WeatherIcon.swift ├── WeatherServiceProtocol.swift ├── WeatherViewController.swift ├── WeatherViewModel.swift └── fonts │ └── weathericons-regular-webfont.ttf ├── SwiftWeatherTests ├── Info.plist └── UnitTests │ ├── ForecastDateTimeSpec.swift │ ├── ForecastSpec.swift │ ├── TemperatureSpec.swift │ ├── WeatherBuilderSpec.swift │ ├── WeatherIconSpec.swift │ └── WeatherSpec.swift ├── SwiftWeatherUITests ├── Info.plist └── SwiftWeatherUITests.swift ├── buddybuild_postclone.sh └── screenshots ├── 4s-fullsize.png ├── 4s-smallsize.png ├── 5s-fullsize.png ├── 5s-smallsize.png ├── 6-Today-fullsize.png ├── 6-Today-smallsize.png ├── 6-fullsize.png ├── 6-smallsize.png ├── 6plus-fullsize.png ├── 6plus-smallsize.png ├── Custom-UIView.png ├── IBDesignable-IBInspectable.png ├── Loading.png ├── SketchDesign.png ├── Swift Weather.png ├── Swift-Weather-33.png ├── UIStackView-with-Size-Classes.png ├── UIStackView.png └── loading-33.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### Swift ### 2 | # Xcode 3 | # 4 | build/ 5 | *.pbxuser 6 | !default.pbxuser 7 | *.mode1v3 8 | !default.mode1v3 9 | *.mode2v3 10 | !default.mode2v3 11 | *.perspectivev3 12 | !default.perspectivev3 13 | xcuserdata 14 | *.xccheckout 15 | *.moved-aside 16 | DerivedData 17 | *.hmap 18 | *.ipa 19 | *.xcuserstate 20 | 21 | ### AppCode ### 22 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio 23 | 24 | *.iml 25 | 26 | ## Directory-based project format: 27 | .idea/ 28 | # if you remove the above rule, at least ignore the following: 29 | 30 | # User-specific stuff: 31 | # .idea/workspace.xml 32 | # .idea/tasks.xml 33 | # .idea/dictionaries 34 | 35 | # Sensitive or high-churn files: 36 | # .idea/dataSources.ids 37 | # .idea/dataSources.xml 38 | # .idea/sqlDataSources.xml 39 | # .idea/dynamic.xml 40 | # .idea/uiDesigner.xml 41 | 42 | # Gradle: 43 | # .idea/gradle.xml 44 | # .idea/libraries 45 | 46 | # Mongo Explorer plugin: 47 | # .idea/mongoSettings.xml 48 | 49 | ## File-based project format: 50 | *.ipr 51 | *.iws 52 | 53 | ## Plugin-specific files: 54 | 55 | # IntelliJ 56 | /out/ 57 | 58 | # mpeltonen/sbt-idea plugin 59 | .idea_modules/ 60 | 61 | # JIRA plugin 62 | atlassian-ide-plugin.xml 63 | 64 | # Crashlytics plugin (for Android Studio and IntelliJ) 65 | com_crashlytics_export_strings.xml 66 | crashlytics.properties 67 | crashlytics-build.properties 68 | 69 | # CocoaPods 70 | # 71 | # We recommend against adding the Pods directory to your .gitignore. However 72 | # you should judge for yourself, the pros and cons are mentioned at: 73 | # http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control? 74 | # 75 | Pods/ 76 | 77 | # Carthage 78 | Carthage.pkg 79 | Carthage.build 80 | Carthage.checkout 81 | 82 | # Access keys 83 | .access_tokens 84 | 85 | # rbenv 86 | .ruby-version 87 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Alamofire"] 2 | path = Alamofire 3 | url = https://github.com/Alamofire/Alamofire.git 4 | [submodule "SwiftyJSON"] 5 | path = SwiftyJSON 6 | url = https://github.com/SwiftyJSON/SwiftyJSON.git 7 | -------------------------------------------------------------------------------- /.swiftlint.yml: -------------------------------------------------------------------------------- 1 | excluded: 2 | - Pods 3 | -------------------------------------------------------------------------------- /Design/SwiftWeather Iconpack for XCODE.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/Design/SwiftWeather Iconpack for XCODE.sketch -------------------------------------------------------------------------------- /Design/SwiftWeather.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/Design/SwiftWeather.sketch -------------------------------------------------------------------------------- /Design/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/Design/background.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jake Lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '10.0' 2 | use_frameworks! 3 | 4 | target 'SwiftWeather' do 5 | pod 'SwiftyJSON' 6 | pod 'FacebookShare' 7 | end 8 | 9 | abstract_target 'Tests' do 10 | pod 'Quick' 11 | pod 'Nimble' 12 | 13 | target 'SwiftWeatherTests' 14 | target 'SwiftWeatherUITests' 15 | end 16 | 17 | 18 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Bolts (1.9.0): 3 | - Bolts/AppLinks (= 1.9.0) 4 | - Bolts/Tasks (= 1.9.0) 5 | - Bolts/AppLinks (1.9.0): 6 | - Bolts/Tasks 7 | - Bolts/Tasks (1.9.0) 8 | - FacebookCore (0.3.0): 9 | - Bolts (~> 1.8) 10 | - FBSDKCoreKit (~> 4.27) 11 | - FacebookShare (0.3.0): 12 | - Bolts (~> 1.8) 13 | - FacebookCore (~> 0.3) 14 | - FBSDKCoreKit (~> 4.27) 15 | - FBSDKShareKit (~> 4.27) 16 | - FBSDKCoreKit (4.33.0): 17 | - Bolts (~> 1.7) 18 | - FBSDKShareKit (4.33.0): 19 | - FBSDKCoreKit (~> 4.33.0) 20 | - Nimble (7.0.3) 21 | - Quick (1.2.0) 22 | - SwiftyJSON (4.0.0) 23 | 24 | DEPENDENCIES: 25 | - FacebookShare 26 | - Nimble 27 | - Quick 28 | - SwiftyJSON 29 | 30 | SPEC REPOS: 31 | https://github.com/CocoaPods/Specs.git: 32 | - Bolts 33 | - FacebookCore 34 | - FacebookShare 35 | - FBSDKCoreKit 36 | - FBSDKShareKit 37 | - Nimble 38 | - Quick 39 | - SwiftyJSON 40 | 41 | SPEC CHECKSUMS: 42 | Bolts: ac6567323eac61e203f6a9763667d0f711be34c8 43 | FacebookCore: 3ffa190a3f1f96cec0e44d3fc221bc322c595ffa 44 | FacebookShare: 0469964297ebd75f052be2c5083389a4208e82b7 45 | FBSDKCoreKit: 572b047a7e029bc44542bcf8a59414e7ff2b543e 46 | FBSDKShareKit: 1869cb24db2cea90666a50cb9d568deb38e2d16e 47 | Nimble: 7f5a9c447a33002645a071bddafbfb24ea70e0ac 48 | Quick: 58d203b1c5e27fff7229c4c1ae445ad7069a7a08 49 | SwiftyJSON: 070dabdcb1beb81b247c65ffa3a79dbbfb3b48aa 50 | 51 | PODFILE CHECKSUM: 28f6397e3ce4b1c0258bb51e941d6155f9477e75 52 | 53 | COCOAPODS: 1.5.0 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Swift Language Weather 2 | ============ 3 | [![BuddyBuild](https://dashboard.buddybuild.com/api/statusImage?appID=562a9aac2492560100211378&branch=master&build=latest)](https://dashboard.buddybuild.com/apps/562a9aac2492560100211378/build/latest) 4 | ![Language](https://img.shields.io/badge/language-Swift%204-orange.svg) 5 | ![License](https://img.shields.io/github/license/JakeLin/SwiftWeather.svg?style=flat) 6 | 7 | **SwiftWeather** has renamed to **Swift Language Weather**. Because this repo is ranked number one in Google when we search "Swift Weather", I got an email from Swift Weather Company's lawyer to ask me to change the name because they said they are the owner of U.S. Trademark SWIFT WEATHER. After discussed with them, they were not happy with the name Swift**y**Weather. Now the new project name is **Swift Language Weather**. More details can be found on [Issue: Open source project using a registered trademark](https://github.com/JakeLin/SwiftWeather/issues/65). 8 | 9 | **Swift Language Weather** is an iOS weather app developed in Swift 4. The app has been actively upgrading to adopt the latest features of iOS and Swift language. 10 | 11 | ## Notices 12 | The current version is working with Xcode Version Xcode 9.1 (9B55). If you are using different Xcode version, please check out the previous releases. 13 | 14 | ## Version 4 15 | This version has been upgraded to support iOS 10+ only using Swift 4. 16 | 17 | There is three major version of the app released before. 18 | 19 | * V1.0 - Support iOS 7+ using CocoaPods and AFNetworking. [README.v1.md](https://github.com/JakeLin/SwiftWeather/blob/master/README.v1.md) and [Release V1 - Using CocoaPods and AFNetworking](https://github.com/JakeLin/SwiftWeather/releases/tag/V1) 20 | * V2.0 - Support iOS 8+ using Carthage, Alamofire, and SwiftyJSON. [README.v2.md](https://github.com/JakeLin/SwiftWeather/blob/master/README.v2.md) and [Release V2.0](https://github.com/JakeLin/SwiftWeather/releases/tag/v2.0) 21 | * V2.1 - Support iOS 8+ using Alamofire and SwiftyJSON. This version has removed Carthage because some developers don't have a paid Apple iOS developer account, and they have issues to build Carthage packages. 22 | * V3.0 - Support iOS 9+ and Swift 3. 23 | 24 | 25 | ## Screenshots 26 | 27 | 28 | ## Features 29 | * Swift Programming Language 30 | * Design-driven development - [Sketch design file ](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/Design/SwiftWeather.sketch) 31 | 32 | ![Sketch design](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/SketchDesign.png) 33 | 34 | * Custom UIView 35 | 36 | ![Custom UIView](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/Custom-UIView.png) 37 | 38 | * `@IBDesignable` and `@IBInspectable` - Reusable UI components 39 | 40 | ![IBDesignable and IBInspectable](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/IBDesignable-IBInspectable.png) 41 | 42 | * `UIStackView` 43 | 44 | ![UIStackView](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/UIStackView.png) 45 | 46 | * Size Classes - Support different devices with adaptive layout 47 | 48 | ![Size Classes](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/UIStackView-with-Size-Classes.png) 49 | 50 | * MVVM - Reactively update `ViewController` UI from `ViewModel` 51 | * Protocol-Oriented Programming - We use Protocol-Oriented Programming in [IBAnimatable open source project](https://github.com/IBAnimatable/IBAnimatable). 52 | * Value-based programming - Use immutable value anywhere. 53 | * Icon fonts - Use [Weather Icons](https://erikflowers.github.io/weather-icons/) 54 | * [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON) 55 | * Core Location 56 | * App indexing like CoreSpotlight and `NSUserActivity` 57 | * Unit Tests 58 | * UI Tests 59 | * Animations 60 | 61 | ## How to build 62 | 63 | 1) Clone the repository 64 | 65 | ```bash 66 | $ git clone https://github.com/JakeLin/SwiftLanguageWeather.git 67 | ``` 68 | 69 | 2) Install pods 70 | 71 | ```bash 72 | $ cd SwiftLanguageWeather 73 | $ pod install 74 | ``` 75 | 76 | 3) Open the workspace in Xcode 77 | 78 | ```bash 79 | $ open "SwiftWeather.xcworkspace" 80 | ``` 81 | 82 | 4) Sign up on [openweathermap.org/appid](http://openweathermap.org/appid) to get an appid 83 | 84 | ```bash 85 | $ mkdir .access_tokens 86 | $ echo "your-openweathermap-appid" > .access_tokens/openweathermap 87 | ``` 88 | *Please replace "your-openweathermap-appid" with your actual appid key.* 89 | 90 | 5) Compile and run the app in your simulator 91 | 92 | 6) If you don't see any data, please check "Simulator" -> "Debug" -> "Location" to change the location. 93 | 94 | # Requirements 95 | 96 | * Xcode 9 97 | * iOS 10+ 98 | * Swift 4 99 | 100 | 101 | -------------------------------------------------------------------------------- /README.v1.md: -------------------------------------------------------------------------------- 1 | Swift Weather 2 | ============ 3 | 4 | SwiftWeather is an iOS weather app developed in Swift language. The app can support iPhone 4(s), iPhone 5(s), iPhone 6 and iPhone 6 Plus. 5 | 6 | ## Screenshots 7 | ![Loading](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/loading-33.png) 8 | 9 | ![Loading](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/6-Today-smallsize.png) 10 | 11 | 12 | #### iPhone 4s 13 | ![Swift Weather](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/4s-smallsize.png) 14 | 15 | #### iPhone 5s 16 | ![Swift Weather](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/5s-smallsize.png) 17 | 18 | 19 | #### iPhone 6 20 | ![Swift Weather](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/6-smallsize.png) 21 | 22 | 23 | #### iPhone 6 Plus 24 | ![Swift Weather](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/6plus-smallsize.png) 25 | 26 | ## Notices 27 | Because Apple keeps changing the Swift compiler, the current version can be compiled in Xcode 6.1. 28 | 29 | ## Used features 30 | * Swift Programming Language 31 | * CocoaPods 32 | * AFNetworking 33 | * Core Location 34 | 35 | 36 | ## How to build 37 | Because the app uses CocoaPods, we need to run `pod install` to install all the pods. 38 | 39 | 1. Open Terminal app. 40 | 2. Change directory to the project folder. `cd $project_dir` 41 | 3. Use `ls` to list all the file to check whether *Podfile* file is in the folder? 42 | 4. If the *Podfile* has been found, then execute `pod install` 43 | 5. If the Mac OS doesn't have CocoaPods installed. Please follow [CocoaPods Getting Started](http://guides.cocoapods.org/using/getting-started.html) to install. 44 | 6. Once complete installation, open *Swift Weather.xcworkspace* file with Xcode 6. 45 | 7. Press *Cmd + B* to build the app. 46 | 8. Press *Cmd + R* to run the app on Simulator. 47 | 48 | ## Credits 49 | * Thanks to [johnsonjake](https://github.com/johnsonjake) for adding iOS 8 support and improving the UI/UX. 50 | * Thanks to [Marc](https://github.com/gizmou) for adding forcast feature, widget and app icon. 51 | -------------------------------------------------------------------------------- /README.v2.1.md: -------------------------------------------------------------------------------- 1 | Swift Weather 2 | ============ 3 | 4 | SwiftWeather is an iOS weather app developed in the Swift language. The app can support iPhone 4(s), iPhone 5(s), iPhone 6, and iPhone 6 Plus. The app also supports Today Widgets. 5 | 6 | ## Notices 7 | The current version is working with Xcode Version 6.4 (6E35b). If you have any issues, please check the Xcode version. If there is still a problem with the supported versions, please raise an issue, thanks. 8 | 9 | ## Version 2.1 10 | This is version 2.1. I have removed Carthage because some developers don't have a paid Apple iOS developer account and they have issues to build Carthage packages. 11 | 12 | There are two major versions of the app released before. 13 | 14 | * V1.0 - Support iOS 7+ using CocoaPods and AFNetworking. [README.v1.md](https://github.com/JakeLin/SwiftWeather/blob/master/README.v1.md) and [Release V1 - Using Cocoapods and AFNetworking](https://github.com/JakeLin/SwiftWeather/releases/tag/V1) 15 | * V2.0 - Support iOS 8+ using Carthage, Alamofire and SwiftyJSON. [README.v2.md](https://github.com/JakeLin/SwiftWeather/blob/master/README.v2.md) and [Release V2.0](https://github.com/JakeLin/SwiftWeather/releases/tag/v2.0)* 16 | 17 | V2.1 will be the last version to support iOS 8. I am working on Xcode 7 to support iOS 9 only features like `UIStackView`. Happy coding. 18 | 19 | ## Screenshots 20 | ![Loading](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/loading-33.png) 21 | 22 | ![Loading](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/6-Today-smallsize.png) 23 | 24 | ![Swift Weather](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/6-smallsize.png) 25 | 26 | 27 | ## Used features 28 | * Swift Programming Language 29 | * [Alamofire](https://github.com/Alamofire/Alamofire) 30 | * [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON) 31 | * Core Location 32 | * Using Framework to share code between targets(app and widget). 33 | 34 | 35 | ## How to build 36 | 37 | 1) Download the repository 38 | 39 | ``` 40 | $ git clone https://github.com/JakeLin/SwiftWeather.git 41 | $ cd SwiftWeather 42 | ``` 43 | 44 | 2) Initialize submodule dependencies 45 | 46 | ``` 47 | $ git submodule update --init --recursive 48 | ``` 49 | 50 | 3) Open the project in Xcode 51 | 52 | ``` 53 | $ open "Swift Weather.xcodeproj" 54 | ``` 55 | 56 | 4) Compile and run the app in your simulator 57 | 58 | # Requirements 59 | 60 | - Xcode 6.4 61 | - iOS 8 62 | 63 | ## Credits 64 | * Thanks to [johnsonjake](https://github.com/johnsonjake) for adding iOS 8 support and improving the UI/UX. 65 | * Thanks to [Marc](https://github.com/gizmou) for adding forecast feature, widget, and app icon. 66 | -------------------------------------------------------------------------------- /README.v2.md: -------------------------------------------------------------------------------- 1 | Swift Weather 2 | ============ 3 | 4 | SwiftWeather is an iOS weather app developed in Swift language. The app can support iPhone 4(s), iPhone 5(s), iPhone 6 and iPhone 6 Plus. The app also supports Today Widgets. 5 | 6 | ## Notices 7 | The current version is working with Xcode Version 6.3 (6D570) and Version 6.3.1 (6D1002), I have been updating the app to support the lastest version of Xcode, if you have any issue, please check the Xcode version. If there is still a problem with the supported versions, please raise an issue, thanks. 8 | 9 | ## Version 2 10 | This is version 2. I have ugraded the entire project to use [Carthage](https://github.com/Carthage/Carthage), [Alamofire](https://github.com/Alamofire/Alamofire) and [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON). And the app can only support iOS8+, if you want to support iOS7 or use CocoaPods and AFNetworking. Please have a look at [README.v1.md](https://github.com/JakeLin/SwiftWeather/blob/master/README.v1.md) and Release [Using Cocoapods and AFNetworking](https://github.com/JakeLin/SwiftWeather/releases/tag/V1). Happy coding. 11 | 12 | ## Screenshots 13 | ![Loading](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/loading-33.png) 14 | 15 | ![Loading](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/6-Today-smallsize.png) 16 | 17 | ![Swift Weather](https://raw.githubusercontent.com/JakeLin/SwiftWeather/master/screenshots/6-smallsize.png) 18 | 19 | 20 | ## Used features 21 | * Swift Programming Language 22 | * [Carthage](https://github.com/Carthage/Carthage) 23 | * [Alamofire](https://github.com/Alamofire/Alamofire) 24 | * [SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON) 25 | * Core Location 26 | * Using Framework to share code between targets(app and widget). 27 | 28 | 29 | ## Known issues 30 | Because we are using [Carthage](https://github.com/Carthage/Carthage) to build the third party packages. There are some build warings like `ld: warning: linking against dylib not safe for use in application extensions: /Build/Products/Debug-iphoneos/Alamofire.framework/Alamofire` 31 | 32 | ## How to build 33 | Because the app uses carthage, we need to install carthage. To install the carthage tool on your system, please download and run the Carthage.pkg file for the latest [release](https://github.com/Carthage/Carthage/releases), then follow the on-screen instructions.. 34 | 35 | 1. Open Terminal app. 36 | 2. Change directory to the project folder. `cd $project_dir` 37 | 3. Use `ls` to list all the file to check whether *Cartfile* file is in the folder? 38 | 4. If the *Cartfile* has been found, then execute `carthage update`.This will fetch dependencies into a Carthage/Checkouts folder, then build each one. 39 | 5. On your application targets’ “General” settings tab, in the “Linked Frameworks and Libraries” section, drag and drop each framework you want to use from the Carthage/Build folder on disk. In our project, which are `Alamofire` and `SwiftyJSON` 40 | 7. Press *Cmd + B* to build the app. 41 | 8. Press *Cmd + R* to run the app on Simulator. 42 | 43 | ## Credits 44 | * Thanks to [johnsonjake](https://github.com/johnsonjake) for adding iOS 8 support and improving the UI/UX. 45 | * Thanks to [Marc](https://github.com/gizmou) for adding forcast feature, widget and app icon. 46 | -------------------------------------------------------------------------------- /SwiftWeather.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1E8FF8444E2C29BD30B867EF /* Pods_Tests_SwiftWeatherUITests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 418594A8DD8093A647A30E12 /* Pods_Tests_SwiftWeatherUITests.framework */; }; 11 | 5120F2EFC3DE077315306794 /* Pods_SwiftWeather.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BF971A1BA4A6D64984CB0A75 /* Pods_SwiftWeather.framework */; }; 12 | 655E8966C3E09626A8221261 /* Pods_Tests_SwiftWeatherTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 342A91306F3CF92DD999C97D /* Pods_Tests_SwiftWeatherTests.framework */; }; 13 | AE09C4301B9723DE00C7CCED /* LocationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE09C42F1B9723DE00C7CCED /* LocationService.swift */; }; 14 | AE0DC2CD1B8E7B3900E67147 /* Observable.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE0DC2CC1B8E7B3900E67147 /* Observable.swift */; }; 15 | AE26CCAB1B875C2400D518CB /* ForecastView.xib in Resources */ = {isa = PBXBuildFile; fileRef = AE26CCAA1B875C2400D518CB /* ForecastView.xib */; }; 16 | AE2C7D6C1BA028C000A7A714 /* WeatherIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = AE2C7D6B1BA028C000A7A714 /* WeatherIcon.swift */; }; 17 | AE6C34E91B84742900F726C2 /* weathericons-regular-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = AE6C34E81B84742900F726C2 /* weathericons-regular-webfont.ttf */; }; 18 | AEBE643B1B8D1F90004A0814 /* ForecastViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEBE643A1B8D1F90004A0814 /* ForecastViewModel.swift */; }; 19 | AEBE643E1B8D2108004A0814 /* Forecast.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEBE643D1B8D2108004A0814 /* Forecast.swift */; }; 20 | AEBE64431B8D2370004A0814 /* Weather.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEBE64421B8D2370004A0814 /* Weather.swift */; }; 21 | AEBE64451B8D23B8004A0814 /* WeatherViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEBE64441B8D23B8004A0814 /* WeatherViewModel.swift */; }; 22 | AECBA5E61B836BF20004A536 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = AECBA5E51B836BF20004A536 /* AppDelegate.swift */; }; 23 | AECBA5E81B836BF20004A536 /* WeatherViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AECBA5E71B836BF20004A536 /* WeatherViewController.swift */; }; 24 | AECBA5EB1B836BF20004A536 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AECBA5E91B836BF20004A536 /* Main.storyboard */; }; 25 | AECBA5ED1B836BF20004A536 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AECBA5EC1B836BF20004A536 /* Assets.xcassets */; }; 26 | AECBA5F01B836BF20004A536 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = AECBA5EE1B836BF20004A536 /* LaunchScreen.storyboard */; }; 27 | AECBA6061B836BF20004A536 /* SwiftWeatherUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AECBA6051B836BF20004A536 /* SwiftWeatherUITests.swift */; }; 28 | AED4B2C21B876E8B0003D765 /* ForecastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = AED4B2C01B876DF50003D765 /* ForecastView.swift */; }; 29 | AEEDF6891B9F09E300C6067B /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEEDF6881B9F09E300C6067B /* Error.swift */; }; 30 | AEEDF68B1B9F99F800C6067B /* WeatherBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEEDF68A1B9F99F800C6067B /* WeatherBuilder.swift */; }; 31 | AEEDF68D1B9F9B2900C6067B /* Temperature.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEEDF68C1B9F9B2900C6067B /* Temperature.swift */; }; 32 | AEF61B281BA23B1200E8F259 /* ForecastDateTime.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEF61B271BA23B1200E8F259 /* ForecastDateTime.swift */; }; 33 | CAB565DDADB61DF9053BD50F /* WeatherServiceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB56A3D23F4A93BB599E4BA /* WeatherServiceProtocol.swift */; }; 34 | CAB56F4267B3BC487990A92D /* OpenWeatherMapService.swift in Sources */ = {isa = PBXBuildFile; fileRef = CAB56D46471CA243B1FD646F /* OpenWeatherMapService.swift */; }; 35 | EADFFD301CAEA22F008357FF /* TemperatureConverter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EADFFD2F1CAEA22F008357FF /* TemperatureConverter.swift */; }; 36 | F3897F7C1C118C4F001609E2 /* ForecastDateTimeSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3897F7B1C118C4F001609E2 /* ForecastDateTimeSpec.swift */; }; 37 | F3897F7E1C118C6E001609E2 /* WeatherSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3897F7D1C118C6E001609E2 /* WeatherSpec.swift */; }; 38 | F3897F801C118D7D001609E2 /* ForecastSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3897F7F1C118D7D001609E2 /* ForecastSpec.swift */; }; 39 | F3897F821C118F0A001609E2 /* TemperatureSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3897F811C118F0A001609E2 /* TemperatureSpec.swift */; }; 40 | F3897F841C11911B001609E2 /* WeatherIconSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3897F831C11911B001609E2 /* WeatherIconSpec.swift */; }; 41 | F3FADA1B1C1327CB006D8551 /* WeatherBuilderSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3FADA1A1C1327CB006D8551 /* WeatherBuilderSpec.swift */; }; 42 | /* End PBXBuildFile section */ 43 | 44 | /* Begin PBXContainerItemProxy section */ 45 | AECBA5F71B836BF20004A536 /* PBXContainerItemProxy */ = { 46 | isa = PBXContainerItemProxy; 47 | containerPortal = AECBA5DA1B836BF20004A536 /* Project object */; 48 | proxyType = 1; 49 | remoteGlobalIDString = AECBA5E11B836BF20004A536; 50 | remoteInfo = SwiftWeather; 51 | }; 52 | AECBA6021B836BF20004A536 /* PBXContainerItemProxy */ = { 53 | isa = PBXContainerItemProxy; 54 | containerPortal = AECBA5DA1B836BF20004A536 /* Project object */; 55 | proxyType = 1; 56 | remoteGlobalIDString = AECBA5E11B836BF20004A536; 57 | remoteInfo = SwiftWeather; 58 | }; 59 | /* End PBXContainerItemProxy section */ 60 | 61 | /* Begin PBXFileReference section */ 62 | 342A91306F3CF92DD999C97D /* Pods_Tests_SwiftWeatherTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tests_SwiftWeatherTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 63 | 418594A8DD8093A647A30E12 /* Pods_Tests_SwiftWeatherUITests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Tests_SwiftWeatherUITests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 64 | 53EEE101C82E858E57980710 /* Pods-Tests-SwiftWeatherUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-SwiftWeatherUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Tests-SwiftWeatherUITests/Pods-Tests-SwiftWeatherUITests.release.xcconfig"; sourceTree = ""; }; 65 | 900AFFBB41C69FC277CB0A33 /* Pods-SwiftWeather.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftWeather.debug.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftWeather/Pods-SwiftWeather.debug.xcconfig"; sourceTree = ""; }; 66 | 919B9EF9E5898277AD9C772A /* Pods-Tests-SwiftWeatherUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-SwiftWeatherUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tests-SwiftWeatherUITests/Pods-Tests-SwiftWeatherUITests.debug.xcconfig"; sourceTree = ""; }; 67 | AE09C42F1B9723DE00C7CCED /* LocationService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocationService.swift; sourceTree = ""; }; 68 | AE0DC2CC1B8E7B3900E67147 /* Observable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Observable.swift; sourceTree = ""; }; 69 | AE26CCAA1B875C2400D518CB /* ForecastView.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ForecastView.xib; sourceTree = ""; }; 70 | AE2C7D6B1BA028C000A7A714 /* WeatherIcon.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeatherIcon.swift; sourceTree = ""; }; 71 | AE6C34E81B84742900F726C2 /* weathericons-regular-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "weathericons-regular-webfont.ttf"; sourceTree = ""; }; 72 | AEBE643A1B8D1F90004A0814 /* ForecastViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForecastViewModel.swift; sourceTree = ""; }; 73 | AEBE643D1B8D2108004A0814 /* Forecast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Forecast.swift; sourceTree = ""; }; 74 | AEBE64421B8D2370004A0814 /* Weather.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Weather.swift; sourceTree = ""; }; 75 | AEBE64441B8D23B8004A0814 /* WeatherViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; indentWidth = 2; lastKnownFileType = sourcecode.swift; path = WeatherViewModel.swift; sourceTree = ""; tabWidth = 2; }; 76 | AECBA5E21B836BF20004A536 /* SwiftWeather.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftWeather.app; sourceTree = BUILT_PRODUCTS_DIR; }; 77 | AECBA5E51B836BF20004A536 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 78 | AECBA5E71B836BF20004A536 /* WeatherViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherViewController.swift; sourceTree = ""; }; 79 | AECBA5EA1B836BF20004A536 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 80 | AECBA5EC1B836BF20004A536 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 81 | AECBA5EF1B836BF20004A536 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 82 | AECBA5F11B836BF20004A536 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 83 | AECBA5F61B836BF20004A536 /* SwiftWeatherTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftWeatherTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 84 | AECBA5FC1B836BF20004A536 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 85 | AECBA6011B836BF20004A536 /* SwiftWeatherUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SwiftWeatherUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 86 | AECBA6051B836BF20004A536 /* SwiftWeatherUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftWeatherUITests.swift; sourceTree = ""; }; 87 | AECBA6071B836BF20004A536 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 88 | AED4B2C01B876DF50003D765 /* ForecastView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForecastView.swift; sourceTree = ""; }; 89 | AEEDF6881B9F09E300C6067B /* Error.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Error.swift; sourceTree = ""; }; 90 | AEEDF68A1B9F99F800C6067B /* WeatherBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeatherBuilder.swift; sourceTree = ""; }; 91 | AEEDF68C1B9F9B2900C6067B /* Temperature.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Temperature.swift; sourceTree = ""; }; 92 | AEF61B271BA23B1200E8F259 /* ForecastDateTime.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForecastDateTime.swift; sourceTree = ""; }; 93 | BF971A1BA4A6D64984CB0A75 /* Pods_SwiftWeather.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_SwiftWeather.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 94 | BFCBC7111E6F5822A73508A5 /* Pods-Tests-SwiftWeatherTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-SwiftWeatherTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Tests-SwiftWeatherTests/Pods-Tests-SwiftWeatherTests.debug.xcconfig"; sourceTree = ""; }; 95 | CAB56A3D23F4A93BB599E4BA /* WeatherServiceProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeatherServiceProtocol.swift; sourceTree = ""; }; 96 | CAB56D46471CA243B1FD646F /* OpenWeatherMapService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenWeatherMapService.swift; sourceTree = ""; }; 97 | E835C39D4B929F35B6A57B12 /* Pods-SwiftWeather.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-SwiftWeather.release.xcconfig"; path = "Pods/Target Support Files/Pods-SwiftWeather/Pods-SwiftWeather.release.xcconfig"; sourceTree = ""; }; 98 | EADFFD2F1CAEA22F008357FF /* TemperatureConverter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemperatureConverter.swift; sourceTree = ""; }; 99 | F3897F7B1C118C4F001609E2 /* ForecastDateTimeSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForecastDateTimeSpec.swift; sourceTree = ""; }; 100 | F3897F7D1C118C6E001609E2 /* WeatherSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeatherSpec.swift; sourceTree = ""; }; 101 | F3897F7F1C118D7D001609E2 /* ForecastSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ForecastSpec.swift; sourceTree = ""; }; 102 | F3897F811C118F0A001609E2 /* TemperatureSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TemperatureSpec.swift; sourceTree = ""; }; 103 | F3897F831C11911B001609E2 /* WeatherIconSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeatherIconSpec.swift; sourceTree = ""; }; 104 | F3FADA1A1C1327CB006D8551 /* WeatherBuilderSpec.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeatherBuilderSpec.swift; sourceTree = ""; }; 105 | F4007F80070D3AAE1C564D8D /* Pods-Tests-SwiftWeatherTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Tests-SwiftWeatherTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-Tests-SwiftWeatherTests/Pods-Tests-SwiftWeatherTests.release.xcconfig"; sourceTree = ""; }; 106 | /* End PBXFileReference section */ 107 | 108 | /* Begin PBXFrameworksBuildPhase section */ 109 | AECBA5DF1B836BF20004A536 /* Frameworks */ = { 110 | isa = PBXFrameworksBuildPhase; 111 | buildActionMask = 2147483647; 112 | files = ( 113 | 5120F2EFC3DE077315306794 /* Pods_SwiftWeather.framework in Frameworks */, 114 | ); 115 | runOnlyForDeploymentPostprocessing = 0; 116 | }; 117 | AECBA5F31B836BF20004A536 /* Frameworks */ = { 118 | isa = PBXFrameworksBuildPhase; 119 | buildActionMask = 2147483647; 120 | files = ( 121 | 655E8966C3E09626A8221261 /* Pods_Tests_SwiftWeatherTests.framework in Frameworks */, 122 | ); 123 | runOnlyForDeploymentPostprocessing = 0; 124 | }; 125 | AECBA5FE1B836BF20004A536 /* Frameworks */ = { 126 | isa = PBXFrameworksBuildPhase; 127 | buildActionMask = 2147483647; 128 | files = ( 129 | 1E8FF8444E2C29BD30B867EF /* Pods_Tests_SwiftWeatherUITests.framework in Frameworks */, 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | /* End PBXFrameworksBuildPhase section */ 134 | 135 | /* Begin PBXGroup section */ 136 | 0D4BA09867F4A2B5754C4C42 /* Frameworks */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | BF971A1BA4A6D64984CB0A75 /* Pods_SwiftWeather.framework */, 140 | 342A91306F3CF92DD999C97D /* Pods_Tests_SwiftWeatherTests.framework */, 141 | 418594A8DD8093A647A30E12 /* Pods_Tests_SwiftWeatherUITests.framework */, 142 | ); 143 | name = Frameworks; 144 | sourceTree = ""; 145 | }; 146 | AE0DC2CB1B8E7ACE00E67147 /* Common */ = { 147 | isa = PBXGroup; 148 | children = ( 149 | AE0DC2CC1B8E7B3900E67147 /* Observable.swift */, 150 | AE09C42F1B9723DE00C7CCED /* LocationService.swift */, 151 | AEEDF6881B9F09E300C6067B /* Error.swift */, 152 | AEF61B271BA23B1200E8F259 /* ForecastDateTime.swift */, 153 | ); 154 | name = Common; 155 | sourceTree = ""; 156 | }; 157 | AE6C34E71B84742900F726C2 /* fonts */ = { 158 | isa = PBXGroup; 159 | children = ( 160 | AE6C34E81B84742900F726C2 /* weathericons-regular-webfont.ttf */, 161 | ); 162 | path = fonts; 163 | sourceTree = ""; 164 | }; 165 | AEBE643C1B8D203C004A0814 /* Forecast */ = { 166 | isa = PBXGroup; 167 | children = ( 168 | AE26CCAA1B875C2400D518CB /* ForecastView.xib */, 169 | AED4B2C01B876DF50003D765 /* ForecastView.swift */, 170 | AEBE643A1B8D1F90004A0814 /* ForecastViewModel.swift */, 171 | AEBE643D1B8D2108004A0814 /* Forecast.swift */, 172 | ); 173 | name = Forecast; 174 | sourceTree = ""; 175 | }; 176 | AEBE643F1B8D22ED004A0814 /* Storyboards */ = { 177 | isa = PBXGroup; 178 | children = ( 179 | AECBA5E91B836BF20004A536 /* Main.storyboard */, 180 | AECBA5EE1B836BF20004A536 /* LaunchScreen.storyboard */, 181 | ); 182 | name = Storyboards; 183 | sourceTree = ""; 184 | }; 185 | AEBE64401B8D2306004A0814 /* Weather */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | AEF61B2B1BA23C2A00E8F259 /* Models */, 189 | AEBE64441B8D23B8004A0814 /* WeatherViewModel.swift */, 190 | AECBA5E71B836BF20004A536 /* WeatherViewController.swift */, 191 | AEF61B291BA23C0500E8F259 /* Services */, 192 | ); 193 | name = Weather; 194 | sourceTree = ""; 195 | }; 196 | AEBE64411B8D231E004A0814 /* Features */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | AEBE64401B8D2306004A0814 /* Weather */, 200 | AEBE643C1B8D203C004A0814 /* Forecast */, 201 | ); 202 | name = Features; 203 | sourceTree = ""; 204 | }; 205 | AECBA5D91B836BF20004A536 = { 206 | isa = PBXGroup; 207 | children = ( 208 | AECBA5E41B836BF20004A536 /* SwiftWeather */, 209 | AECBA5F91B836BF20004A536 /* SwiftWeatherTests */, 210 | AECBA6041B836BF20004A536 /* SwiftWeatherUITests */, 211 | AECBA5E31B836BF20004A536 /* Products */, 212 | E117EDC87D7E5DC450870FDD /* Pods */, 213 | 0D4BA09867F4A2B5754C4C42 /* Frameworks */, 214 | ); 215 | sourceTree = ""; 216 | }; 217 | AECBA5E31B836BF20004A536 /* Products */ = { 218 | isa = PBXGroup; 219 | children = ( 220 | AECBA5E21B836BF20004A536 /* SwiftWeather.app */, 221 | AECBA5F61B836BF20004A536 /* SwiftWeatherTests.xctest */, 222 | AECBA6011B836BF20004A536 /* SwiftWeatherUITests.xctest */, 223 | ); 224 | name = Products; 225 | sourceTree = ""; 226 | }; 227 | AECBA5E41B836BF20004A536 /* SwiftWeather */ = { 228 | isa = PBXGroup; 229 | children = ( 230 | AE0DC2CB1B8E7ACE00E67147 /* Common */, 231 | AEBE64411B8D231E004A0814 /* Features */, 232 | AEBE643F1B8D22ED004A0814 /* Storyboards */, 233 | AE6C34E71B84742900F726C2 /* fonts */, 234 | AECBA5EC1B836BF20004A536 /* Assets.xcassets */, 235 | AECBA5E51B836BF20004A536 /* AppDelegate.swift */, 236 | AECBA5F11B836BF20004A536 /* Info.plist */, 237 | ); 238 | path = SwiftWeather; 239 | sourceTree = ""; 240 | }; 241 | AECBA5F91B836BF20004A536 /* SwiftWeatherTests */ = { 242 | isa = PBXGroup; 243 | children = ( 244 | F3897F7A1C118C4F001609E2 /* UnitTests */, 245 | AECBA5FC1B836BF20004A536 /* Info.plist */, 246 | ); 247 | path = SwiftWeatherTests; 248 | sourceTree = ""; 249 | }; 250 | AECBA6041B836BF20004A536 /* SwiftWeatherUITests */ = { 251 | isa = PBXGroup; 252 | children = ( 253 | AECBA6051B836BF20004A536 /* SwiftWeatherUITests.swift */, 254 | AECBA6071B836BF20004A536 /* Info.plist */, 255 | ); 256 | path = SwiftWeatherUITests; 257 | sourceTree = ""; 258 | }; 259 | AEF61B291BA23C0500E8F259 /* Services */ = { 260 | isa = PBXGroup; 261 | children = ( 262 | CAB56A3D23F4A93BB599E4BA /* WeatherServiceProtocol.swift */, 263 | CAB56D46471CA243B1FD646F /* OpenWeatherMapService.swift */, 264 | ); 265 | name = Services; 266 | sourceTree = ""; 267 | }; 268 | AEF61B2B1BA23C2A00E8F259 /* Models */ = { 269 | isa = PBXGroup; 270 | children = ( 271 | AEBE64421B8D2370004A0814 /* Weather.swift */, 272 | AEEDF68C1B9F9B2900C6067B /* Temperature.swift */, 273 | EADFFD2F1CAEA22F008357FF /* TemperatureConverter.swift */, 274 | AE2C7D6B1BA028C000A7A714 /* WeatherIcon.swift */, 275 | AEEDF68A1B9F99F800C6067B /* WeatherBuilder.swift */, 276 | ); 277 | name = Models; 278 | sourceTree = ""; 279 | }; 280 | E117EDC87D7E5DC450870FDD /* Pods */ = { 281 | isa = PBXGroup; 282 | children = ( 283 | 900AFFBB41C69FC277CB0A33 /* Pods-SwiftWeather.debug.xcconfig */, 284 | E835C39D4B929F35B6A57B12 /* Pods-SwiftWeather.release.xcconfig */, 285 | BFCBC7111E6F5822A73508A5 /* Pods-Tests-SwiftWeatherTests.debug.xcconfig */, 286 | F4007F80070D3AAE1C564D8D /* Pods-Tests-SwiftWeatherTests.release.xcconfig */, 287 | 919B9EF9E5898277AD9C772A /* Pods-Tests-SwiftWeatherUITests.debug.xcconfig */, 288 | 53EEE101C82E858E57980710 /* Pods-Tests-SwiftWeatherUITests.release.xcconfig */, 289 | ); 290 | name = Pods; 291 | sourceTree = ""; 292 | }; 293 | F3897F7A1C118C4F001609E2 /* UnitTests */ = { 294 | isa = PBXGroup; 295 | children = ( 296 | F3897F7B1C118C4F001609E2 /* ForecastDateTimeSpec.swift */, 297 | F3897F7D1C118C6E001609E2 /* WeatherSpec.swift */, 298 | F3897F7F1C118D7D001609E2 /* ForecastSpec.swift */, 299 | F3897F811C118F0A001609E2 /* TemperatureSpec.swift */, 300 | F3897F831C11911B001609E2 /* WeatherIconSpec.swift */, 301 | F3FADA1A1C1327CB006D8551 /* WeatherBuilderSpec.swift */, 302 | ); 303 | path = UnitTests; 304 | sourceTree = ""; 305 | }; 306 | /* End PBXGroup section */ 307 | 308 | /* Begin PBXNativeTarget section */ 309 | AECBA5E11B836BF20004A536 /* SwiftWeather */ = { 310 | isa = PBXNativeTarget; 311 | buildConfigurationList = AECBA60A1B836BF20004A536 /* Build configuration list for PBXNativeTarget "SwiftWeather" */; 312 | buildPhases = ( 313 | 875372B40EF6130AA88D65AB /* [CP] Check Pods Manifest.lock */, 314 | AECBA5DE1B836BF20004A536 /* Sources */, 315 | AECBA5DF1B836BF20004A536 /* Frameworks */, 316 | AECBA5E01B836BF20004A536 /* Resources */, 317 | ADD0EBFC1C562E52002D8392 /* ShellScript */, 318 | 6691608BB84CB91D5EDADE36 /* [CP] Embed Pods Frameworks */, 319 | ); 320 | buildRules = ( 321 | ); 322 | dependencies = ( 323 | ); 324 | name = SwiftWeather; 325 | productName = SwiftWeather; 326 | productReference = AECBA5E21B836BF20004A536 /* SwiftWeather.app */; 327 | productType = "com.apple.product-type.application"; 328 | }; 329 | AECBA5F51B836BF20004A536 /* SwiftWeatherTests */ = { 330 | isa = PBXNativeTarget; 331 | buildConfigurationList = AECBA60D1B836BF20004A536 /* Build configuration list for PBXNativeTarget "SwiftWeatherTests" */; 332 | buildPhases = ( 333 | 2A25B97139655BE3B502C748 /* [CP] Check Pods Manifest.lock */, 334 | AECBA5F21B836BF20004A536 /* Sources */, 335 | AECBA5F31B836BF20004A536 /* Frameworks */, 336 | AECBA5F41B836BF20004A536 /* Resources */, 337 | 3211EE071253F1D6513A17BA /* [CP] Embed Pods Frameworks */, 338 | ); 339 | buildRules = ( 340 | ); 341 | dependencies = ( 342 | AECBA5F81B836BF20004A536 /* PBXTargetDependency */, 343 | ); 344 | name = SwiftWeatherTests; 345 | productName = SwiftWeatherTests; 346 | productReference = AECBA5F61B836BF20004A536 /* SwiftWeatherTests.xctest */; 347 | productType = "com.apple.product-type.bundle.unit-test"; 348 | }; 349 | AECBA6001B836BF20004A536 /* SwiftWeatherUITests */ = { 350 | isa = PBXNativeTarget; 351 | buildConfigurationList = AECBA6101B836BF20004A536 /* Build configuration list for PBXNativeTarget "SwiftWeatherUITests" */; 352 | buildPhases = ( 353 | 7E7F83383B6114FE2CC03BD6 /* [CP] Check Pods Manifest.lock */, 354 | AECBA5FD1B836BF20004A536 /* Sources */, 355 | AECBA5FE1B836BF20004A536 /* Frameworks */, 356 | AECBA5FF1B836BF20004A536 /* Resources */, 357 | EC9ED72913B1DE182823A53C /* [CP] Embed Pods Frameworks */, 358 | ); 359 | buildRules = ( 360 | ); 361 | dependencies = ( 362 | AECBA6031B836BF20004A536 /* PBXTargetDependency */, 363 | ); 364 | name = SwiftWeatherUITests; 365 | productName = SwiftWeatherUITests; 366 | productReference = AECBA6011B836BF20004A536 /* SwiftWeatherUITests.xctest */; 367 | productType = "com.apple.product-type.bundle.ui-testing"; 368 | }; 369 | /* End PBXNativeTarget section */ 370 | 371 | /* Begin PBXProject section */ 372 | AECBA5DA1B836BF20004A536 /* Project object */ = { 373 | isa = PBXProject; 374 | attributes = { 375 | LastSwiftUpdateCheck = 0710; 376 | LastUpgradeCheck = 0900; 377 | ORGANIZATIONNAME = "Jake Lin"; 378 | TargetAttributes = { 379 | AECBA5E11B836BF20004A536 = { 380 | CreatedOnToolsVersion = 7.0; 381 | DevelopmentTeam = 32GB2HU6K5; 382 | LastSwiftMigration = 0900; 383 | }; 384 | AECBA5F51B836BF20004A536 = { 385 | CreatedOnToolsVersion = 7.0; 386 | LastSwiftMigration = 0900; 387 | TestTargetID = AECBA5E11B836BF20004A536; 388 | }; 389 | AECBA6001B836BF20004A536 = { 390 | CreatedOnToolsVersion = 7.0; 391 | LastSwiftMigration = 0900; 392 | TestTargetID = AECBA5E11B836BF20004A536; 393 | }; 394 | }; 395 | }; 396 | buildConfigurationList = AECBA5DD1B836BF20004A536 /* Build configuration list for PBXProject "SwiftWeather" */; 397 | compatibilityVersion = "Xcode 3.2"; 398 | developmentRegion = English; 399 | hasScannedForEncodings = 0; 400 | knownRegions = ( 401 | en, 402 | Base, 403 | ); 404 | mainGroup = AECBA5D91B836BF20004A536; 405 | productRefGroup = AECBA5E31B836BF20004A536 /* Products */; 406 | projectDirPath = ""; 407 | projectRoot = ""; 408 | targets = ( 409 | AECBA5E11B836BF20004A536 /* SwiftWeather */, 410 | AECBA5F51B836BF20004A536 /* SwiftWeatherTests */, 411 | AECBA6001B836BF20004A536 /* SwiftWeatherUITests */, 412 | ); 413 | }; 414 | /* End PBXProject section */ 415 | 416 | /* Begin PBXResourcesBuildPhase section */ 417 | AECBA5E01B836BF20004A536 /* Resources */ = { 418 | isa = PBXResourcesBuildPhase; 419 | buildActionMask = 2147483647; 420 | files = ( 421 | AECBA5F01B836BF20004A536 /* LaunchScreen.storyboard in Resources */, 422 | AECBA5ED1B836BF20004A536 /* Assets.xcassets in Resources */, 423 | AE6C34E91B84742900F726C2 /* weathericons-regular-webfont.ttf in Resources */, 424 | AECBA5EB1B836BF20004A536 /* Main.storyboard in Resources */, 425 | AE26CCAB1B875C2400D518CB /* ForecastView.xib in Resources */, 426 | ); 427 | runOnlyForDeploymentPostprocessing = 0; 428 | }; 429 | AECBA5F41B836BF20004A536 /* Resources */ = { 430 | isa = PBXResourcesBuildPhase; 431 | buildActionMask = 2147483647; 432 | files = ( 433 | ); 434 | runOnlyForDeploymentPostprocessing = 0; 435 | }; 436 | AECBA5FF1B836BF20004A536 /* Resources */ = { 437 | isa = PBXResourcesBuildPhase; 438 | buildActionMask = 2147483647; 439 | files = ( 440 | ); 441 | runOnlyForDeploymentPostprocessing = 0; 442 | }; 443 | /* End PBXResourcesBuildPhase section */ 444 | 445 | /* Begin PBXShellScriptBuildPhase section */ 446 | 2A25B97139655BE3B502C748 /* [CP] Check Pods Manifest.lock */ = { 447 | isa = PBXShellScriptBuildPhase; 448 | buildActionMask = 2147483647; 449 | files = ( 450 | ); 451 | inputPaths = ( 452 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 453 | "${PODS_ROOT}/Manifest.lock", 454 | ); 455 | name = "[CP] Check Pods Manifest.lock"; 456 | outputPaths = ( 457 | "$(DERIVED_FILE_DIR)/Pods-Tests-SwiftWeatherTests-checkManifestLockResult.txt", 458 | ); 459 | runOnlyForDeploymentPostprocessing = 0; 460 | shellPath = /bin/sh; 461 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 462 | showEnvVarsInLog = 0; 463 | }; 464 | 3211EE071253F1D6513A17BA /* [CP] Embed Pods Frameworks */ = { 465 | isa = PBXShellScriptBuildPhase; 466 | buildActionMask = 2147483647; 467 | files = ( 468 | ); 469 | inputPaths = ( 470 | "${SRCROOT}/Pods/Target Support Files/Pods-Tests-SwiftWeatherTests/Pods-Tests-SwiftWeatherTests-frameworks.sh", 471 | "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", 472 | "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", 473 | ); 474 | name = "[CP] Embed Pods Frameworks"; 475 | outputPaths = ( 476 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", 477 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", 478 | ); 479 | runOnlyForDeploymentPostprocessing = 0; 480 | shellPath = /bin/sh; 481 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Tests-SwiftWeatherTests/Pods-Tests-SwiftWeatherTests-frameworks.sh\"\n"; 482 | showEnvVarsInLog = 0; 483 | }; 484 | 6691608BB84CB91D5EDADE36 /* [CP] Embed Pods Frameworks */ = { 485 | isa = PBXShellScriptBuildPhase; 486 | buildActionMask = 2147483647; 487 | files = ( 488 | ); 489 | inputPaths = ( 490 | "${SRCROOT}/Pods/Target Support Files/Pods-SwiftWeather/Pods-SwiftWeather-frameworks.sh", 491 | "${BUILT_PRODUCTS_DIR}/Bolts/Bolts.framework", 492 | "${BUILT_PRODUCTS_DIR}/FBSDKCoreKit/FBSDKCoreKit.framework", 493 | "${BUILT_PRODUCTS_DIR}/FBSDKShareKit/FBSDKShareKit.framework", 494 | "${BUILT_PRODUCTS_DIR}/FacebookCore/FacebookCore.framework", 495 | "${BUILT_PRODUCTS_DIR}/FacebookShare/FacebookShare.framework", 496 | "${BUILT_PRODUCTS_DIR}/SwiftyJSON/SwiftyJSON.framework", 497 | ); 498 | name = "[CP] Embed Pods Frameworks"; 499 | outputPaths = ( 500 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Bolts.framework", 501 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSDKCoreKit.framework", 502 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FBSDKShareKit.framework", 503 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FacebookCore.framework", 504 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FacebookShare.framework", 505 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyJSON.framework", 506 | ); 507 | runOnlyForDeploymentPostprocessing = 0; 508 | shellPath = /bin/sh; 509 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SwiftWeather/Pods-SwiftWeather-frameworks.sh\"\n"; 510 | showEnvVarsInLog = 0; 511 | }; 512 | 7E7F83383B6114FE2CC03BD6 /* [CP] Check Pods Manifest.lock */ = { 513 | isa = PBXShellScriptBuildPhase; 514 | buildActionMask = 2147483647; 515 | files = ( 516 | ); 517 | inputPaths = ( 518 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 519 | "${PODS_ROOT}/Manifest.lock", 520 | ); 521 | name = "[CP] Check Pods Manifest.lock"; 522 | outputPaths = ( 523 | "$(DERIVED_FILE_DIR)/Pods-Tests-SwiftWeatherUITests-checkManifestLockResult.txt", 524 | ); 525 | runOnlyForDeploymentPostprocessing = 0; 526 | shellPath = /bin/sh; 527 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 528 | showEnvVarsInLog = 0; 529 | }; 530 | 875372B40EF6130AA88D65AB /* [CP] Check Pods Manifest.lock */ = { 531 | isa = PBXShellScriptBuildPhase; 532 | buildActionMask = 2147483647; 533 | files = ( 534 | ); 535 | inputPaths = ( 536 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 537 | "${PODS_ROOT}/Manifest.lock", 538 | ); 539 | name = "[CP] Check Pods Manifest.lock"; 540 | outputPaths = ( 541 | "$(DERIVED_FILE_DIR)/Pods-SwiftWeather-checkManifestLockResult.txt", 542 | ); 543 | runOnlyForDeploymentPostprocessing = 0; 544 | shellPath = /bin/sh; 545 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 546 | showEnvVarsInLog = 0; 547 | }; 548 | ADD0EBFC1C562E52002D8392 /* ShellScript */ = { 549 | isa = PBXShellScriptBuildPhase; 550 | buildActionMask = 2147483647; 551 | files = ( 552 | ); 553 | inputPaths = ( 554 | ); 555 | outputPaths = ( 556 | ); 557 | runOnlyForDeploymentPostprocessing = 0; 558 | shellPath = /bin/sh; 559 | 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"; 560 | }; 561 | EC9ED72913B1DE182823A53C /* [CP] Embed Pods Frameworks */ = { 562 | isa = PBXShellScriptBuildPhase; 563 | buildActionMask = 2147483647; 564 | files = ( 565 | ); 566 | inputPaths = ( 567 | "${SRCROOT}/Pods/Target Support Files/Pods-Tests-SwiftWeatherUITests/Pods-Tests-SwiftWeatherUITests-frameworks.sh", 568 | "${BUILT_PRODUCTS_DIR}/Nimble/Nimble.framework", 569 | "${BUILT_PRODUCTS_DIR}/Quick/Quick.framework", 570 | ); 571 | name = "[CP] Embed Pods Frameworks"; 572 | outputPaths = ( 573 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Nimble.framework", 574 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Quick.framework", 575 | ); 576 | runOnlyForDeploymentPostprocessing = 0; 577 | shellPath = /bin/sh; 578 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Tests-SwiftWeatherUITests/Pods-Tests-SwiftWeatherUITests-frameworks.sh\"\n"; 579 | showEnvVarsInLog = 0; 580 | }; 581 | /* End PBXShellScriptBuildPhase section */ 582 | 583 | /* Begin PBXSourcesBuildPhase section */ 584 | AECBA5DE1B836BF20004A536 /* Sources */ = { 585 | isa = PBXSourcesBuildPhase; 586 | buildActionMask = 2147483647; 587 | files = ( 588 | AE0DC2CD1B8E7B3900E67147 /* Observable.swift in Sources */, 589 | AE2C7D6C1BA028C000A7A714 /* WeatherIcon.swift in Sources */, 590 | AECBA5E81B836BF20004A536 /* WeatherViewController.swift in Sources */, 591 | AEBE643B1B8D1F90004A0814 /* ForecastViewModel.swift in Sources */, 592 | AECBA5E61B836BF20004A536 /* AppDelegate.swift in Sources */, 593 | AEF61B281BA23B1200E8F259 /* ForecastDateTime.swift in Sources */, 594 | AEEDF6891B9F09E300C6067B /* Error.swift in Sources */, 595 | AEEDF68B1B9F99F800C6067B /* WeatherBuilder.swift in Sources */, 596 | AEBE64451B8D23B8004A0814 /* WeatherViewModel.swift in Sources */, 597 | AED4B2C21B876E8B0003D765 /* ForecastView.swift in Sources */, 598 | EADFFD301CAEA22F008357FF /* TemperatureConverter.swift in Sources */, 599 | AE09C4301B9723DE00C7CCED /* LocationService.swift in Sources */, 600 | AEBE64431B8D2370004A0814 /* Weather.swift in Sources */, 601 | AEBE643E1B8D2108004A0814 /* Forecast.swift in Sources */, 602 | CAB56F4267B3BC487990A92D /* OpenWeatherMapService.swift in Sources */, 603 | AEEDF68D1B9F9B2900C6067B /* Temperature.swift in Sources */, 604 | CAB565DDADB61DF9053BD50F /* WeatherServiceProtocol.swift in Sources */, 605 | ); 606 | runOnlyForDeploymentPostprocessing = 0; 607 | }; 608 | AECBA5F21B836BF20004A536 /* Sources */ = { 609 | isa = PBXSourcesBuildPhase; 610 | buildActionMask = 2147483647; 611 | files = ( 612 | F3897F7C1C118C4F001609E2 /* ForecastDateTimeSpec.swift in Sources */, 613 | F3897F821C118F0A001609E2 /* TemperatureSpec.swift in Sources */, 614 | F3897F7E1C118C6E001609E2 /* WeatherSpec.swift in Sources */, 615 | F3FADA1B1C1327CB006D8551 /* WeatherBuilderSpec.swift in Sources */, 616 | F3897F841C11911B001609E2 /* WeatherIconSpec.swift in Sources */, 617 | F3897F801C118D7D001609E2 /* ForecastSpec.swift in Sources */, 618 | ); 619 | runOnlyForDeploymentPostprocessing = 0; 620 | }; 621 | AECBA5FD1B836BF20004A536 /* Sources */ = { 622 | isa = PBXSourcesBuildPhase; 623 | buildActionMask = 2147483647; 624 | files = ( 625 | AECBA6061B836BF20004A536 /* SwiftWeatherUITests.swift in Sources */, 626 | ); 627 | runOnlyForDeploymentPostprocessing = 0; 628 | }; 629 | /* End PBXSourcesBuildPhase section */ 630 | 631 | /* Begin PBXTargetDependency section */ 632 | AECBA5F81B836BF20004A536 /* PBXTargetDependency */ = { 633 | isa = PBXTargetDependency; 634 | target = AECBA5E11B836BF20004A536 /* SwiftWeather */; 635 | targetProxy = AECBA5F71B836BF20004A536 /* PBXContainerItemProxy */; 636 | }; 637 | AECBA6031B836BF20004A536 /* PBXTargetDependency */ = { 638 | isa = PBXTargetDependency; 639 | target = AECBA5E11B836BF20004A536 /* SwiftWeather */; 640 | targetProxy = AECBA6021B836BF20004A536 /* PBXContainerItemProxy */; 641 | }; 642 | /* End PBXTargetDependency section */ 643 | 644 | /* Begin PBXVariantGroup section */ 645 | AECBA5E91B836BF20004A536 /* Main.storyboard */ = { 646 | isa = PBXVariantGroup; 647 | children = ( 648 | AECBA5EA1B836BF20004A536 /* Base */, 649 | ); 650 | name = Main.storyboard; 651 | sourceTree = ""; 652 | }; 653 | AECBA5EE1B836BF20004A536 /* LaunchScreen.storyboard */ = { 654 | isa = PBXVariantGroup; 655 | children = ( 656 | AECBA5EF1B836BF20004A536 /* Base */, 657 | ); 658 | name = LaunchScreen.storyboard; 659 | sourceTree = ""; 660 | }; 661 | /* End PBXVariantGroup section */ 662 | 663 | /* Begin XCBuildConfiguration section */ 664 | AECBA6081B836BF20004A536 /* Debug */ = { 665 | isa = XCBuildConfiguration; 666 | buildSettings = { 667 | ALWAYS_SEARCH_USER_PATHS = NO; 668 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 669 | CLANG_CXX_LIBRARY = "libc++"; 670 | CLANG_ENABLE_MODULES = YES; 671 | CLANG_ENABLE_OBJC_ARC = YES; 672 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 673 | CLANG_WARN_BOOL_CONVERSION = YES; 674 | CLANG_WARN_COMMA = YES; 675 | CLANG_WARN_CONSTANT_CONVERSION = YES; 676 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 677 | CLANG_WARN_EMPTY_BODY = YES; 678 | CLANG_WARN_ENUM_CONVERSION = YES; 679 | CLANG_WARN_INFINITE_RECURSION = YES; 680 | CLANG_WARN_INT_CONVERSION = YES; 681 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 682 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 683 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 684 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 685 | CLANG_WARN_STRICT_PROTOTYPES = YES; 686 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 687 | CLANG_WARN_UNREACHABLE_CODE = YES; 688 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 689 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 690 | COPY_PHASE_STRIP = NO; 691 | DEBUG_INFORMATION_FORMAT = dwarf; 692 | ENABLE_STRICT_OBJC_MSGSEND = YES; 693 | ENABLE_TESTABILITY = YES; 694 | GCC_C_LANGUAGE_STANDARD = gnu99; 695 | GCC_DYNAMIC_NO_PIC = NO; 696 | GCC_NO_COMMON_BLOCKS = YES; 697 | GCC_OPTIMIZATION_LEVEL = 0; 698 | GCC_PREPROCESSOR_DEFINITIONS = ( 699 | "DEBUG=1", 700 | "$(inherited)", 701 | ); 702 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 703 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 704 | GCC_WARN_UNDECLARED_SELECTOR = YES; 705 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 706 | GCC_WARN_UNUSED_FUNCTION = YES; 707 | GCC_WARN_UNUSED_VARIABLE = YES; 708 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 709 | MTL_ENABLE_DEBUG_INFO = YES; 710 | ONLY_ACTIVE_ARCH = YES; 711 | SDKROOT = iphoneos; 712 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 713 | SWIFT_VERSION = 3.0; 714 | TARGETED_DEVICE_FAMILY = "1,2"; 715 | }; 716 | name = Debug; 717 | }; 718 | AECBA6091B836BF20004A536 /* Release */ = { 719 | isa = XCBuildConfiguration; 720 | buildSettings = { 721 | ALWAYS_SEARCH_USER_PATHS = NO; 722 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 723 | CLANG_CXX_LIBRARY = "libc++"; 724 | CLANG_ENABLE_MODULES = YES; 725 | CLANG_ENABLE_OBJC_ARC = YES; 726 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 727 | CLANG_WARN_BOOL_CONVERSION = YES; 728 | CLANG_WARN_COMMA = YES; 729 | CLANG_WARN_CONSTANT_CONVERSION = YES; 730 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 731 | CLANG_WARN_EMPTY_BODY = YES; 732 | CLANG_WARN_ENUM_CONVERSION = YES; 733 | CLANG_WARN_INFINITE_RECURSION = YES; 734 | CLANG_WARN_INT_CONVERSION = YES; 735 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 736 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 737 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 738 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 739 | CLANG_WARN_STRICT_PROTOTYPES = YES; 740 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 741 | CLANG_WARN_UNREACHABLE_CODE = YES; 742 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 743 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 744 | COPY_PHASE_STRIP = NO; 745 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 746 | ENABLE_NS_ASSERTIONS = NO; 747 | ENABLE_STRICT_OBJC_MSGSEND = YES; 748 | GCC_C_LANGUAGE_STANDARD = gnu99; 749 | GCC_NO_COMMON_BLOCKS = YES; 750 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 751 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 752 | GCC_WARN_UNDECLARED_SELECTOR = YES; 753 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 754 | GCC_WARN_UNUSED_FUNCTION = YES; 755 | GCC_WARN_UNUSED_VARIABLE = YES; 756 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 757 | MTL_ENABLE_DEBUG_INFO = NO; 758 | SDKROOT = iphoneos; 759 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 760 | SWIFT_VERSION = 3.0; 761 | TARGETED_DEVICE_FAMILY = "1,2"; 762 | VALIDATE_PRODUCT = YES; 763 | }; 764 | name = Release; 765 | }; 766 | AECBA60B1B836BF20004A536 /* Debug */ = { 767 | isa = XCBuildConfiguration; 768 | baseConfigurationReference = 900AFFBB41C69FC277CB0A33 /* Pods-SwiftWeather.debug.xcconfig */; 769 | buildSettings = { 770 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 771 | DEVELOPMENT_TEAM = 32GB2HU6K5; 772 | INFOPLIST_FILE = SwiftWeather/Info.plist; 773 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 774 | PRODUCT_BUNDLE_IDENTIFIER = com.rushjet.SwiftWeather; 775 | PRODUCT_NAME = "$(TARGET_NAME)"; 776 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 777 | SWIFT_VERSION = 4.0; 778 | }; 779 | name = Debug; 780 | }; 781 | AECBA60C1B836BF20004A536 /* Release */ = { 782 | isa = XCBuildConfiguration; 783 | baseConfigurationReference = E835C39D4B929F35B6A57B12 /* Pods-SwiftWeather.release.xcconfig */; 784 | buildSettings = { 785 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 786 | DEVELOPMENT_TEAM = 32GB2HU6K5; 787 | INFOPLIST_FILE = SwiftWeather/Info.plist; 788 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 789 | PRODUCT_BUNDLE_IDENTIFIER = com.rushjet.SwiftWeather; 790 | PRODUCT_NAME = "$(TARGET_NAME)"; 791 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 792 | SWIFT_VERSION = 4.0; 793 | }; 794 | name = Release; 795 | }; 796 | AECBA60E1B836BF20004A536 /* Debug */ = { 797 | isa = XCBuildConfiguration; 798 | baseConfigurationReference = BFCBC7111E6F5822A73508A5 /* Pods-Tests-SwiftWeatherTests.debug.xcconfig */; 799 | buildSettings = { 800 | BUNDLE_LOADER = "$(TEST_HOST)"; 801 | CLANG_ENABLE_MODULES = YES; 802 | DEVELOPMENT_TEAM = ""; 803 | INFOPLIST_FILE = SwiftWeatherTests/Info.plist; 804 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 805 | PRODUCT_BUNDLE_IDENTIFIER = com.rushjet.SwiftWeatherTests; 806 | PRODUCT_NAME = "$(TARGET_NAME)"; 807 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 808 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 809 | SWIFT_VERSION = 4.0; 810 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftWeather.app/SwiftWeather"; 811 | }; 812 | name = Debug; 813 | }; 814 | AECBA60F1B836BF20004A536 /* Release */ = { 815 | isa = XCBuildConfiguration; 816 | baseConfigurationReference = F4007F80070D3AAE1C564D8D /* Pods-Tests-SwiftWeatherTests.release.xcconfig */; 817 | buildSettings = { 818 | BUNDLE_LOADER = "$(TEST_HOST)"; 819 | CLANG_ENABLE_MODULES = YES; 820 | DEVELOPMENT_TEAM = ""; 821 | INFOPLIST_FILE = SwiftWeatherTests/Info.plist; 822 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 823 | PRODUCT_BUNDLE_IDENTIFIER = com.rushjet.SwiftWeatherTests; 824 | PRODUCT_NAME = "$(TARGET_NAME)"; 825 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 826 | SWIFT_VERSION = 4.0; 827 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SwiftWeather.app/SwiftWeather"; 828 | }; 829 | name = Release; 830 | }; 831 | AECBA6111B836BF20004A536 /* Debug */ = { 832 | isa = XCBuildConfiguration; 833 | baseConfigurationReference = 919B9EF9E5898277AD9C772A /* Pods-Tests-SwiftWeatherUITests.debug.xcconfig */; 834 | buildSettings = { 835 | DEVELOPMENT_TEAM = ""; 836 | INFOPLIST_FILE = SwiftWeatherUITests/Info.plist; 837 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 838 | PRODUCT_BUNDLE_IDENTIFIER = com.rushjet.SwiftWeatherUITests; 839 | PRODUCT_NAME = "$(TARGET_NAME)"; 840 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 841 | SWIFT_VERSION = 4.0; 842 | TEST_TARGET_NAME = SwiftWeather; 843 | USES_XCTRUNNER = YES; 844 | }; 845 | name = Debug; 846 | }; 847 | AECBA6121B836BF20004A536 /* Release */ = { 848 | isa = XCBuildConfiguration; 849 | baseConfigurationReference = 53EEE101C82E858E57980710 /* Pods-Tests-SwiftWeatherUITests.release.xcconfig */; 850 | buildSettings = { 851 | DEVELOPMENT_TEAM = ""; 852 | INFOPLIST_FILE = SwiftWeatherUITests/Info.plist; 853 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 854 | PRODUCT_BUNDLE_IDENTIFIER = com.rushjet.SwiftWeatherUITests; 855 | PRODUCT_NAME = "$(TARGET_NAME)"; 856 | SWIFT_SWIFT3_OBJC_INFERENCE = Default; 857 | SWIFT_VERSION = 4.0; 858 | TEST_TARGET_NAME = SwiftWeather; 859 | USES_XCTRUNNER = YES; 860 | }; 861 | name = Release; 862 | }; 863 | /* End XCBuildConfiguration section */ 864 | 865 | /* Begin XCConfigurationList section */ 866 | AECBA5DD1B836BF20004A536 /* Build configuration list for PBXProject "SwiftWeather" */ = { 867 | isa = XCConfigurationList; 868 | buildConfigurations = ( 869 | AECBA6081B836BF20004A536 /* Debug */, 870 | AECBA6091B836BF20004A536 /* Release */, 871 | ); 872 | defaultConfigurationIsVisible = 0; 873 | defaultConfigurationName = Release; 874 | }; 875 | AECBA60A1B836BF20004A536 /* Build configuration list for PBXNativeTarget "SwiftWeather" */ = { 876 | isa = XCConfigurationList; 877 | buildConfigurations = ( 878 | AECBA60B1B836BF20004A536 /* Debug */, 879 | AECBA60C1B836BF20004A536 /* Release */, 880 | ); 881 | defaultConfigurationIsVisible = 0; 882 | defaultConfigurationName = Release; 883 | }; 884 | AECBA60D1B836BF20004A536 /* Build configuration list for PBXNativeTarget "SwiftWeatherTests" */ = { 885 | isa = XCConfigurationList; 886 | buildConfigurations = ( 887 | AECBA60E1B836BF20004A536 /* Debug */, 888 | AECBA60F1B836BF20004A536 /* Release */, 889 | ); 890 | defaultConfigurationIsVisible = 0; 891 | defaultConfigurationName = Release; 892 | }; 893 | AECBA6101B836BF20004A536 /* Build configuration list for PBXNativeTarget "SwiftWeatherUITests" */ = { 894 | isa = XCConfigurationList; 895 | buildConfigurations = ( 896 | AECBA6111B836BF20004A536 /* Debug */, 897 | AECBA6121B836BF20004A536 /* Release */, 898 | ); 899 | defaultConfigurationIsVisible = 0; 900 | defaultConfigurationName = Release; 901 | }; 902 | /* End XCConfigurationList section */ 903 | }; 904 | rootObject = AECBA5DA1B836BF20004A536 /* Project object */; 905 | } 906 | -------------------------------------------------------------------------------- /SwiftWeather.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SwiftWeather.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SwiftWeather/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SwiftWeather 4 | // 5 | // Created by Jake Lin on 8/18/15. 6 | // Copyright © 2015 Jake Lin. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | func application(_ application: UIApplication, 17 | didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // Sent when the application is about to move from active to inactive state. This can occur for 24 | // certain types of temporary interruptions (such as an incoming phone call or SMS message) or 25 | // when the user quits the application and it begins the transition to the background state. 26 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame 27 | // rates. Games should use this method to pause the game. 28 | } 29 | 30 | func applicationDidEnterBackground(_ application: UIApplication) { 31 | // Use this method to release shared resources, save user data, invalidate timers, and store 32 | // enough application state information to restore your application to its current state in case 33 | // it is terminated later. 34 | // If your application supports background execution, this method is called instead of 35 | // applicationWillTerminate: when the user quits. 36 | } 37 | 38 | func applicationWillEnterForeground(_ application: UIApplication) { 39 | // Called as part of the transition from the background to the inactive state; here you can undo 40 | // many of the changes made on entering the background. 41 | } 42 | 43 | func applicationDidBecomeActive(_ application: UIApplication) { 44 | // Restart any tasks that were paused (or not yet started) while the application was inactive. 45 | // If the application was previously in the background, optionally refresh the user interface. 46 | } 47 | 48 | func applicationWillTerminate(_ application: UIApplication) { 49 | // Called when the application is about to terminate. Save data if appropriate. See also 50 | // applicationDidEnterBackground:. 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /SwiftWeather/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /SwiftWeather/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SwiftWeather/Assets.xcassets/background.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "background.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftWeather/Assets.xcassets/background.imageset/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/SwiftWeather/Assets.xcassets/background.imageset/background.png -------------------------------------------------------------------------------- /SwiftWeather/Assets.xcassets/share.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "share.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /SwiftWeather/Assets.xcassets/share.imageset/share.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/SwiftWeather/Assets.xcassets/share.imageset/share.png -------------------------------------------------------------------------------- /SwiftWeather/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SwiftWeather/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | WeatherIcons-Regular 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 128 | 134 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /SwiftWeather/Error.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 9/8/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct SWError { 9 | enum Code: Int { 10 | case urlError = -6000 11 | case networkRequestFailed = -6001 12 | case jsonSerializationFailed = -6002 13 | case jsonParsingFailed = -6003 14 | case unableToFindLocation = -6004 15 | } 16 | 17 | let errorCode: Code 18 | } 19 | -------------------------------------------------------------------------------- /SwiftWeather/Forecast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 8/26/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct Forecast { 9 | let time: String 10 | let iconText: String 11 | let temperature: String 12 | } 13 | -------------------------------------------------------------------------------- /SwiftWeather/ForecastDateTime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 9/11/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct ForecastDateTime { 9 | let rawDate: Double 10 | let timeZone: TimeZone 11 | 12 | init(date: Double, timeZone: TimeZone) { 13 | self.timeZone = timeZone 14 | self.rawDate = date 15 | } 16 | 17 | var shortTime: String { 18 | let dateFormatter = DateFormatter() 19 | dateFormatter.timeZone = timeZone 20 | dateFormatter.dateFormat = "HH:mm" 21 | let date = Date(timeIntervalSince1970: rawDate) 22 | return dateFormatter.string(from: date) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /SwiftWeather/ForecastView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 8/22/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | @IBDesignable class ForecastView: UIView { 9 | // Our custom view from the XIB file 10 | var view: UIView! 11 | 12 | @IBOutlet weak var timeLabel: UILabel! 13 | @IBOutlet weak var iconLabel: UILabel! 14 | @IBOutlet weak var temperatureLabel: UILabel! 15 | 16 | // MARK: - init 17 | override init(frame: CGRect) { 18 | super.init(frame: frame) 19 | view = loadViewFromNib() 20 | } 21 | 22 | required init?(coder aDecoder: NSCoder) { 23 | super.init(coder: aDecoder) 24 | view = loadViewFromNib() 25 | } 26 | 27 | func loadViewFromNib() -> UIView { 28 | let bundle = Bundle(for: type(of: self)) 29 | let nib = UINib(nibName: nibName(), bundle: bundle) 30 | // swiftlint:disable force_cast 31 | let view = nib.instantiate(withOwner: self, options: nil)[0] as! UIView 32 | // swiftlint:enable force_cast 33 | 34 | view.frame = bounds 35 | view.autoresizingMask = [.flexibleWidth, .flexibleHeight] 36 | addSubview(view) 37 | return view 38 | } 39 | 40 | // MARK: - ViewModel 41 | var viewModel: ForecastViewModel? { 42 | didSet { 43 | viewModel?.time.observe { 44 | [unowned self] in 45 | self.timeLabel.text = $0 46 | } 47 | 48 | viewModel?.iconText.observe { 49 | [unowned self] in 50 | self.iconLabel.text = $0 51 | } 52 | 53 | viewModel?.temperature.observe { 54 | [unowned self] in 55 | self.temperatureLabel.text = $0 56 | } 57 | } 58 | } 59 | 60 | func loadViewModel(_ viewModel: ForecastViewModel) { 61 | self.viewModel = viewModel 62 | } 63 | 64 | // MARK: - IBInspectable 65 | @IBInspectable var time: String? { 66 | get { 67 | return timeLabel.text 68 | } 69 | 70 | set { 71 | timeLabel.text = newValue 72 | } 73 | } 74 | 75 | @IBInspectable var icon: String? { 76 | get { 77 | return iconLabel.text 78 | } 79 | 80 | set { 81 | iconLabel.text = newValue 82 | } 83 | } 84 | 85 | @IBInspectable var temperature: String? { 86 | get { 87 | return temperatureLabel.text 88 | } 89 | 90 | set { 91 | temperatureLabel.text = newValue 92 | } 93 | } 94 | 95 | @IBInspectable var timeColor: UIColor { 96 | get { 97 | return timeLabel.textColor 98 | } 99 | 100 | set { 101 | timeLabel.textColor = newValue 102 | } 103 | } 104 | 105 | @IBInspectable var iconColor: UIColor { 106 | get { 107 | return iconLabel.textColor 108 | } 109 | 110 | set { 111 | iconLabel.textColor = newValue 112 | } 113 | } 114 | 115 | @IBInspectable var temperatureColor: UIColor { 116 | get { 117 | return temperatureLabel.textColor 118 | } 119 | 120 | set { 121 | temperatureLabel.textColor = newValue 122 | } 123 | } 124 | 125 | @IBInspectable var bgColor: UIColor { 126 | get { 127 | return view.backgroundColor! 128 | } 129 | 130 | set { 131 | view.backgroundColor = newValue 132 | } 133 | } 134 | 135 | // MARK: - Private 136 | fileprivate func nibName() -> String { 137 | return String(describing: type(of: self)) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /SwiftWeather/ForecastView.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | WeatherIcons-Regular 9 | WeatherIcons-Regular 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 40 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /SwiftWeather/ForecastViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 8/26/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct ForecastViewModel { 9 | let time: Observable 10 | let iconText: Observable 11 | let temperature: Observable 12 | 13 | init(_ forecast: Forecast) { 14 | time = Observable(forecast.time) 15 | iconText = Observable(forecast.iconText) 16 | temperature = Observable(forecast.temperature) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SwiftWeather/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | NSLocationWhenInUseUsageDescription 11 | Location is required to retrieve the weather info for your current place. 12 | UIAppFonts 13 | 14 | weathericons-regular-webfont.ttf 15 | 16 | CFBundleDevelopmentRegion 17 | en 18 | CFBundleExecutable 19 | $(EXECUTABLE_NAME) 20 | CFBundleIdentifier 21 | $(PRODUCT_BUNDLE_IDENTIFIER) 22 | CFBundleInfoDictionaryVersion 23 | 6.0 24 | CFBundleName 25 | $(PRODUCT_NAME) 26 | CFBundlePackageType 27 | APPL 28 | CFBundleShortVersionString 29 | 3.0 30 | CFBundleSignature 31 | ???? 32 | CFBundleVersion 33 | 1 34 | LSRequiresIPhoneOS 35 | 36 | UILaunchStoryboardName 37 | LaunchScreen 38 | UIMainStoryboardFile 39 | Main 40 | UIRequiredDeviceCapabilities 41 | 42 | armv7 43 | 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | UIInterfaceOrientationLandscapeLeft 48 | UIInterfaceOrientationLandscapeRight 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | CFBundleURLTypes 58 | 59 | 60 | CFBundleURLSchemes 61 | 62 | fb1725220270856995 63 | 64 | 65 | 66 | FacebookAppID 67 | 1725220270856995 68 | FacebookDisplayName 69 | Swift Weather 70 | LSApplicationQueriesSchemes 71 | 72 | fbapi 73 | fb-messenger-share-api 74 | fbauth2 75 | fbshareextension 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /SwiftWeather/LocationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 9/2/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import CoreLocation 8 | 9 | protocol LocationServiceDelegate { 10 | func locationDidUpdate(_ service: LocationService, location: CLLocation) 11 | func locationDidFail(withError error: SWError) 12 | } 13 | 14 | class LocationService: NSObject { 15 | var delegate: LocationServiceDelegate? 16 | 17 | fileprivate let locationManager = CLLocationManager() 18 | 19 | override init() { 20 | super.init() 21 | locationManager.delegate = self 22 | locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters 23 | } 24 | 25 | func requestLocation() { 26 | locationManager.requestWhenInUseAuthorization() 27 | locationManager.requestLocation() 28 | } 29 | } 30 | 31 | // MARK: - CLLocationManager Delegate 32 | extension LocationService : CLLocationManagerDelegate { 33 | func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { 34 | if let location = locations.first { 35 | print("Current location: \(location)") 36 | delegate?.locationDidUpdate(self, location: location) 37 | } 38 | } 39 | 40 | func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { 41 | let swError = SWError(errorCode: .unableToFindLocation) 42 | delegate?.locationDidFail(withError: swError) 43 | print("Error finding location: \(error.localizedDescription)") 44 | } 45 | 46 | // requestWhenInUseAuthorization issue #70 47 | // Handle Authorization Status Changes 48 | func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) { 49 | if status == .authorizedWhenInUse { 50 | locationManager.requestLocation() 51 | } 52 | } 53 | 54 | @available(iOS 14.0, *) 55 | func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { 56 | if manager.authorizationStatus == .authorizedWhenInUse{ 57 | locationManager.requestLocation() 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /SwiftWeather/Observable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 8/27/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | class Observable { 9 | typealias Observer = (T) -> Void 10 | var observer: Observer? 11 | 12 | func observe(_ observer: Observer?) { 13 | self.observer = observer 14 | observer?(value) 15 | } 16 | 17 | var value: T { 18 | didSet { 19 | observer?(value) 20 | } 21 | } 22 | 23 | init(_ value: T) { 24 | self.value = value 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /SwiftWeather/OpenWeatherMapService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 9/2/15. 3 | // Copyright (c) 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import CoreLocation 8 | import SwiftyJSON 9 | 10 | struct OpenWeatherMapService: WeatherServiceProtocol { 11 | fileprivate let urlPath = "http://api.openweathermap.org/data/2.5/forecast" 12 | 13 | fileprivate func getFirstFourForecasts(_ json: JSON) -> [Forecast] { 14 | var forecasts: [Forecast] = [] 15 | 16 | for index in 0...3 { 17 | guard let forecastTempDegrees = json["list"][index]["main"]["temp"].double, 18 | let rawDateTime = json["list"][index]["dt"].double, 19 | let forecastCondition = json["list"][index]["weather"][0]["id"].int, 20 | let forecastIcon = json["list"][index]["weather"][0]["icon"].string else { 21 | break 22 | } 23 | 24 | let country = json["city"]["country"].string 25 | let forecastTemperature = Temperature(country: country!, 26 | openWeatherMapDegrees: forecastTempDegrees) 27 | let forecastTimeString = ForecastDateTime(date: rawDateTime, timeZone: TimeZone.current).shortTime 28 | let weatherIcon = WeatherIcon(condition: forecastCondition, iconString: forecastIcon) 29 | let forcastIconText = weatherIcon.iconText 30 | 31 | let forecast = Forecast(time: forecastTimeString, 32 | iconText: forcastIconText, 33 | temperature: forecastTemperature.degrees) 34 | 35 | forecasts.append(forecast) 36 | } 37 | 38 | return forecasts 39 | } 40 | 41 | func retrieveWeatherInfo(_ location: CLLocation, completionHandler: @escaping WeatherCompletionHandler) { 42 | let sessionConfig = URLSessionConfiguration.default 43 | let session = URLSession(configuration: sessionConfig) 44 | 45 | guard let url = generateRequestURL(location) else { 46 | let error = SWError(errorCode: .urlError) 47 | completionHandler(nil, error) 48 | return 49 | } 50 | 51 | let task = session.dataTask(with: url) { (data, response, error) in 52 | // Check network error 53 | guard error == nil else { 54 | let error = SWError(errorCode: .networkRequestFailed) 55 | completionHandler(nil, error) 56 | return 57 | } 58 | 59 | // Check JSON serialization error 60 | guard let data = data else { 61 | let error = SWError(errorCode: .jsonSerializationFailed) 62 | completionHandler(nil, error) 63 | return 64 | } 65 | 66 | guard let json = try? JSON(data: data) else { 67 | let error = SWError(errorCode: .jsonParsingFailed) 68 | completionHandler(nil, error) 69 | return 70 | } 71 | 72 | // Get temperature, location and icon and check parsing error 73 | guard let tempDegrees = json["list"][0]["main"]["temp"].double, 74 | let country = json["city"]["country"].string, 75 | let city = json["city"]["name"].string, 76 | let weatherCondition = json["list"][0]["weather"][0]["id"].int, 77 | let iconString = json["list"][0]["weather"][0]["icon"].string else { 78 | let error = SWError(errorCode: .jsonParsingFailed) 79 | completionHandler(nil, error) 80 | return 81 | } 82 | 83 | var weatherBuilder = WeatherBuilder() 84 | let temperature = Temperature(country: country, openWeatherMapDegrees:tempDegrees) 85 | weatherBuilder.temperature = temperature.degrees 86 | weatherBuilder.location = city 87 | 88 | let weatherIcon = WeatherIcon(condition: weatherCondition, iconString: iconString) 89 | weatherBuilder.iconText = weatherIcon.iconText 90 | 91 | weatherBuilder.forecasts = self.getFirstFourForecasts(json) 92 | 93 | completionHandler(weatherBuilder.build(), nil) 94 | } 95 | 96 | task.resume() 97 | } 98 | 99 | fileprivate func generateRequestURL(_ location: CLLocation) -> URL? { 100 | guard var components = URLComponents(string:urlPath) else { 101 | return nil 102 | } 103 | 104 | // get appId from Info.plist 105 | let filePath = Bundle.main.path(forResource: "Info", ofType: "plist")! 106 | let parameters = NSDictionary(contentsOfFile:filePath) 107 | let appId = parameters!["OWMAccessToken"]! as! String 108 | 109 | let latitude = String(location.coordinate.latitude) 110 | let longitude = String(location.coordinate.longitude) 111 | 112 | components.queryItems = [URLQueryItem(name:"lat", value:latitude), 113 | URLQueryItem(name:"lon", value:longitude), 114 | URLQueryItem(name:"appid", value:appId)] 115 | 116 | return components.url 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /SwiftWeather/Temperature.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 9/9/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct Temperature { 9 | let degrees: String 10 | 11 | init(country: String, openWeatherMapDegrees: Double) { 12 | if country == "US" { 13 | degrees = String(TemperatureConverter.kelvinToFahrenheit(openWeatherMapDegrees)) + "\u{f045}" 14 | } else { 15 | degrees = String(TemperatureConverter.kelvinToCelsius(openWeatherMapDegrees)) + "\u{f03c}" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /SwiftWeather/TemperatureConverter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Tiago Martinho on 4/1/16. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct TemperatureConverter { 9 | static func kelvinToCelsius(_ degrees: Double) -> Double { 10 | return round(degrees - 273.15) 11 | } 12 | 13 | static func kelvinToFahrenheit(_ degrees: Double) -> Double { 14 | return round(degrees * 9 / 5 - 459.67) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SwiftWeather/Weather.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 8/26/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct Weather { 9 | let location: String 10 | let iconText: String 11 | let temperature: String 12 | 13 | let forecasts: [Forecast] 14 | } 15 | -------------------------------------------------------------------------------- /SwiftWeather/WeatherBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 9/9/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | struct WeatherBuilder { 9 | var location: String? 10 | var iconText: String? 11 | var temperature: String? 12 | 13 | var forecasts: [Forecast]? 14 | 15 | func build() -> Weather { 16 | return Weather(location: location!, 17 | iconText: iconText!, 18 | temperature: temperature!, 19 | forecasts: forecasts!) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /SwiftWeather/WeatherIcon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 9/9/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | 8 | /* 9 | `WeatherIcon` is used to map Open Weather Map icon string to Weather Icons unicode string. 10 | It is generated by 11 | ``` 12 | var caseString = ''; 13 | var caseAndReturnString = ''; 14 | Array.prototype.forEach.call(document.styleSheets[1].cssRules,function(element){ 15 | if (element.selectorText && element.selectorText.startsWith('.wi-owm')) { 16 | var caseName = element.selectorText.substring(8, 17 | element.selectorText.indexOf('::before')).replace('-', '') 18 | caseString += 'case ' + caseName + ' = "' + caseName + '"\n'; 19 | caseAndReturnString += 'case .' + caseName + ': return "\\u{' 20 | + element.style['content'].charCodeAt(1).toString(16) + '}"\n' 21 | } 22 | }); 23 | console.log(caseString); 24 | console.log(caseAndReturnString); 25 | ``` 26 | */ 27 | // swiftlint:disable type_body_length 28 | struct WeatherIcon { 29 | let iconText: String 30 | 31 | enum IconType: String, CustomStringConvertible { 32 | case day200 = "day200" 33 | case day201 = "day201" 34 | case day202 = "day202" 35 | case day210 = "day210" 36 | case day211 = "day211" 37 | case day212 = "day212" 38 | case day221 = "day221" 39 | case day230 = "day230" 40 | case day231 = "day231" 41 | case day232 = "day232" 42 | case day300 = "day300" 43 | case day301 = "day301" 44 | case day302 = "day302" 45 | case day310 = "day310" 46 | case day311 = "day311" 47 | case day312 = "day312" 48 | case day313 = "day313" 49 | case day314 = "day314" 50 | case day321 = "day321" 51 | case day500 = "day500" 52 | case day501 = "day501" 53 | case day502 = "day502" 54 | case day503 = "day503" 55 | case day504 = "day504" 56 | case day511 = "day511" 57 | case day520 = "day520" 58 | case day521 = "day521" 59 | case day522 = "day522" 60 | case day531 = "day531" 61 | case day600 = "day600" 62 | case day601 = "day601" 63 | case day602 = "day602" 64 | case day611 = "day611" 65 | case day612 = "day612" 66 | case day615 = "day615" 67 | case day616 = "day616" 68 | case day620 = "day620" 69 | case day621 = "day621" 70 | case day622 = "day622" 71 | case day701 = "day701" 72 | case day711 = "day711" 73 | case day721 = "day721" 74 | case day731 = "day731" 75 | case day741 = "day741" 76 | case day761 = "day761" 77 | case day762 = "day762" 78 | case day781 = "day781" 79 | case day800 = "day800" 80 | case day801 = "day801" 81 | case day802 = "day802" 82 | case day803 = "day803" 83 | case day804 = "day804" 84 | case day900 = "day900" 85 | case day902 = "day902" 86 | case day903 = "day903" 87 | case day904 = "day904" 88 | case day906 = "day906" 89 | case day957 = "day957" 90 | case night200 = "night200" 91 | case night201 = "night201" 92 | case night202 = "night202" 93 | case night210 = "night210" 94 | case night211 = "night211" 95 | case night212 = "night212" 96 | case night221 = "night221" 97 | case night230 = "night230" 98 | case night231 = "night231" 99 | case night232 = "night232" 100 | case night300 = "night300" 101 | case night301 = "night301" 102 | case night302 = "night302" 103 | case night310 = "night310" 104 | case night311 = "night311" 105 | case night312 = "night312" 106 | case night313 = "night313" 107 | case night314 = "night314" 108 | case night321 = "night321" 109 | case night500 = "night500" 110 | case night501 = "night501" 111 | case night502 = "night502" 112 | case night503 = "night503" 113 | case night504 = "night504" 114 | case night511 = "night511" 115 | case night520 = "night520" 116 | case night521 = "night521" 117 | case night522 = "night522" 118 | case night531 = "night531" 119 | case night600 = "night600" 120 | case night601 = "night601" 121 | case night602 = "night602" 122 | case night611 = "night611" 123 | case night612 = "night612" 124 | case night615 = "night615" 125 | case night616 = "night616" 126 | case night620 = "night620" 127 | case night621 = "night621" 128 | case night622 = "night622" 129 | case night701 = "night701" 130 | case night711 = "night711" 131 | case night721 = "night721" 132 | case night731 = "night731" 133 | case night741 = "night741" 134 | case night761 = "night761" 135 | case night762 = "night762" 136 | case night781 = "night781" 137 | case night800 = "night800" 138 | case night801 = "night801" 139 | case night802 = "night802" 140 | case night803 = "night803" 141 | case night804 = "night804" 142 | case night900 = "night900" 143 | case night902 = "night902" 144 | case night903 = "night903" 145 | case night904 = "night904" 146 | case night906 = "night906" 147 | case night957 = "night957" 148 | 149 | var description: String { 150 | switch self { 151 | case .day200: return "\u{f010}" 152 | case .day201: return "\u{f010}" 153 | case .day202: return "\u{f010}" 154 | case .day210: return "\u{f005}" 155 | case .day211: return "\u{f005}" 156 | case .day212: return "\u{f005}" 157 | case .day221: return "\u{f005}" 158 | case .day230: return "\u{f010}" 159 | case .day231: return "\u{f010}" 160 | case .day232: return "\u{f010}" 161 | case .day300: return "\u{f00b}" 162 | case .day301: return "\u{f00b}" 163 | case .day302: return "\u{f008}" 164 | case .day310: return "\u{f008}" 165 | case .day311: return "\u{f008}" 166 | case .day312: return "\u{f008}" 167 | case .day313: return "\u{f008}" 168 | case .day314: return "\u{f008}" 169 | case .day321: return "\u{f00b}" 170 | case .day500: return "\u{f00b}" 171 | case .day501: return "\u{f008}" 172 | case .day502: return "\u{f008}" 173 | case .day503: return "\u{f008}" 174 | case .day504: return "\u{f008}" 175 | case .day511: return "\u{f006}" 176 | case .day520: return "\u{f009}" 177 | case .day521: return "\u{f009}" 178 | case .day522: return "\u{f009}" 179 | case .day531: return "\u{f00e}" 180 | case .day600: return "\u{f00a}" 181 | case .day601: return "\u{f0b2}" 182 | case .day602: return "\u{f00a}" 183 | case .day611: return "\u{f006}" 184 | case .day612: return "\u{f006}" 185 | case .day615: return "\u{f006}" 186 | case .day616: return "\u{f006}" 187 | case .day620: return "\u{f006}" 188 | case .day621: return "\u{f00a}" 189 | case .day622: return "\u{f00a}" 190 | case .day701: return "\u{f009}" 191 | case .day711: return "\u{f062}" 192 | case .day721: return "\u{f0b6}" 193 | case .day731: return "\u{f063}" 194 | case .day741: return "\u{f003}" 195 | case .day761: return "\u{f063}" 196 | case .day762: return "\u{f063}" 197 | case .day781: return "\u{f056}" 198 | case .day800: return "\u{f00d}" 199 | case .day801: return "\u{f000}" 200 | case .day802: return "\u{f000}" 201 | case .day803: return "\u{f000}" 202 | case .day804: return "\u{f00c}" 203 | case .day900: return "\u{f056}" 204 | case .day902: return "\u{f073}" 205 | case .day903: return "\u{f076}" 206 | case .day904: return "\u{f072}" 207 | case .day906: return "\u{f004}" 208 | case .day957: return "\u{f050}" 209 | case .night200: return "\u{f02d}" 210 | case .night201: return "\u{f02d}" 211 | case .night202: return "\u{f02d}" 212 | case .night210: return "\u{f025}" 213 | case .night211: return "\u{f025}" 214 | case .night212: return "\u{f025}" 215 | case .night221: return "\u{f025}" 216 | case .night230: return "\u{f02d}" 217 | case .night231: return "\u{f02d}" 218 | case .night232: return "\u{f02d}" 219 | case .night300: return "\u{f02b}" 220 | case .night301: return "\u{f02b}" 221 | case .night302: return "\u{f028}" 222 | case .night310: return "\u{f028}" 223 | case .night311: return "\u{f028}" 224 | case .night312: return "\u{f028}" 225 | case .night313: return "\u{f028}" 226 | case .night314: return "\u{f028}" 227 | case .night321: return "\u{f02b}" 228 | case .night500: return "\u{f02b}" 229 | case .night501: return "\u{f028}" 230 | case .night502: return "\u{f028}" 231 | case .night503: return "\u{f028}" 232 | case .night504: return "\u{f028}" 233 | case .night511: return "\u{f026}" 234 | case .night520: return "\u{f029}" 235 | case .night521: return "\u{f029}" 236 | case .night522: return "\u{f029}" 237 | case .night531: return "\u{f02c}" 238 | case .night600: return "\u{f02a}" 239 | case .night601: return "\u{f0b4}" 240 | case .night602: return "\u{f02a}" 241 | case .night611: return "\u{f026}" 242 | case .night612: return "\u{f026}" 243 | case .night615: return "\u{f026}" 244 | case .night616: return "\u{f026}" 245 | case .night620: return "\u{f026}" 246 | case .night621: return "\u{f02a}" 247 | case .night622: return "\u{f02a}" 248 | case .night701: return "\u{f029}" 249 | case .night711: return "\u{f062}" 250 | case .night721: return "\u{f0b6}" 251 | case .night731: return "\u{f063}" 252 | case .night741: return "\u{f04a}" 253 | case .night761: return "\u{f063}" 254 | case .night762: return "\u{f063}" 255 | case .night781: return "\u{f056}" 256 | case .night800: return "\u{f02e}" 257 | case .night801: return "\u{f022}" 258 | case .night802: return "\u{f022}" 259 | case .night803: return "\u{f022}" 260 | case .night804: return "\u{f086}" 261 | case .night900: return "\u{f056}" 262 | case .night902: return "\u{f073}" 263 | case .night903: return "\u{f076}" 264 | case .night904: return "\u{f072}" 265 | case .night906: return "\u{f024}" 266 | case .night957: return "\u{f050}" 267 | } 268 | } 269 | } 270 | 271 | init(condition: Int, iconString: String) { 272 | var rawValue: String 273 | 274 | // if iconString has 'n', it means night time. 275 | if iconString.range(of: "n") != nil { 276 | rawValue = "night" + String(condition) 277 | } else { 278 | // day time 279 | rawValue = "day" + String(condition) 280 | } 281 | 282 | guard let iconType = IconType(rawValue: rawValue) else { 283 | iconText = "" 284 | return 285 | } 286 | iconText = iconType.description 287 | } 288 | } 289 | // swiftlint:enable type_body_length 290 | -------------------------------------------------------------------------------- /SwiftWeather/WeatherServiceProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 9/2/15. 3 | // Copyright (c) 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import CoreLocation 8 | 9 | typealias WeatherCompletionHandler = (Weather?, SWError?) -> Void 10 | 11 | protocol WeatherServiceProtocol { 12 | func retrieveWeatherInfo(_ location: CLLocation, completionHandler: @escaping WeatherCompletionHandler) 13 | } 14 | -------------------------------------------------------------------------------- /SwiftWeather/WeatherViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 8/18/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | import FacebookShare 8 | import CoreSpotlight 9 | import MobileCoreServices 10 | 11 | //MARK: - UIViewController Properties 12 | class WeatherViewController: UIViewController { 13 | 14 | //MARK: - IBOutlets 15 | @IBOutlet weak var locationLabel: UILabel! 16 | @IBOutlet weak var iconLabel: UILabel! 17 | @IBOutlet weak var temperatureLabel: UILabel! 18 | @IBOutlet var forecastViews: [ForecastView]! 19 | 20 | let identifier = "WeatherIdentifier" 21 | 22 | //MARK: - Super Methods 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | viewModel = WeatherViewModel() 26 | viewModel?.startLocationService() 27 | setAccessibilityIdentifiers() 28 | } 29 | 30 | override func viewWillAppear(_ animated: Bool) { 31 | super.viewWillAppear(animated) 32 | configureLabels() 33 | } 34 | 35 | override func viewDidAppear(_ animated: Bool) { 36 | super.viewDidAppear(animated) 37 | configureLabelsWithAnimation() 38 | } 39 | 40 | //MARK: ViewModel 41 | var viewModel: WeatherViewModel? { 42 | didSet { 43 | setLocationLabel() 44 | viewModel?.iconText.observe { 45 | [unowned self] in 46 | self.iconLabel.text = $0 47 | } 48 | 49 | viewModel?.temperature.observe { 50 | [unowned self] in 51 | self.temperatureLabel.text = $0 52 | } 53 | setForcastView() 54 | } 55 | } 56 | 57 | //MARK: Accessibility 58 | func setAccessibilityIdentifiers() { 59 | locationLabel.accessibilityIdentifier = "a11y_current_city" 60 | iconLabel.accessibilityIdentifier = "a11y_wheather_icon" 61 | temperatureLabel.accessibilityIdentifier = "a11y_wheather_temperature" 62 | } 63 | 64 | //MARK: Functions 65 | func configureLabels(){ 66 | locationLabel.center.x -= view.bounds.width 67 | iconLabel.center.x -= view.bounds.width 68 | temperatureLabel.center.x -= view.bounds.width 69 | iconLabel.alpha = 0.0 70 | locationLabel.alpha = 0.0 71 | temperatureLabel.alpha = 0.0 72 | } 73 | 74 | func configureLabelsWithAnimation(){ 75 | UIView.animate(withDuration: 0.5, animations: { 76 | self.locationLabel.center.x += self.view.bounds.width 77 | }) 78 | 79 | UIView.animate(withDuration: 0.5, delay: 0.3, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: { 80 | self.iconLabel.center.x += self.view.bounds.width 81 | }, completion: nil) 82 | 83 | UIView.animate(withDuration: 0.5, delay: 0.4, usingSpringWithDamping: 0.2, initialSpringVelocity: 0.0, options: [], animations: { 84 | self.temperatureLabel.center.x += self.view.bounds.width 85 | }, completion: nil) 86 | 87 | UIView.animate(withDuration: 0.5, delay: 0.3, options: [], animations: { 88 | self.iconLabel.alpha = 1.0 89 | }, completion: nil) 90 | 91 | UIView.animate(withDuration: 0.5, delay: 0.4, options: [], animations: { 92 | self.locationLabel.alpha = 1.0 93 | }, completion: nil) 94 | 95 | UIView.animate(withDuration: 0.5, delay: 0.5, options: [], animations: { 96 | self.temperatureLabel.alpha = 1.0 97 | }, completion: nil) 98 | } 99 | 100 | //MARK: Actions 101 | @IBAction func shareButtonPressed(_ sender: Any) { 102 | shareOnFacebook() 103 | } 104 | 105 | func shareOnFacebook(){ 106 | let photo = Photo(image: #imageLiteral(resourceName: "background"), userGenerated: false) 107 | let myContent = PhotoShareContent(photos: [photo]) 108 | let shareDialog = ShareDialog(content: myContent) 109 | shareDialog.mode = .native 110 | shareDialog.failsOnInvalidData = true 111 | 112 | try? shareDialog.show() 113 | } 114 | 115 | //MARK: Private Functions 116 | private func setLocationLabel() { 117 | viewModel?.location.observe { 118 | [unowned self] in 119 | self.locationLabel.text = $0 120 | 121 | let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeText as String) 122 | attributeSet.title = self.locationLabel.text 123 | 124 | let item = CSSearchableItem(uniqueIdentifier: self.identifier, domainIdentifier: "com.rushjet.SwiftWeather", attributeSet: attributeSet) 125 | CSSearchableIndex.default().indexSearchableItems([item]){error in 126 | if let error = error { 127 | print("Indexing error: \(error.localizedDescription)") 128 | } else { 129 | print("Location item successfully indexed") 130 | } 131 | } 132 | } 133 | } 134 | 135 | private func setForcastView() { 136 | viewModel?.forecasts.observe { 137 | [unowned self] (forecastViewModels) in 138 | if forecastViewModels.count >= 4 { 139 | for (index, forecastView) in self.forecastViews.enumerated() { 140 | forecastView.loadViewModel(forecastViewModels[index]) 141 | } 142 | } 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /SwiftWeather/WeatherViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 8/26/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Foundation 7 | import CoreLocation 8 | 9 | class WeatherViewModel { 10 | // MARK: - Constants 11 | fileprivate let emptyString = "" 12 | 13 | // MARK: - Properties 14 | let hasError: Observable 15 | let errorMessage: Observable 16 | 17 | let location: Observable 18 | let iconText: Observable 19 | let temperature: Observable 20 | let forecasts: Observable<[ForecastViewModel]> 21 | 22 | // MARK: - Services 23 | fileprivate var locationService: LocationService 24 | fileprivate var weatherService: WeatherServiceProtocol 25 | 26 | // MARK: - init 27 | init() { 28 | hasError = Observable(false) 29 | errorMessage = Observable(nil) 30 | 31 | location = Observable(emptyString) 32 | iconText = Observable(emptyString) 33 | temperature = Observable(emptyString) 34 | forecasts = Observable([]) 35 | 36 | // Can put Dependency Injection here 37 | locationService = LocationService() 38 | weatherService = OpenWeatherMapService() 39 | } 40 | 41 | // MARK: - public 42 | func startLocationService() { 43 | locationService.delegate = self 44 | locationService.requestLocation() 45 | } 46 | 47 | // MARK: - private 48 | fileprivate func update(_ weather: Weather) { 49 | hasError.value = false 50 | errorMessage.value = nil 51 | 52 | location.value = weather.location 53 | iconText.value = weather.iconText 54 | temperature.value = weather.temperature 55 | 56 | let tempForecasts = weather.forecasts.map { forecast in 57 | return ForecastViewModel(forecast) 58 | } 59 | forecasts.value = tempForecasts 60 | } 61 | 62 | fileprivate func update(_ error: SWError) { 63 | hasError.value = true 64 | 65 | switch error.errorCode { 66 | case .urlError: 67 | errorMessage.value = "The weather service is not working." 68 | case .networkRequestFailed: 69 | errorMessage.value = "The network appears to be down." 70 | case .jsonSerializationFailed: 71 | errorMessage.value = "We're having trouble processing weather data." 72 | case .jsonParsingFailed: 73 | errorMessage.value = "We're having trouble parsing weather data." 74 | case .unableToFindLocation: 75 | errorMessage.value = "We're having trouble getting user location." 76 | } 77 | 78 | location.value = emptyString 79 | iconText.value = emptyString 80 | temperature.value = emptyString 81 | self.forecasts.value = [] 82 | } 83 | } 84 | 85 | // MARK: LocationServiceDelegate 86 | extension WeatherViewModel: LocationServiceDelegate { 87 | func locationDidUpdate(_ service: LocationService, location: CLLocation) { 88 | weatherService.retrieveWeatherInfo(location) { (weather, error) -> Void in 89 | DispatchQueue.main.async(execute: { 90 | if let unwrappedError = error { 91 | print(unwrappedError) 92 | self.update(unwrappedError) 93 | return 94 | } 95 | 96 | guard let unwrappedWeather = weather else { 97 | return 98 | } 99 | self.update(unwrappedWeather) 100 | }) 101 | } 102 | } 103 | 104 | func locationDidFail(withError error: SWError) { 105 | self.update(error) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /SwiftWeather/fonts/weathericons-regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/SwiftWeather/fonts/weathericons-regular-webfont.ttf -------------------------------------------------------------------------------- /SwiftWeatherTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftWeatherTests/UnitTests/ForecastDateTimeSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Tran Xuan Hoang on 12/4/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Quick 7 | import Nimble 8 | @testable import SwiftWeather 9 | 10 | class ForecastDateTimeSpec: QuickSpec { 11 | 12 | private let testTimeZone = TimeZone(abbreviation: "UTC+11:00")! 13 | 14 | override func spec() { 15 | describe("#init") { 16 | it("should init with the rawDate correctly assigned") { 17 | var forecastDateTime = ForecastDateTime(date: 1488096060, timeZone: self.testTimeZone) 18 | expect(forecastDateTime.rawDate).to(beCloseTo(1488096060)) 19 | forecastDateTime = ForecastDateTime(date: 0, timeZone: self.testTimeZone) 20 | expect(forecastDateTime.rawDate).to(beCloseTo(0)) 21 | } 22 | } 23 | 24 | describe("#shortTime") { 25 | it("should return the correct shortTime string with format HH:mm") { 26 | var forecastDateTime = ForecastDateTime(date: 1488096060, timeZone: self.testTimeZone) 27 | expect(forecastDateTime.shortTime).to(equal("7:01 PM")) 28 | forecastDateTime = ForecastDateTime(date: 1488103200, timeZone: self.testTimeZone) 29 | expect(forecastDateTime.shortTime).to(equal("9:00 PM")) 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /SwiftWeatherTests/UnitTests/ForecastSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Tran Xuan Hoang on 12/4/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Quick 7 | import Nimble 8 | @testable import SwiftWeather 9 | 10 | class ForecastSpec: QuickSpec { 11 | 12 | override func spec() { 13 | 14 | describe("#init") { 15 | it("should have time, iconText, temperature") { 16 | let forecast = Forecast(time: "time", iconText: "iconText", temperature: "temperature") 17 | expect(forecast.time).to(equal("time")) 18 | expect(forecast.iconText).to(equal("iconText")) 19 | expect(forecast.temperature).to(equal("temperature")) 20 | } 21 | } 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /SwiftWeatherTests/UnitTests/TemperatureSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Tran Xuan Hoang on 12/4/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Quick 7 | import Nimble 8 | @testable import SwiftWeather 9 | 10 | class TemperatureSpec: QuickSpec { 11 | 12 | override func spec() { 13 | 14 | describe("#init(country:openWeatherMapDegrees:)") { 15 | context("country is US") { 16 | it("should convert temperature to Fahrenheit") { 17 | let temperature = Temperature(country: "US", openWeatherMapDegrees: 20) 18 | expect(temperature.degrees).to(equal("-424.0" + "\u{f045}")) 19 | } 20 | } 21 | context("country is not US") { 22 | it("should convert to Celsius") { 23 | let temperature = Temperature(country: "ABC", openWeatherMapDegrees: 20) 24 | expect(temperature.degrees).to(equal("-253.0" + "\u{f03c}")) 25 | } 26 | } 27 | } 28 | 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /SwiftWeatherTests/UnitTests/WeatherBuilderSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Tran Xuan Hoang on 12/5/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Quick 7 | import Nimble 8 | @testable import SwiftWeather 9 | 10 | class WeatherBuilderSpec: QuickSpec { 11 | 12 | override func spec() { 13 | 14 | describe("#build") { 15 | it("should create a Weather model with all the properties correctly") { 16 | let builder = WeatherBuilder(location: "location", iconText: "iconText", 17 | temperature: "temperature", forecasts: []) 18 | let weather = builder.build() 19 | expect(weather.location).to(equal("location")) 20 | expect(weather.iconText).to(equal("iconText")) 21 | expect(weather.temperature).to(equal("temperature")) 22 | expect(weather.forecasts.count).to(equal(0)) 23 | } 24 | } 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /SwiftWeatherTests/UnitTests/WeatherIconSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Tran Xuan Hoang on 12/4/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Quick 7 | import Nimble 8 | @testable import SwiftWeather 9 | 10 | class WeatherIconSpec: QuickSpec { 11 | 12 | let dayDictionary = [ 13 | 200: "\u{f010}", 14 | 201: "\u{f010}", 15 | 202: "\u{f010}", 16 | 210: "\u{f005}", 17 | 211: "\u{f005}", 18 | 212: "\u{f005}", 19 | 221: "\u{f005}", 20 | 230: "\u{f010}", 21 | 231: "\u{f010}", 22 | 232: "\u{f010}", 23 | 300: "\u{f00b}", 24 | 301: "\u{f00b}", 25 | 302: "\u{f008}", 26 | 310: "\u{f008}", 27 | 311: "\u{f008}", 28 | 312: "\u{f008}", 29 | 313: "\u{f008}", 30 | 314: "\u{f008}", 31 | 321: "\u{f00b}", 32 | 500: "\u{f00b}", 33 | 501: "\u{f008}", 34 | 502: "\u{f008}", 35 | 503: "\u{f008}", 36 | 504: "\u{f008}", 37 | 511: "\u{f006}", 38 | 520: "\u{f009}", 39 | 521: "\u{f009}", 40 | 522: "\u{f009}", 41 | 531: "\u{f00e}", 42 | 600: "\u{f00a}", 43 | 601: "\u{f0b2}", 44 | 602: "\u{f00a}", 45 | 611: "\u{f006}", 46 | 612: "\u{f006}", 47 | 615: "\u{f006}", 48 | 616: "\u{f006}", 49 | 620: "\u{f006}", 50 | 621: "\u{f00a}", 51 | 622: "\u{f00a}", 52 | 701: "\u{f009}", 53 | 711: "\u{f062}", 54 | 721: "\u{f0b6}", 55 | 731: "\u{f063}", 56 | 741: "\u{f003}", 57 | 761: "\u{f063}", 58 | 762: "\u{f063}", 59 | 781: "\u{f056}", 60 | 800: "\u{f00d}", 61 | 801: "\u{f000}", 62 | 802: "\u{f000}", 63 | 803: "\u{f000}", 64 | 804: "\u{f00c}", 65 | 900: "\u{f056}", 66 | 902: "\u{f073}", 67 | 903: "\u{f076}", 68 | 904: "\u{f072}", 69 | 906: "\u{f004}", 70 | 957: "\u{f050}" 71 | ] 72 | let nightDictionary = [ 73 | 200: "\u{f02d}", 74 | 201: "\u{f02d}", 75 | 202: "\u{f02d}", 76 | 210: "\u{f025}", 77 | 211: "\u{f025}", 78 | 212: "\u{f025}", 79 | 221: "\u{f025}", 80 | 230: "\u{f02d}", 81 | 231: "\u{f02d}", 82 | 232: "\u{f02d}", 83 | 300: "\u{f02b}", 84 | 301: "\u{f02b}", 85 | 302: "\u{f028}", 86 | 310: "\u{f028}", 87 | 311: "\u{f028}", 88 | 312: "\u{f028}", 89 | 313: "\u{f028}", 90 | 314: "\u{f028}", 91 | 321: "\u{f02b}", 92 | 500: "\u{f02b}", 93 | 501: "\u{f028}", 94 | 502: "\u{f028}", 95 | 503: "\u{f028}", 96 | 504: "\u{f028}", 97 | 511: "\u{f026}", 98 | 520: "\u{f029}", 99 | 521: "\u{f029}", 100 | 522: "\u{f029}", 101 | 531: "\u{f02c}", 102 | 600: "\u{f02a}", 103 | 601: "\u{f0b4}", 104 | 602: "\u{f02a}", 105 | 611: "\u{f026}", 106 | 612: "\u{f026}", 107 | 615: "\u{f026}", 108 | 616: "\u{f026}", 109 | 620: "\u{f026}", 110 | 621: "\u{f02a}", 111 | 622: "\u{f02a}", 112 | 701: "\u{f029}", 113 | 711: "\u{f062}", 114 | 721: "\u{f0b6}", 115 | 731: "\u{f063}", 116 | 741: "\u{f04a}", 117 | 761: "\u{f063}", 118 | 762: "\u{f063}", 119 | 781: "\u{f056}", 120 | 800: "\u{f02e}", 121 | 801: "\u{f022}", 122 | 802: "\u{f022}", 123 | 803: "\u{f022}", 124 | 804: "\u{f086}", 125 | 900: "\u{f056}", 126 | 902: "\u{f073}", 127 | 903: "\u{f076}", 128 | 904: "\u{f072}", 129 | 906: "\u{f024}", 130 | 957: "\u{f050}" 131 | ] 132 | 133 | override func spec() { 134 | 135 | describe("#init(condition:,iconString:)") { 136 | context("day") { 137 | context("invalid condition Int") { 138 | self.expectWeatherIconWithCondition(999, isDay: true, toHaveIconTextEqualToString: "") 139 | } 140 | context("valid condition Int") { 141 | for (condition, description) in self.dayDictionary { 142 | self.expectWeatherIconWithCondition(condition, isDay: true, 143 | toHaveIconTextEqualToString: description) 144 | } 145 | } 146 | } 147 | context("night") { 148 | context("invalid condition Int") { 149 | self.expectWeatherIconWithCondition(999, isDay: false, toHaveIconTextEqualToString: "") 150 | } 151 | context("valid condition Int") { 152 | for (condition, description) in self.nightDictionary { 153 | self.expectWeatherIconWithCondition(condition, isDay: false, 154 | toHaveIconTextEqualToString: description) 155 | } 156 | } 157 | } 158 | } 159 | 160 | } 161 | 162 | } 163 | 164 | extension WeatherIconSpec { 165 | 166 | func expectWeatherIconWithCondition(_ condition: Int, isDay: Bool, 167 | toHaveIconTextEqualToString description: String) { 168 | let weatherIcon = WeatherIcon(condition: condition, iconString: isDay ? "day" : "night") 169 | expect(weatherIcon.iconText).to(equal(description)) 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /SwiftWeatherTests/UnitTests/WeatherSpec.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Tran Xuan Hoang on 12/4/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import Quick 7 | import Nimble 8 | @testable import SwiftWeather 9 | 10 | class WeatherSpec: QuickSpec { 11 | 12 | override func spec() { 13 | 14 | describe("#init") { 15 | it("should have location, iconText, temperature and forecasts") { 16 | let weather = Weather(location: "location", iconText: "iconText", 17 | temperature: "temperature", forecasts: []) 18 | expect(weather.location).to(equal("location")) 19 | expect(weather.iconText).to(equal("iconText")) 20 | expect(weather.temperature).to(equal("temperature")) 21 | expect(weather.forecasts.count).to(equal(0)) 22 | } 23 | } 24 | 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /SwiftWeatherUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SwiftWeatherUITests/SwiftWeatherUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jake Lin on 8/18/15. 3 | // Copyright © 2015 Jake Lin. All rights reserved. 4 | // 5 | 6 | import XCTest 7 | import Quick 8 | import Nimble 9 | 10 | class SwiftWeatherUITests: QuickSpec { 11 | 12 | override func spec() { 13 | let app = XCUIApplication() 14 | 15 | beforeSuite { 16 | self.continueAfterFailure = false 17 | 18 | app.launch() 19 | } 20 | 21 | describe("a wheather viewcontroller") { 22 | context("location service is enabled") { 23 | context("when in portrait") { 24 | beforeEach { 25 | XCUIDevice.shared.orientation = .portrait 26 | } 27 | itBehavesLike("a properly laidout wheather viewcontroller") 28 | } 29 | 30 | context("when in landscape") { 31 | beforeEach { 32 | XCUIDevice.shared.orientation = .landscapeLeft 33 | } 34 | itBehavesLike("a properly laidout wheather viewcontroller") 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | class RegularWheatherViewControllerConfiguration: QuickConfiguration { 42 | override class func configure(_ configuration: Configuration) { 43 | let app = XCUIApplication() 44 | let window = app.windows.element(boundBy: 0) 45 | 46 | sharedExamples("a properly laidout wheather viewcontroller") { (context: SharedExampleContext) in 47 | it("shows city") { 48 | let cityLabel = app.staticTexts["a11y_current_city"] 49 | 50 | expect(cityLabel.exists).to(beTruthy()) 51 | expect(window.frame.contains(cityLabel.frame)).to(beTruthy()) 52 | } 53 | 54 | it("shows wheather icon") { 55 | let wheatherIconLabel = app.staticTexts["a11y_wheather_icon"] 56 | 57 | expect(wheatherIconLabel.exists).to(beTruthy()) 58 | expect(window.frame.contains(wheatherIconLabel.frame)).to(beTruthy()) 59 | } 60 | 61 | it("shows wheather temperature") { 62 | let wheatherTemperatureLabel = app.staticTexts["a11y_wheather_temperature"] 63 | 64 | expect(wheatherTemperatureLabel.exists).to(beTruthy()) 65 | expect(window.frame.contains(wheatherTemperatureLabel.frame)).to(beTruthy()) 66 | } 67 | } 68 | } 69 | } 70 | 71 | -------------------------------------------------------------------------------- /buddybuild_postclone.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | cd /Users/buddybuild/workspace 3 | 4 | mkdir .access_tokens 5 | echo $OPENWEATHERMAP > .access_tokens/openweathermap 6 | -------------------------------------------------------------------------------- /screenshots/4s-fullsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/4s-fullsize.png -------------------------------------------------------------------------------- /screenshots/4s-smallsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/4s-smallsize.png -------------------------------------------------------------------------------- /screenshots/5s-fullsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/5s-fullsize.png -------------------------------------------------------------------------------- /screenshots/5s-smallsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/5s-smallsize.png -------------------------------------------------------------------------------- /screenshots/6-Today-fullsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/6-Today-fullsize.png -------------------------------------------------------------------------------- /screenshots/6-Today-smallsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/6-Today-smallsize.png -------------------------------------------------------------------------------- /screenshots/6-fullsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/6-fullsize.png -------------------------------------------------------------------------------- /screenshots/6-smallsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/6-smallsize.png -------------------------------------------------------------------------------- /screenshots/6plus-fullsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/6plus-fullsize.png -------------------------------------------------------------------------------- /screenshots/6plus-smallsize.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/6plus-smallsize.png -------------------------------------------------------------------------------- /screenshots/Custom-UIView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/Custom-UIView.png -------------------------------------------------------------------------------- /screenshots/IBDesignable-IBInspectable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/IBDesignable-IBInspectable.png -------------------------------------------------------------------------------- /screenshots/Loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/Loading.png -------------------------------------------------------------------------------- /screenshots/SketchDesign.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/SketchDesign.png -------------------------------------------------------------------------------- /screenshots/Swift Weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/Swift Weather.png -------------------------------------------------------------------------------- /screenshots/Swift-Weather-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/Swift-Weather-33.png -------------------------------------------------------------------------------- /screenshots/UIStackView-with-Size-Classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/UIStackView-with-Size-Classes.png -------------------------------------------------------------------------------- /screenshots/UIStackView.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/UIStackView.png -------------------------------------------------------------------------------- /screenshots/loading-33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeLin/SwiftLanguageWeather/57faf3d6b5e07b633a99bc1fbbdd00dc8d6ee13d/screenshots/loading-33.png --------------------------------------------------------------------------------