├── .gitignore ├── Design ├── Template-AppIcons-iOS-assets │ ├── Icon-App-20x20@1x.png │ ├── Icon-App-29x29@1x.png │ ├── Icon-App-29x29@2x.png │ ├── Icon-App-29x29@3x.png │ ├── Icon-App-40x40@1x.png │ ├── Icon-App-40x40@2x.png │ ├── Icon-App-40x40@3x.png │ ├── Icon-App-512x512@1x.png │ ├── Icon-App-512x512@2x.png │ ├── Icon-App-512x512@3x.png │ ├── Icon-App-60x60@1x.png │ ├── Icon-App-60x60@2x.png │ ├── Icon-App-60x60@3x.png │ ├── Icon-App-76x76@1x.png │ ├── Icon-App-76x76@2x.png │ ├── Icon-App-76x76@3x.png │ └── Icon-App-83.5x83.5@2x.png ├── Template-AppIcons-iOS.psd ├── arrow-assets │ ├── Arrow.png │ ├── scaled-at-25 │ │ ├── Arrow.png │ │ ├── Arrow@2x.png │ │ └── Arrow@3x.png │ ├── scaled-at-50 │ │ └── Arrow@2x.png │ └── scaled-at-75 │ │ └── Arrow@3x.png ├── arrow.psd ├── wx-icons-highres-assets │ ├── Cloudy.png │ ├── Haze.png │ ├── Mist.png │ ├── Moon.png │ ├── Mostly-sunny-day.png │ ├── Mostly-sunny-night.png │ ├── Partly-sunny-day.png │ ├── Partly-sunny-night.png │ ├── Snow.png │ ├── Sun.png │ ├── Thunder-cloud.png │ ├── Thunder.png │ ├── Wind.png │ ├── scaled-at-25 │ │ ├── Cloudy.png │ │ ├── Haze.png │ │ ├── Mist.png │ │ ├── Moon.png │ │ ├── Mostly-sunny-day.png │ │ ├── Mostly-sunny-night.png │ │ ├── Partly-sunny-day.png │ │ ├── Partly-sunny-night.png │ │ ├── Snow.png │ │ ├── Sun.png │ │ ├── Thunder-cloud.png │ │ ├── Thunder.png │ │ └── Wind.png │ ├── scaled-at-50 │ │ ├── Cloudy@2x.png │ │ ├── Haze@2x.png │ │ ├── Mist@2x.png │ │ ├── Moon@2x.png │ │ ├── Mostly-sunny-day@2x.png │ │ ├── Mostly-sunny-night@2x.png │ │ ├── Partly-sunny-day@2x.png │ │ ├── Partly-sunny-night@2x.png │ │ ├── Snow@2x.png │ │ ├── Sun@2x.png │ │ ├── Thunder-cloud@2x.png │ │ ├── Thunder@2x.png │ │ └── Wind@2x.png │ └── scaled-at-75 │ │ ├── Cloudy@3x.png │ │ ├── Haze@3x.png │ │ ├── Mist@3x.png │ │ ├── Moon@3x.png │ │ ├── Mostly-sunny-day@3x.png │ │ ├── Mostly-sunny-night@3x.png │ │ ├── Partly-sunny-day@3x.png │ │ ├── Partly-sunny-night@3x.png │ │ ├── Snow@3x.png │ │ ├── Sun@3x.png │ │ ├── Thunder-cloud@3x.png │ │ ├── Thunder@3x.png │ │ └── Wind@3x.png ├── wx-icons-highres.psd ├── x-assets │ ├── Delete.png │ ├── scaled-at-25 │ │ └── Delete.png │ ├── scaled-at-50 │ │ └── Delete@2x.png │ └── scaled-at-75 │ │ └── Delete@3x.png └── x.psd ├── LatLon.request ├── Playground.playground ├── Contents.swift └── contents.xcplayground ├── Podfile ├── Podfile.lock ├── Pods ├── IGListKit │ ├── LICENSE.md │ ├── README.md │ └── Source │ │ ├── IGListAdapter.h │ │ ├── IGListAdapter.m │ │ ├── IGListAdapterDataSource.h │ │ ├── IGListAdapterDelegate.h │ │ ├── IGListAdapterUpdater.h │ │ ├── IGListAdapterUpdater.m │ │ ├── IGListAdapterUpdaterDelegate.h │ │ ├── IGListAssert.h │ │ ├── IGListBatchUpdateData.h │ │ ├── IGListBatchUpdateData.mm │ │ ├── IGListCollectionContext.h │ │ ├── IGListCollectionView.h │ │ ├── IGListCollectionView.m │ │ ├── IGListDiff.h │ │ ├── IGListDiff.mm │ │ ├── IGListDiffable.h │ │ ├── IGListDisplayDelegate.h │ │ ├── IGListExperiments.h │ │ ├── IGListGridCollectionViewLayout.h │ │ ├── IGListGridCollectionViewLayout.m │ │ ├── IGListIndexPathResult.h │ │ ├── IGListIndexPathResult.m │ │ ├── IGListIndexSetResult.h │ │ ├── IGListIndexSetResult.m │ │ ├── IGListKit.h │ │ ├── IGListMacros.h │ │ ├── IGListMoveIndex.h │ │ ├── IGListMoveIndex.m │ │ ├── IGListMoveIndexPath.h │ │ ├── IGListMoveIndexPath.m │ │ ├── IGListReloadDataUpdater.h │ │ ├── IGListReloadDataUpdater.m │ │ ├── IGListScrollDelegate.h │ │ ├── IGListSectionController.h │ │ ├── IGListSectionController.m │ │ ├── IGListSectionType.h │ │ ├── IGListSingleSectionController.h │ │ ├── IGListSingleSectionController.m │ │ ├── IGListStackedSectionController.h │ │ ├── IGListStackedSectionController.m │ │ ├── IGListSupplementaryViewSource.h │ │ ├── IGListUpdatingDelegate.h │ │ ├── IGListWorkingRangeDelegate.h │ │ ├── Internal │ │ ├── IGListAdapterInternal.h │ │ ├── IGListAdapterProxy.h │ │ ├── IGListAdapterProxy.m │ │ ├── IGListAdapterUpdaterInternal.h │ │ ├── IGListDisplayHandler.h │ │ ├── IGListDisplayHandler.m │ │ ├── IGListIndexPathResultInternal.h │ │ ├── IGListIndexSetResultInternal.h │ │ ├── IGListMoveIndexInternal.h │ │ ├── IGListMoveIndexPathInternal.h │ │ ├── IGListSectionControllerInternal.h │ │ ├── IGListSectionMap.h │ │ ├── IGListSectionMap.m │ │ ├── IGListStackedSectionControllerInternal.h │ │ ├── IGListWorkingRangeHandler.h │ │ ├── IGListWorkingRangeHandler.mm │ │ ├── UICollectionView+IGListBatchUpdateData.h │ │ └── UICollectionView+IGListBatchUpdateData.m │ │ ├── NSNumber+IGListDiffable.h │ │ ├── NSNumber+IGListDiffable.m │ │ ├── NSString+IGListDiffable.h │ │ └── NSString+IGListDiffable.m ├── Local Podspecs │ └── IGListKit.podspec.json ├── Manifest.lock ├── Pods.xcodeproj │ └── project.pbxproj └── Target Support Files │ ├── IGListKit │ ├── IGListKit-dummy.m │ ├── IGListKit-prefix.pch │ ├── IGListKit-umbrella.h │ ├── IGListKit.modulemap │ ├── IGListKit.xcconfig │ └── Info.plist │ ├── Pods-SimpleWeather │ ├── Info.plist │ ├── Pods-SimpleWeather-acknowledgements.markdown │ ├── Pods-SimpleWeather-acknowledgements.plist │ ├── Pods-SimpleWeather-dummy.m │ ├── Pods-SimpleWeather-frameworks.sh │ ├── Pods-SimpleWeather-resources.sh │ ├── Pods-SimpleWeather-umbrella.h │ ├── Pods-SimpleWeather.debug.xcconfig │ ├── Pods-SimpleWeather.modulemap │ └── Pods-SimpleWeather.release.xcconfig │ └── Pods-SimpleWeatherTests │ ├── Info.plist │ ├── Pods-SimpleWeatherTests-acknowledgements.markdown │ ├── Pods-SimpleWeatherTests-acknowledgements.plist │ ├── Pods-SimpleWeatherTests-dummy.m │ ├── Pods-SimpleWeatherTests-frameworks.sh │ ├── Pods-SimpleWeatherTests-resources.sh │ ├── Pods-SimpleWeatherTests-umbrella.h │ ├── Pods-SimpleWeatherTests.debug.xcconfig │ ├── Pods-SimpleWeatherTests.modulemap │ └── Pods-SimpleWeatherTests.release.xcconfig ├── README.md ├── SimpleWeather.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── SimpleWeather.xcworkspace └── contents.xcworkspacedata ├── SimpleWeather ├── .DS_Store ├── AppDelegate.swift ├── Classes │ ├── .DS_Store │ ├── Models │ │ ├── Alert+JSONConvertible.swift │ │ ├── Alert.swift │ │ ├── Astronomy+JSONConvertible.swift │ │ ├── Astronomy.swift │ │ ├── Condition+Icon.swift │ │ ├── Condition.swift │ │ ├── ConditionsSection+Forecast.swift │ │ ├── ConditionsSection+IGListDiffable.swift │ │ ├── ConditionsSection+ViewModels.swift │ │ ├── ConditionsSection.swift │ │ ├── DailyForecastSection+Forecast.swift │ │ ├── DailyForecastSection+IGListDiffable.swift │ │ ├── DailyForecastSection.swift │ │ ├── EmbeddedSection+Forecast.swift │ │ ├── EmbeddedSection+IGListDiffable.swift │ │ ├── EmbeddedSection.swift │ │ ├── Forecast+JSONConvertible.swift │ │ ├── Forecast.swift │ │ ├── ForecastDay+Equatable.swift │ │ ├── ForecastDay+JSONConvertible.swift │ │ ├── ForecastDay.swift │ │ ├── ForecastHourly+JSONConvertible.swift │ │ ├── ForecastHourly.swift │ │ ├── JSONConvertible.swift │ │ ├── Location+JSONConvertible.swift │ │ ├── Location.swift │ │ ├── MKLocalSearchCompletion+IGListDiffable.swift │ │ ├── Observation+JSONConvertible.swift │ │ ├── Observation.swift │ │ ├── RadarSection+IGListDiffable.swift │ │ ├── RadarSection.swift │ │ ├── SavedLocation+Equatable.swift │ │ ├── SavedLocation+IGListDiffable.swift │ │ ├── SavedLocation+NSEncoding.swift │ │ ├── SavedLocation.swift │ │ └── Wind.swift │ ├── SectionControllers │ │ ├── ConditionsSectionController.swift │ │ ├── DailyForecastSectionController.swift │ │ ├── EmbeddedAdapterSectionController.swift │ │ ├── ErrorSectionController.swift │ │ ├── HourlyForecastSectionController.swift │ │ ├── RadarSectionController.swift │ │ ├── SavedLocationSectionController.swift │ │ └── SearchResultSectionController.swift │ ├── Systems │ │ ├── API.swift │ │ ├── CurrentLocationRequester.swift │ │ ├── Date+Daytime.swift │ │ ├── KeypathParsing.swift │ │ ├── MKCoordinateRegion+Bounds.swift │ │ ├── MKCoordinateRegion+Convenience.swift │ │ ├── SavedLocationStore.swift │ │ ├── URL+DiskCacheKey.swift │ │ ├── URLBuilder+Wunderground.swift │ │ ├── URLBuilder.swift │ │ ├── URLSession+Convenience.swift │ │ ├── URLSessionDataTaskResponse.swift │ │ └── ViewPulser.swift │ ├── ViewControllers │ │ ├── AlertsViewController.swift │ │ ├── SavedLocationsViewController.swift │ │ ├── SearchViewController.swift │ │ └── WeatherViewController.swift │ ├── ViewModels │ │ ├── ConditionsCellViewModel.swift │ │ ├── ConditionsDetailCellViewModel.swift │ │ ├── ErrorViewModel+IGListDiffable.swift │ │ ├── ErrorViewModel.swift │ │ ├── ForecastDayCellViewModel+Equatable.swift │ │ ├── ForecastDayCellViewModel.swift │ │ ├── ForecastHourCellViewModel+IGListDiffable.swift │ │ ├── ForecastHourCellViewModel.swift │ │ ├── ForecastHourlyDataSource.swift │ │ ├── RadarOverlay.swift │ │ ├── SavedLocationCellViewModel.swift │ │ ├── WeatherNavigationViewModel.swift │ │ └── WeatherStateMachine.swift │ └── Views │ │ ├── BorderedButton.swift │ │ ├── ConditionsCell.swift │ │ ├── ConditionsDetailCell.swift │ │ ├── EmbeddedCollectionViewCell.swift │ │ ├── ForecastDayCell.swift │ │ ├── ForecastHourCell.swift │ │ ├── HighLowAttributedString.swift │ │ ├── LoadingView.swift │ │ ├── MapCell.swift │ │ ├── RadarOverlayView.swift │ │ ├── RetryCell.swift │ │ ├── RoundedCollectionViewCell.swift │ │ ├── SavedLocationCell.swift │ │ ├── SavedLocationContentView.swift │ │ ├── SavedLocationDeleteView.swift │ │ └── SearchResultCell.swift ├── Resources │ ├── .DS_Store │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x-1.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x-1.png │ │ │ ├── Icon-App-40x40@1x-2.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x-1.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@1x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ ├── Arrow.imageset │ │ │ ├── Arrow.png │ │ │ ├── Arrow@2x.png │ │ │ ├── Arrow@3x.png │ │ │ └── Contents.json │ │ ├── Cloudy.imageset │ │ │ ├── Cloudy.png │ │ │ ├── Cloudy@2x.png │ │ │ ├── Cloudy@3x.png │ │ │ └── Contents.json │ │ ├── Contents.json │ │ ├── Delete.imageset │ │ │ ├── Contents.json │ │ │ ├── Delete.png │ │ │ ├── Delete@2x.png │ │ │ └── Delete@3x.png │ │ ├── Disclosure.imageset │ │ │ ├── Contents.json │ │ │ ├── Disclosure.png │ │ │ ├── Disclosure@2x.png │ │ │ └── Disclosure@3x.png │ │ ├── Haze.imageset │ │ │ ├── Contents.json │ │ │ ├── Haze.png │ │ │ ├── Haze@2x.png │ │ │ └── Haze@3x.png │ │ ├── Mist.imageset │ │ │ ├── Contents.json │ │ │ ├── Mist.png │ │ │ ├── Mist@2x.png │ │ │ └── Mist@3x.png │ │ ├── Moon.imageset │ │ │ ├── Contents.json │ │ │ ├── Moon.png │ │ │ ├── Moon@2x.png │ │ │ └── Moon@3x.png │ │ ├── Mostly-sunny-day.imageset │ │ │ ├── Contents.json │ │ │ ├── Mostly-sunny-day.png │ │ │ ├── Mostly-sunny-day@2x.png │ │ │ └── Mostly-sunny-day@3x.png │ │ ├── Mostly-sunny-night.imageset │ │ │ ├── Contents.json │ │ │ ├── Mostly-sunny-night.png │ │ │ ├── Mostly-sunny-night@2x.png │ │ │ └── Mostly-sunny-night@3x.png │ │ ├── Partly-sunny-day.imageset │ │ │ ├── Contents.json │ │ │ ├── Partly-sunny-day.png │ │ │ ├── Partly-sunny-day@2x.png │ │ │ └── Partly-sunny-day@3x.png │ │ ├── Snow.imageset │ │ │ ├── Contents.json │ │ │ ├── Snow.png │ │ │ ├── Snow@2x.png │ │ │ └── Snow@3x.png │ │ ├── Sun.imageset │ │ │ ├── Contents.json │ │ │ ├── Sun.png │ │ │ ├── Sun@2x.png │ │ │ └── Sun@3x.png │ │ ├── Thunder-cloud.imageset │ │ │ ├── Contents.json │ │ │ ├── Thunder-cloud.png │ │ │ ├── Thunder-cloud@2x.png │ │ │ └── Thunder-cloud@3x.png │ │ └── Wind.imageset │ │ │ ├── Contents.json │ │ │ ├── Wind.png │ │ │ ├── Wind@2x.png │ │ │ └── Wind@3x.png │ ├── Info.plist │ ├── LaunchScreen.storyboard │ └── Main.storyboard └── SimpleWeather-Briding-Header.h ├── SimpleWeatherTests ├── APISessionTests.swift ├── ConditionsSectionTests.swift ├── DateTests.swift ├── Info.plist ├── KeypathParsingTests.swift ├── ModelTests.swift ├── WeatherNavigationViewModelTests.swift └── sample_response.json ├── SimpleWeatherUITests ├── Info.plist └── SimpleWeatherUITests.swift ├── fastlane ├── Appfile ├── Fastfile ├── README.md ├── Snapfile ├── SnapshotHelper.swift └── SnapshotHelper2-3.swift └── report.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | 40 | # CocoaPods 41 | # 42 | # We recommend against adding the Pods directory to your .gitignore. However 43 | # you should judge for yourself, the pros and cons are mentioned at: 44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 45 | # 46 | # Pods/ 47 | 48 | # Carthage 49 | # 50 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 51 | # Carthage/Checkouts 52 | 53 | Carthage/Build 54 | 55 | # fastlane 56 | # 57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 58 | # screenshots whenever they are needed. 59 | # For more information about the recommended setup visit: 60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/Preview.html 64 | fastlane/screenshots 65 | fastlane/test_output 66 | 67 | .DS_Store 68 | 69 | -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-512x512@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-512x512@1x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-512x512@2x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-512x512@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-512x512@3x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-60x60@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-60x60@1x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-76x76@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-76x76@3x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS-assets/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS-assets/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /Design/Template-AppIcons-iOS.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/Template-AppIcons-iOS.psd -------------------------------------------------------------------------------- /Design/arrow-assets/Arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/arrow-assets/Arrow.png -------------------------------------------------------------------------------- /Design/arrow-assets/scaled-at-25/Arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/arrow-assets/scaled-at-25/Arrow.png -------------------------------------------------------------------------------- /Design/arrow-assets/scaled-at-25/Arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/arrow-assets/scaled-at-25/Arrow@2x.png -------------------------------------------------------------------------------- /Design/arrow-assets/scaled-at-25/Arrow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/arrow-assets/scaled-at-25/Arrow@3x.png -------------------------------------------------------------------------------- /Design/arrow-assets/scaled-at-50/Arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/arrow-assets/scaled-at-50/Arrow@2x.png -------------------------------------------------------------------------------- /Design/arrow-assets/scaled-at-75/Arrow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/arrow-assets/scaled-at-75/Arrow@3x.png -------------------------------------------------------------------------------- /Design/arrow.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/arrow.psd -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Cloudy.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Haze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Haze.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Mist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Mist.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Moon.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Mostly-sunny-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Mostly-sunny-day.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Mostly-sunny-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Mostly-sunny-night.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Partly-sunny-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Partly-sunny-day.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Partly-sunny-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Partly-sunny-night.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Snow.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Sun.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Thunder-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Thunder-cloud.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Thunder.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/Wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/Wind.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Cloudy.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Haze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Haze.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Mist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Mist.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Moon.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Mostly-sunny-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Mostly-sunny-day.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Mostly-sunny-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Mostly-sunny-night.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Partly-sunny-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Partly-sunny-day.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Partly-sunny-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Partly-sunny-night.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Snow.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Sun.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Thunder-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Thunder-cloud.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Thunder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Thunder.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-25/Wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-25/Wind.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Cloudy@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Cloudy@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Haze@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Haze@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Mist@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Mist@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Moon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Moon@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Mostly-sunny-day@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Mostly-sunny-day@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Mostly-sunny-night@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Mostly-sunny-night@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Partly-sunny-day@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Partly-sunny-day@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Partly-sunny-night@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Partly-sunny-night@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Snow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Snow@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Sun@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Sun@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Thunder-cloud@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Thunder-cloud@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Thunder@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Thunder@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-50/Wind@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-50/Wind@2x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Cloudy@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Cloudy@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Haze@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Haze@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Mist@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Mist@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Moon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Moon@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Mostly-sunny-day@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Mostly-sunny-day@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Mostly-sunny-night@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Mostly-sunny-night@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Partly-sunny-day@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Partly-sunny-day@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Partly-sunny-night@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Partly-sunny-night@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Snow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Snow@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Sun@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Sun@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Thunder-cloud@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Thunder-cloud@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Thunder@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Thunder@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres-assets/scaled-at-75/Wind@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres-assets/scaled-at-75/Wind@3x.png -------------------------------------------------------------------------------- /Design/wx-icons-highres.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/wx-icons-highres.psd -------------------------------------------------------------------------------- /Design/x-assets/Delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/x-assets/Delete.png -------------------------------------------------------------------------------- /Design/x-assets/scaled-at-25/Delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/x-assets/scaled-at-25/Delete.png -------------------------------------------------------------------------------- /Design/x-assets/scaled-at-50/Delete@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/x-assets/scaled-at-50/Delete@2x.png -------------------------------------------------------------------------------- /Design/x-assets/scaled-at-75/Delete@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/x-assets/scaled-at-75/Delete@3x.png -------------------------------------------------------------------------------- /Design/x.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/Design/x.psd -------------------------------------------------------------------------------- /LatLon.request: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | baseURL 6 | http://api.wunderground.com/api/36c847c1ef056205/forecast/geolookup/conditions/forecast10day/alerts/hourly/q/40.71,-74.json 7 | followRedirect 8 | 9 | handleJSONPCallbacks 10 | 11 | headers 12 | 13 | httpMethod 14 | GET 15 | jsonpScript 16 | 17 | paramBodyUIChoice 18 | 0 19 | parameters 20 | 21 | parametersType 22 | 0 23 | presentBeforeChallenge 24 | 25 | stringEncoding 26 | 4 27 | usingHTTPBody 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /Playground.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | target 'SimpleWeather' do 4 | use_frameworks! 5 | pod 'IGListKit' 6 | end 7 | 8 | target 'SimpleWeatherTests' do 9 | use_frameworks! 10 | pod 'IGListKit' 11 | end 12 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - IGListKit (2.0.0) 3 | 4 | DEPENDENCIES: 5 | - IGListKit 6 | 7 | SPEC CHECKSUMS: 8 | IGListKit: ee255e4e61865bc3e01ca96b690c5435c2887849 9 | 10 | PODFILE CHECKSUM: f19cdf1c0457c987c46c6bbd1fd4c966fe79fd7b 11 | 12 | COCOAPODS: 1.1.1 13 | -------------------------------------------------------------------------------- /Pods/IGListKit/LICENSE.md: -------------------------------------------------------------------------------- 1 | BSD License 2 | 3 | For `IGListKit` software 4 | 5 | Copyright (c) 2016, Facebook, Inc. All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, 8 | are permitted provided that the following conditions are met: 9 | 10 | * Redistributions of source code must retain the above copyright notice, this 11 | list of conditions and the following disclaimer. 12 | 13 | * Redistributions in binary form must reproduce the above copyright notice, 14 | this list of conditions and the following disclaimer in the documentation 15 | and/or other materials provided with the distribution. 16 | 17 | * Neither the name Facebook nor the names of its contributors may be used to 18 | endorse or promote products derived from this software without specific 19 | prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 22 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 25 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 27 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 28 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListAdapterDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @class IGListAdapter; 13 | 14 | /** 15 | Conform to `IGListAdapterDelegate` to receive display events for objects in a list. 16 | */ 17 | @protocol IGListAdapterDelegate 18 | 19 | /** 20 | Notifies the delegate that a list object is about to be displayed. 21 | 22 | @param listAdapter The list adapter sending this information. 23 | @param object The object that will display. 24 | @param index The index of the object in the list. 25 | */ 26 | - (void)listAdapter:(IGListAdapter *)listAdapter willDisplayObject:(id)object atIndex:(NSInteger)index; 27 | 28 | /** 29 | Notifies the delegate that a list object is no longer being displayed. 30 | 31 | @param listAdapter The list adapter sending this information. 32 | @param object The object that ended display. 33 | @param index The index of the object in the list. 34 | */ 35 | - (void)listAdapter:(IGListAdapter *)listAdapter didEndDisplayingObject:(id)object atIndex:(NSInteger)index; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListAdapterUpdater.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | #import 14 | #import 15 | #import 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | /** 20 | An `IGListAdapterUpdater` is a concrete type that conforms to `IGListUpdatingDelegate`. 21 | It is an out-of-box upater for `IGListAdapter` objects to use. 22 | 23 | @note This updater performs re-entrant, coalesced updating for a list. It also uses a least-minimal diff 24 | for calculating UI updates when `IGListAdapter` calls 25 | `-performUpdateWithCollectionView:fromObjects:toObjects:completion:`. 26 | */ 27 | IGLK_SUBCLASSING_RESTRICTED 28 | @interface IGListAdapterUpdater : NSObject 29 | 30 | /** 31 | The delegate that receives events with data on the performance of a transition. 32 | */ 33 | @property (nonatomic, weak) id delegate; 34 | 35 | /** 36 | A flag indicating if a move should be treated as a "delete, then insert" operation. 37 | */ 38 | @property (nonatomic, assign) BOOL movesAsDeletesInserts; 39 | 40 | /** 41 | A bitmask of experiments to conduct on the updater. 42 | */ 43 | @property (nonatomic, assign) IGListExperiment experiments; 44 | 45 | @end 46 | 47 | NS_ASSUME_NONNULL_END 48 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListAssert.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #ifndef IGAssert 11 | #define IGAssert( condition, ... ) NSCAssert( (condition) , ##__VA_ARGS__) 12 | #endif // IGAssert 13 | 14 | #ifndef IGParameterAssert 15 | #define IGParameterAssert( condition ) IGAssert( (condition) , @"Invalid parameter not satisfying: %@", @#condition) 16 | #endif // IGParameterAssert 17 | 18 | #ifndef IGAssertMainThread 19 | #define IGAssertMainThread() IGAssert( ([NSThread isMainThread] == YES), @"Must be on the main thread") 20 | #endif // IGAssertMainThread 21 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListCollectionView.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "IGListCollectionView.h" 11 | 12 | @implementation IGListCollectionView 13 | 14 | - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { 15 | if (self = [super initWithFrame:frame collectionViewLayout:layout]) { 16 | 17 | UIColor *backgroundAppearanceColor = (UIColor *) [[[self class] appearance] backgroundColor]; 18 | if (!backgroundAppearanceColor) { 19 | self.backgroundColor = [UIColor whiteColor]; 20 | } 21 | 22 | self.alwaysBounceVertical = YES; 23 | } 24 | return self; 25 | } 26 | 27 | - (void)layoutSubviews { 28 | /** 29 | UICollectionView will sometimes lay its cells out with an animation. This is especially noticeable on older devices 30 | while scrolling quickly. The simplest fix is to just disable animations for -layoutSubviews, which is where cells 31 | and other views inside the UICollectionView are laid out. 32 | */ 33 | [UIView performWithoutAnimation:^{ 34 | [super layoutSubviews]; 35 | }]; 36 | } 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListDiffable.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | /** 13 | The `IGListDiffable` protocol provides methods needed to compare the identity and equality of two objects. 14 | */ 15 | @protocol IGListDiffable 16 | 17 | /** 18 | Returns a key that uniquely identifies the object. 19 | 20 | @return A key that can be used to uniquely identify the object. 21 | 22 | @note Two objects may share the same identifier, but are not equal. A common pattern is to use the `NSObject` 23 | category for automatic conformance. However this means that objects will be identified on their 24 | pointer value so finding updates becomes impossible. 25 | 26 | @warning This value should never be mutated. 27 | */ 28 | - (nonnull id)diffIdentifier; 29 | 30 | /** 31 | Returns whether the receiver and a given object are equal. 32 | 33 | @param object The object to be compared to the receiver. 34 | 35 | @return `YES` if the receiver and object are equal, otherwise `NO`. 36 | */ 37 | - (BOOL)isEqualToDiffableObject:(nullable id)object; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListGridCollectionViewLayout.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /** 16 | `IGListGridCollectionViewLayout` provides a vertically-scrolling, section-based grid layout for `UICollectionView`. 17 | Items in the layout are displayed consecutively in a grid with exactly 1 item per section. 18 | If items are square, the appearance would be similar to the iOS Photos app. 19 | However, the size of the items for each section can vary. 20 | */ 21 | IGLK_SUBCLASSING_RESTRICTED 22 | @interface IGListGridCollectionViewLayout : UICollectionViewLayout 23 | 24 | /** 25 | The minimum spacing to use between lines of items in the grid. The default value is `0.0`. 26 | */ 27 | @property (nonatomic, assign) IBInspectable CGFloat minimumLineSpacing; 28 | 29 | /** 30 | The minimum spacing to use between items in the same row. The default value is `0.0`. 31 | */ 32 | @property (nonatomic, assign) IBInspectable CGFloat minimumInteritemSpacing; 33 | 34 | /** 35 | The default size to use for cells. The default value is `(0.0, 0.0)`. 36 | If this size is non-zero, the layout will use this item size for all items. 37 | When the size is zero (the default), then the layout will query the collection view's delegate for the size. 38 | */ 39 | @property (nonatomic, assign) IBInspectable CGSize itemSize; 40 | 41 | @end 42 | 43 | NS_ASSUME_NONNULL_END 44 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListKit.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | /** 13 | * Project version number for IGListKit. 14 | */ 15 | FOUNDATION_EXPORT double IGListKitVersionNumber; 16 | 17 | /** 18 | * Project version string for IGListKit. 19 | */ 20 | FOUNDATION_EXPORT const unsigned char IGListKitVersionString[]; 21 | 22 | #import 23 | #import 24 | #import 25 | #import 26 | #import 27 | #import 28 | #import 29 | #import 30 | #import 31 | #import 32 | #import 33 | #import 34 | #import 35 | #import 36 | #import 37 | #import 38 | #import 39 | #import 40 | #import 41 | #import 42 | #import 43 | #import 44 | #import 45 | #import 46 | #import 47 | #import 48 | #import 49 | #import 50 | #import 51 | #import 52 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListMacros.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #ifndef IGLK_SUBCLASSING_RESTRICTED 11 | #if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted) 12 | #define IGLK_SUBCLASSING_RESTRICTED __attribute__((objc_subclassing_restricted)) 13 | #else 14 | #define IGLK_SUBCLASSING_RESTRICTED 15 | #endif // #if defined(__has_attribute) && __has_attribute(objc_subclassing_restricted) 16 | #endif // #ifndef IGLK_SUBCLASSING_RESTRICTED 17 | 18 | #ifndef IGLK_UNAVAILABLE 19 | #define IGLK_UNAVAILABLE(message) __attribute__((unavailable(message))) 20 | #endif // #ifndef IGLK_UNAVAILABLE 21 | 22 | #if IGLK_LOGGING_ENABLED 23 | #define IGLKLog( s, ... ) do { NSLog( @"IGListKit: %@", [NSString stringWithFormat: (s), ##__VA_ARGS__] ); } while(0) 24 | #else 25 | #define IGLKLog( s, ... ) 26 | #endif 27 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListMoveIndex.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /** 15 | An object representing a move between indexes. 16 | */ 17 | @interface IGListMoveIndex : NSObject 18 | 19 | /** 20 | An index in the old collection. 21 | */ 22 | @property (nonatomic, assign, readonly) NSInteger from; 23 | 24 | /** 25 | An index in the new collection. 26 | */ 27 | @property (nonatomic, assign, readonly) NSInteger to; 28 | 29 | /** 30 | :nodoc: 31 | */ 32 | - (instancetype)init NS_UNAVAILABLE; 33 | 34 | /** 35 | :nodoc: 36 | */ 37 | + (instancetype)new NS_UNAVAILABLE; 38 | 39 | @end 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListMoveIndex.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "IGListMoveIndex.h" 11 | #import "IGListMoveIndexInternal.h" 12 | 13 | @implementation IGListMoveIndex 14 | 15 | - (instancetype)initWithFrom:(NSInteger)from to:(NSInteger)to { 16 | if (self = [super init]) { 17 | _from = from; 18 | _to = to; 19 | } 20 | return self; 21 | } 22 | 23 | - (NSUInteger)hash { 24 | return _from ^ _to; 25 | } 26 | 27 | - (BOOL)isEqual:(id)object { 28 | if (object == self) { 29 | return YES; 30 | } 31 | if ([object isKindOfClass:[IGListMoveIndex class]]) { 32 | const NSInteger f1 = self.from, f2 = [object from]; 33 | const NSInteger t1 = self.to, t2 = [object to]; 34 | return f1 == f2 && t1 == t2; 35 | } 36 | return NO; 37 | } 38 | 39 | - (NSComparisonResult)compare:(id)object { 40 | const NSInteger right = [object from]; 41 | const NSInteger left = [self from]; 42 | if (left == right) { 43 | return NSOrderedSame; 44 | } else if (left < right) { 45 | return NSOrderedAscending; 46 | } else { 47 | return NSOrderedDescending; 48 | } 49 | } 50 | 51 | - (NSString *)description { 52 | return [NSString stringWithFormat:@"<%@ %p; from: %zi; to: %zi;>", NSStringFromClass(self.class), self, self.from, self.to]; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListMoveIndexPath.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /** 15 | An object representing a move between indexes. 16 | */ 17 | @interface IGListMoveIndexPath : NSObject 18 | 19 | /** 20 | An index path in the old collection. 21 | */ 22 | @property (nonatomic, strong, readonly) NSIndexPath *from; 23 | 24 | /** 25 | An index path in the new collection. 26 | */ 27 | @property (nonatomic, strong, readonly) NSIndexPath *to; 28 | 29 | /** 30 | :nodoc: 31 | */ 32 | - (instancetype)init NS_UNAVAILABLE; 33 | 34 | /** 35 | :nodoc: 36 | */ 37 | + (instancetype)new NS_UNAVAILABLE; 38 | 39 | @end 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListMoveIndexPath.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "IGListMoveIndexPath.h" 11 | #import "IGListMoveIndexPathInternal.h" 12 | 13 | #import 14 | 15 | @implementation IGListMoveIndexPath 16 | 17 | - (instancetype)initWithFrom:(NSIndexPath *)from to:(NSIndexPath *)to { 18 | NSParameterAssert(from != nil); 19 | NSParameterAssert(to != nil); 20 | if (self = [super init]) { 21 | _from = from; 22 | _to = to; 23 | } 24 | return self; 25 | } 26 | 27 | - (NSUInteger)hash { 28 | return [_from hash] ^ [_to hash]; 29 | } 30 | 31 | - (BOOL)isEqual:(id)object { 32 | if (object == self) { 33 | return YES; 34 | } 35 | if ([object isKindOfClass:[IGListMoveIndexPath class]]) { 36 | NSIndexPath *f1 = self.from, *f2 = [object from]; 37 | NSIndexPath *t1 = self.to, *t2 = [object to]; 38 | return [f1 isEqual:f2] && [t1 isEqual:t2]; 39 | } 40 | return NO; 41 | } 42 | 43 | - (NSComparisonResult)compare:(id)object { 44 | return [[self from] compare:[object from]]; 45 | } 46 | 47 | - (NSString *)description { 48 | return [NSString stringWithFormat:@"<%@ %p; from: %@; to: %@;>", NSStringFromClass(self.class), self, self.from, self.to]; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListReloadDataUpdater.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | #import 14 | 15 | /** 16 | An `IGListReloadDataUpdater` is a concrete type that conforms to `IGListUpdatingDelegate`. 17 | It is an out-of-box upater for `IGListAdapter` objects to use. 18 | 19 | @note This updater performs simple, synchronous updates using `-[UICollectionView reloadData]`. 20 | */ 21 | IGLK_SUBCLASSING_RESTRICTED 22 | @interface IGListReloadDataUpdater : NSObject 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListScrollDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @class IGListAdapter; 13 | @class IGListSectionController; 14 | 15 | @protocol IGListSectionType; 16 | 17 | /** 18 | Implement this protocol to receive display events for an section controller when it is on screen. 19 | */ 20 | @protocol IGListScrollDelegate 21 | 22 | /** 23 | Tells the delegate that the section controller was scrolled on screen. 24 | 25 | @param listAdapter The list adapter whose collection view was scrolled. 26 | @param sectionController The visible section controller that was scrolled. 27 | */ 28 | - (void)listAdapter:(IGListAdapter *)listAdapter didScrollSectionController:(IGListSectionController *)sectionController; 29 | 30 | /** 31 | Tells the delegate that the section controller will be dragged on screen. 32 | 33 | @param listAdapter The list adapter whose collection view will drag. 34 | @param sectionController The visible section controller that will drag. 35 | */ 36 | - (void)listAdapter:(IGListAdapter *)listAdapter willBeginDraggingSectionController:(IGListSectionController *)sectionController; 37 | 38 | /** 39 | Tells the delegate that the section controller did end dragging on screen. 40 | 41 | @param listAdapter The list adapter whose collection view ended dragging. 42 | @param sectionController The visible section controller that ended dragging. 43 | */ 44 | - (void)listAdapter:(IGListAdapter *)listAdapter didEndDraggingSectionController:(IGListSectionController *)sectionController willDecelerate:(BOOL)decelerate; 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListStackedSectionController.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | #import 12 | #import 13 | 14 | /** 15 | An instace of `IGListStackedSectionController` is a clustered section controller, 16 | composed of many child section controllers. It constructs and routes item-level 17 | indexes to the appropriate child section controller with a local index. This lets you build section controllers made up 18 | of individual units that can be shared and reused with other section controllers. 19 | 20 | For example, you can create a "Comments" section controller that displays lists of text that is used alongside photo, 21 | video, or slideshow section controllers. You then have four small and manageable section controllers instead of one 22 | huge class. 23 | */ 24 | IGLK_SUBCLASSING_RESTRICTED 25 | @interface IGListStackedSectionController : IGListSectionController 26 | 27 | /** 28 | Creates a new stacked section controller. 29 | 30 | @param sectionControllers An array of section controllers that make up the stack. 31 | 32 | @note The order of the section controllers dictates the order in which they appear. 33 | 34 | @warning The first section controller that is the supplementary source decides which supplementary views get displayed. 35 | */ 36 | - (instancetype)initWithSectionControllers:(NSArray *> *)sectionControllers NS_DESIGNATED_INITIALIZER; 37 | 38 | /** 39 | :nodoc: 40 | */ 41 | - (instancetype)init NS_UNAVAILABLE; 42 | 43 | /** 44 | :nodoc: 45 | */ 46 | + (instancetype)new NS_UNAVAILABLE; 47 | 48 | @end 49 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListSupplementaryViewSource.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /** 15 | Conform to this protocol to provide information about a list's supplementary views. This data is used in 16 | `IGListAdapter` which then configures and maintains a `UICollectionView`. The supplementary API reflects that in 17 | `UICollectionView`, `UICollectionViewLayout`, and `UICollectionViewDataSource`. 18 | */ 19 | @protocol IGListSupplementaryViewSource 20 | 21 | /** 22 | Asks the SupplementaryViewSource for an array of supported element kinds. 23 | 24 | @return An array of element kind strings that the supplementary source handles. 25 | */ 26 | - (NSArray *)supportedElementKinds; 27 | 28 | /** 29 | Asks the SupplementaryViewSource for a configured supplementary view for the specified kind and index. 30 | 31 | @param elementKind The kind of supplementary view being requested 32 | @param index The index for the supplementary veiw being requested. 33 | 34 | @note This is your opportunity to do any supplementary view setup and configuration. 35 | 36 | @warning You should never allocate new views in this method. Instead deque a view from the `IGListCollectionContext`. 37 | */ 38 | - (__kindof UICollectionReusableView *)viewForSupplementaryElementOfKind:(NSString *)elementKind 39 | atIndex:(NSInteger)index; 40 | 41 | /** 42 | Asks the SupplementaryViewSource for the size of a supplementary view for the given kind and index path. 43 | 44 | @param elementKind The kind of supplementary view. 45 | @param index The index of the requested view. 46 | 47 | @return The size for the supplementary view. 48 | */ 49 | - (CGSize)sizeForSupplementaryViewOfKind:(NSString *)elementKind 50 | atIndex:(NSInteger)index; 51 | 52 | @end 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/IGListWorkingRangeDelegate.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @class IGListAdapter; 13 | @class IGListSectionController; 14 | 15 | @protocol IGListSectionType; 16 | 17 | NS_ASSUME_NONNULL_BEGIN 18 | 19 | /** 20 | Implement this protocol to receive working range events for a list. 21 | 22 | The working range is a range *near* the viewport in which you can begin preparing content for display. For example, 23 | you could begin decoding images, or warming text caches. 24 | */ 25 | @protocol IGListWorkingRangeDelegate 26 | 27 | /** 28 | Notifies the delegate that an section controller will enter the working range. 29 | 30 | @param listAdapter The adapter controlling the list. 31 | @param sectionController The section controller entering the range. 32 | */ 33 | - (void)listAdapter:(IGListAdapter *)listAdapter sectionControllerWillEnterWorkingRange:(IGListSectionController *)sectionController; 34 | 35 | /** 36 | Notifies the delegate that an section controller exited the working range. 37 | 38 | @param listAdapter The adapter controlling the list. 39 | @param sectionController The section controller that exited the range. 40 | */ 41 | - (void)listAdapter:(IGListAdapter *)listAdapter sectionControllerDidExitWorkingRange:(IGListSectionController *)sectionController; 42 | 43 | @end 44 | 45 | NS_ASSUME_NONNULL_END 46 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/Internal/IGListAdapterProxy.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | 14 | @class IGListAdapter; 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | /** 19 | A proxy that sends a custom set of selectors to an IGListAdapter object and the rest to a UICollectionViewDelegate 20 | target. 21 | */ 22 | IGLK_SUBCLASSING_RESTRICTED 23 | @interface IGListAdapterProxy : NSProxy 24 | 25 | /** 26 | Create a new proxy object with targets and interceptor. 27 | 28 | @param collectionViewTarget A UICollectionViewDelegate conforming object that receives unintercepted messages. 29 | @param scrollViewTarget A UIScrollViewDelegate conforming object that receives unintercepted messages. 30 | @param interceptor An IGListAdapter object that intercepts a set of messages. 31 | 32 | @return A new IGListAdapterProxy object. 33 | */ 34 | - (instancetype)initWithCollectionViewTarget:(nullable id)collectionViewTarget 35 | scrollViewTarget:(nullable id)scrollViewTarget 36 | interceptor:(IGListAdapter *)interceptor; 37 | 38 | @end 39 | 40 | NS_ASSUME_NONNULL_END 41 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/Internal/IGListDisplayHandler.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | 14 | @class IGListAdapter; 15 | @class IGListSectionController; 16 | 17 | @protocol IGListSectionType; 18 | 19 | NS_ASSUME_NONNULL_BEGIN 20 | 21 | IGLK_SUBCLASSING_RESTRICTED 22 | @interface IGListDisplayHandler : NSObject 23 | 24 | /** 25 | Tells the handler that a cell will be displayed in the IGListKit infra. 26 | 27 | @param cell A cell that will display. 28 | @param listAdapter The adapter managing the infra. 29 | @param sectionController The section controller the cell is in. 30 | @param object The object associated with the section controller. 31 | @param indexPath The index path of the cell in the UICollectionView. 32 | */ 33 | - (void)willDisplayCell:(UICollectionViewCell *)cell 34 | forListAdapter:(IGListAdapter *)listAdapter 35 | sectionController:(IGListSectionController *)sectionController 36 | object:(id)object 37 | indexPath:(NSIndexPath *)indexPath; 38 | 39 | /** 40 | Tells the handler that a cell did end display in the IGListKit infra. 41 | 42 | @param cell A cell that did end display. 43 | @param listAdapter The adapter managing the infra. 44 | @param sectionController The section controller the cell is in. 45 | @param indexPath The index path of the cell in the UICollectionView. 46 | */ 47 | - (void)didEndDisplayingCell:(UICollectionViewCell *)cell 48 | forListAdapter:(IGListAdapter *)listAdapter 49 | sectionController:(IGListSectionController *)sectionController 50 | indexPath:(NSIndexPath *)indexPath; 51 | 52 | @end 53 | 54 | NS_ASSUME_NONNULL_END 55 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/Internal/IGListIndexPathResultInternal.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface IGListIndexPathResult() 17 | 18 | - (instancetype)initWithInserts:(NSArray *)inserts 19 | deletes:(NSArray *)deletes 20 | updates:(NSArray *)updates 21 | moves:(NSArray *)moves 22 | oldIndexPathMap:(NSMapTable, NSIndexPath *> *)oldIndexPathMap 23 | newIndexPathMap:(NSMapTable, NSIndexPath *> *)newIndexPathMap; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/Internal/IGListIndexSetResultInternal.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface IGListIndexSetResult() 17 | 18 | - (instancetype)initWithInserts:(NSIndexSet *)inserts 19 | deletes:(NSIndexSet *)deletes 20 | updates:(NSIndexSet *)updates 21 | moves:(NSArray *)moves 22 | oldIndexMap:(NSMapTable, NSNumber *> *)oldIndexMap 23 | newIndexMap:(NSMapTable, NSNumber *> *)newIndexMap; 24 | 25 | @end 26 | 27 | NS_ASSUME_NONNULL_END 28 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/Internal/IGListMoveIndexInternal.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface IGListMoveIndex () 17 | 18 | - (instancetype)initWithFrom:(NSInteger)from to:(NSInteger)to NS_DESIGNATED_INITIALIZER; 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/Internal/IGListMoveIndexPathInternal.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface IGListMoveIndexPath () 15 | 16 | - (instancetype)initWithFrom:(NSIndexPath *)from to:(NSIndexPath *)to NS_DESIGNATED_INITIALIZER; 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/Internal/IGListSectionControllerInternal.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "IGListSectionController.h" 11 | 12 | FOUNDATION_EXTERN void IGListSectionControllerPushThread(UIViewController *viewController, id collectionContext); 13 | 14 | FOUNDATION_EXTERN void IGListSectionControllerPopThread(void); 15 | 16 | @interface IGListSectionController() 17 | 18 | @property (nonatomic, weak, readwrite) id collectionContext; 19 | 20 | @property (nonatomic, weak, readwrite) UIViewController *viewController; 21 | 22 | @property (nonatomic, assign, readwrite) BOOL isFirstSection; 23 | 24 | @property (nonatomic, assign, readwrite) BOOL isLastSection; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/Internal/IGListStackedSectionControllerInternal.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import "IGListStackedSectionController.h" 13 | 14 | @interface IGListStackedSectionController () 15 | < 16 | IGListCollectionContext, 17 | IGListDisplayDelegate, 18 | IGListScrollDelegate 19 | > 20 | 21 | @property (nonatomic, strong, readonly) NSOrderedSet<__kindof IGListSectionController *> *sectionControllers; 22 | 23 | /// An array the length of the total number of items in the stack, pointing to a section controller for the item index. 24 | @property (nonatomic, copy) NSArray *> *sectionControllersForItems; 25 | 26 | /// An array of index offsets for each item in the flattened stack. 27 | @property (nonatomic, copy) NSArray *sectionControllerOffsets; 28 | 29 | /// A cached collection of the number of items summed from each section controller in the stack. 30 | @property (nonatomic, assign) NSInteger flattenedNumberOfItems; 31 | 32 | /// A counted set of the visible section controllers, used to forward granular display events to child section controllers 33 | @property (nonatomic, strong, readonly) NSCountedSet *visibleSectionControllers; 34 | 35 | - (IGListSectionController *)sectionControllerForObjectIndex:(NSInteger)itemIndex; 36 | - (NSInteger)offsetForSectionController:(IGListSectionController *)sectionController; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/Internal/IGListWorkingRangeHandler.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @class IGListAdapter; 13 | 14 | @protocol IGListSectionType; 15 | 16 | @interface IGListWorkingRangeHandler : NSObject 17 | 18 | /** 19 | Initializes the working range handler. 20 | 21 | @param workingRangeSize the number of sections beyond the visible viewport that should be considered within the working 22 | range. Applies equally in both directions above and below the viewport. 23 | */ 24 | - (instancetype)initWithWorkingRangeSize:(NSInteger)workingRangeSize; 25 | 26 | /** 27 | Tells the handler that a cell will be displayed in the IGListKit infra. 28 | 29 | @param indexPath The index path of the cell in the UICollectionView. 30 | @param listAdapter The adapter managing the infra. 31 | */ 32 | - (void)willDisplayItemAtIndexPath:(NSIndexPath *)indexPath 33 | forListAdapter:(IGListAdapter *)listAdapter; 34 | 35 | /** 36 | Tells the handler that a cell did end display in the IGListKit infra. 37 | 38 | @param indexPath The index path of the cell in the UICollectionView. 39 | @param listAdapter The adapter managing the infra. 40 | */ 41 | - (void)didEndDisplayingItemAtIndexPath:(NSIndexPath *)indexPath 42 | forListAdapter:(IGListAdapter *)listAdapter; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/Internal/UICollectionView+IGListBatchUpdateData.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | @class IGListBatchUpdateData; 13 | 14 | @interface UICollectionView (IGListBatchUpdateData) 15 | 16 | - (void)ig_applyBatchUpdateData:(IGListBatchUpdateData *)updateData; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/Internal/UICollectionView+IGListBatchUpdateData.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "UICollectionView+IGListBatchUpdateData.h" 11 | 12 | #import "IGListBatchUpdateData.h" 13 | 14 | @implementation UICollectionView (IGListBatchUpdateData) 15 | 16 | - (void)ig_applyBatchUpdateData:(IGListBatchUpdateData *)updateData { 17 | [self deleteItemsAtIndexPaths:[updateData.deleteIndexPaths allObjects]]; 18 | [self insertItemsAtIndexPaths:[updateData.insertIndexPaths allObjects]]; 19 | [self reloadItemsAtIndexPaths:[updateData.reloadIndexPaths allObjects]]; 20 | 21 | for (IGListMoveIndex *move in updateData.moveSections) { 22 | [self moveSection:move.from toSection:move.to]; 23 | } 24 | 25 | [self deleteSections:updateData.deleteSections]; 26 | [self insertSections:updateData.insertSections]; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/NSNumber+IGListDiffable.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | 14 | /** 15 | This category provides default `IGListDiffable` conformance for `NSNumber`. 16 | */ 17 | @interface NSNumber (IGListDiffable) 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/NSNumber+IGListDiffable.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "NSNumber+IGListDiffable.h" 11 | 12 | @implementation NSNumber (IGListDiffable) 13 | 14 | - (id)diffIdentifier { 15 | return self; 16 | } 17 | 18 | - (BOOL)isEqualToDiffableObject:(id)object { 19 | return [self isEqual:object]; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/NSString+IGListDiffable.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import 11 | 12 | #import 13 | 14 | /** 15 | This category provides default `IGListDiffable` conformance for `NSString`. 16 | */ 17 | @interface NSString (IGListDiffable) 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /Pods/IGListKit/Source/NSString+IGListDiffable.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2016-present, Facebook, Inc. 3 | * All rights reserved. 4 | * 5 | * This source code is licensed under the BSD-style license found in the 6 | * LICENSE file in the root directory of this source tree. An additional grant 7 | * of patent rights can be found in the PATENTS file in the same directory. 8 | */ 9 | 10 | #import "NSString+IGListDiffable.h" 11 | 12 | @implementation NSString (IGListDiffable) 13 | 14 | - (id)diffIdentifier { 15 | return self; 16 | } 17 | 18 | - (BOOL)isEqualToDiffableObject:(id)object { 19 | return [self isEqual:object]; 20 | } 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /Pods/Local Podspecs/IGListKit.podspec.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "IGListKit", 3 | "version": "1.0.0", 4 | "summary": "A data-driven UICollectionView framework.", 5 | "homepage": "https://github.com/Instagram/IGListKit", 6 | "documentation_url": "https://instagram.github.io/IGListKit", 7 | "description": "A data-driven UICollectionView framework for building fast and flexible lists.", 8 | "license": { 9 | "type": "BSD" 10 | }, 11 | "authors": "Instagram", 12 | "social_media_url": "https://twitter.com/fbOpenSource", 13 | "source": { 14 | "git": "https://github.com/Instagram/IGListKit.git", 15 | "tag": "1.0.0", 16 | "branch": "stable" 17 | }, 18 | "source_files": "Source/**/*.{h,m,mm}", 19 | "private_header_files": "Source/Internal/*.h", 20 | "requires_arc": true, 21 | "platforms": { 22 | "ios": "8.0", 23 | "tvos": "9.0" 24 | }, 25 | "frameworks": "UIKit", 26 | "libraries": "c++", 27 | "pod_target_xcconfig": { 28 | "CLANG_CXX_LANGUAGE_STANDARD": "c++11", 29 | "CLANG_CXX_LIBRARY": "libc++" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - IGListKit (2.0.0) 3 | 4 | DEPENDENCIES: 5 | - IGListKit 6 | 7 | SPEC CHECKSUMS: 8 | IGListKit: ee255e4e61865bc3e01ca96b690c5435c2887849 9 | 10 | PODFILE CHECKSUM: f19cdf1c0457c987c46c6bbd1fd4c966fe79fd7b 11 | 12 | COCOAPODS: 1.1.1 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IGListKit/IGListKit-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_IGListKit : NSObject 3 | @end 4 | @implementation PodsDummy_IGListKit 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IGListKit/IGListKit-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IGListKit/IGListKit-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | #import "IGListAdapter.h" 6 | #import "IGListAdapterDataSource.h" 7 | #import "IGListAdapterDelegate.h" 8 | #import "IGListAdapterUpdater.h" 9 | #import "IGListAdapterUpdaterDelegate.h" 10 | #import "IGListAssert.h" 11 | #import "IGListBatchUpdateData.h" 12 | #import "IGListCollectionContext.h" 13 | #import "IGListCollectionView.h" 14 | #import "IGListDiff.h" 15 | #import "IGListDiffable.h" 16 | #import "IGListDisplayDelegate.h" 17 | #import "IGListExperiments.h" 18 | #import "IGListGridCollectionViewLayout.h" 19 | #import "IGListIndexPathResult.h" 20 | #import "IGListIndexSetResult.h" 21 | #import "IGListKit.h" 22 | #import "IGListMacros.h" 23 | #import "IGListMoveIndex.h" 24 | #import "IGListMoveIndexPath.h" 25 | #import "IGListReloadDataUpdater.h" 26 | #import "IGListScrollDelegate.h" 27 | #import "IGListSectionController.h" 28 | #import "IGListSectionType.h" 29 | #import "IGListSingleSectionController.h" 30 | #import "IGListStackedSectionController.h" 31 | #import "IGListSupplementaryViewSource.h" 32 | #import "IGListUpdatingDelegate.h" 33 | #import "IGListWorkingRangeDelegate.h" 34 | #import "NSNumber+IGListDiffable.h" 35 | #import "NSString+IGListDiffable.h" 36 | 37 | FOUNDATION_EXPORT double IGListKitVersionNumber; 38 | FOUNDATION_EXPORT const unsigned char IGListKitVersionString[]; 39 | 40 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IGListKit/IGListKit.modulemap: -------------------------------------------------------------------------------- 1 | framework module IGListKit { 2 | umbrella header "IGListKit-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IGListKit/IGListKit.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_CXX_LANGUAGE_STANDARD = c++11 2 | CLANG_CXX_LIBRARY = libc++ 3 | CONFIGURATION_BUILD_DIR = $PODS_CONFIGURATION_BUILD_DIR/IGListKit 4 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 5 | HEADER_SEARCH_PATHS = "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Public" 6 | OTHER_LDFLAGS = -l"c++" -framework "UIKit" 7 | PODS_BUILD_DIR = $BUILD_DIR 8 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_ROOT = ${SRCROOT} 10 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 11 | SKIP_INSTALL = YES 12 | -------------------------------------------------------------------------------- /Pods/Target Support Files/IGListKit/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 | FMWK 17 | CFBundleShortVersionString 18 | 2.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeather/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeather/Pods-SimpleWeather-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## IGListKit 5 | 6 | BSD License 7 | 8 | For `IGListKit` software 9 | 10 | Copyright (c) 2016, Facebook, Inc. All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without modification, 13 | are permitted provided that the following conditions are met: 14 | 15 | * Redistributions of source code must retain the above copyright notice, this 16 | list of conditions and the following disclaimer. 17 | 18 | * Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | 22 | * Neither the name Facebook nor the names of its contributors may be used to 23 | endorse or promote products derived from this software without specific 24 | prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 27 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 28 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 30 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 31 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 32 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 33 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 35 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | 37 | Generated by CocoaPods - https://cocoapods.org 38 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeather/Pods-SimpleWeather-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_SimpleWeather : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_SimpleWeather 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeather/Pods-SimpleWeather-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | 6 | FOUNDATION_EXPORT double Pods_SimpleWeatherVersionNumber; 7 | FOUNDATION_EXPORT const unsigned char Pods_SimpleWeatherVersionString[]; 8 | 9 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeather/Pods-SimpleWeather.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/IGListKit" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/IGListKit/IGListKit.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "IGListKit" 7 | PODS_BUILD_DIR = $BUILD_DIR 8 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeather/Pods-SimpleWeather.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_SimpleWeather { 2 | umbrella header "Pods-SimpleWeather-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeather/Pods-SimpleWeather.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/IGListKit" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/IGListKit/IGListKit.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "IGListKit" 7 | PODS_BUILD_DIR = $BUILD_DIR 8 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeatherTests/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 | FMWK 17 | CFBundleShortVersionString 18 | 1.0.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | ${CURRENT_PROJECT_VERSION} 23 | NSPrincipalClass 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeatherTests/Pods-SimpleWeatherTests-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## IGListKit 5 | 6 | BSD License 7 | 8 | For `IGListKit` software 9 | 10 | Copyright (c) 2016, Facebook, Inc. All rights reserved. 11 | 12 | Redistribution and use in source and binary forms, with or without modification, 13 | are permitted provided that the following conditions are met: 14 | 15 | * Redistributions of source code must retain the above copyright notice, this 16 | list of conditions and the following disclaimer. 17 | 18 | * Redistributions in binary form must reproduce the above copyright notice, 19 | this list of conditions and the following disclaimer in the documentation 20 | and/or other materials provided with the distribution. 21 | 22 | * Neither the name Facebook nor the names of its contributors may be used to 23 | endorse or promote products derived from this software without specific 24 | prior written permission. 25 | 26 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 27 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 28 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 29 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 30 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 31 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 32 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 33 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 34 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 35 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | 37 | Generated by CocoaPods - https://cocoapods.org 38 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeatherTests/Pods-SimpleWeatherTests-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_SimpleWeatherTests : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_SimpleWeatherTests 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeatherTests/Pods-SimpleWeatherTests-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #endif 4 | 5 | 6 | FOUNDATION_EXPORT double Pods_SimpleWeatherTestsVersionNumber; 7 | FOUNDATION_EXPORT const unsigned char Pods_SimpleWeatherTestsVersionString[]; 8 | 9 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeatherTests/Pods-SimpleWeatherTests.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/IGListKit" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/IGListKit/IGListKit.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "IGListKit" 7 | PODS_BUILD_DIR = $BUILD_DIR 8 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeatherTests/Pods-SimpleWeatherTests.modulemap: -------------------------------------------------------------------------------- 1 | framework module Pods_SimpleWeatherTests { 2 | umbrella header "Pods-SimpleWeatherTests-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-SimpleWeatherTests/Pods-SimpleWeatherTests.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = NO 2 | FRAMEWORK_SEARCH_PATHS = $(inherited) "$PODS_CONFIGURATION_BUILD_DIR/IGListKit" 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LD_RUNPATH_SEARCH_PATHS = $(inherited) '@executable_path/Frameworks' '@loader_path/Frameworks' 5 | OTHER_CFLAGS = $(inherited) -iquote "$PODS_CONFIGURATION_BUILD_DIR/IGListKit/IGListKit.framework/Headers" 6 | OTHER_LDFLAGS = $(inherited) -framework "IGListKit" 7 | PODS_BUILD_DIR = $BUILD_DIR 8 | PODS_CONFIGURATION_BUILD_DIR = $PODS_BUILD_DIR/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 9 | PODS_ROOT = ${SRCROOT}/Pods 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | ``` 5 | sudo gem install fastlane 6 | ``` 7 | # Available Actions 8 | ### beta 9 | ``` 10 | fastlane beta 11 | ``` 12 | 13 | 14 | ---- 15 | 16 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 17 | More information about fastlane can be found on [https://fastlane.tools](https://fastlane.tools). 18 | The documentation of fastlane can be found on [GitHub](https://github.com/fastlane/fastlane/tree/master/fastlane). 19 | -------------------------------------------------------------------------------- /SimpleWeather.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SimpleWeather.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /SimpleWeather/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/.DS_Store -------------------------------------------------------------------------------- /SimpleWeather/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/13/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | let savedLocationStore = SavedLocationStore() 17 | 18 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 19 | if let nav = window?.rootViewController as? UINavigationController, 20 | let controller = nav.viewControllers.first as? SavedLocationsViewController { 21 | controller.store = savedLocationStore 22 | } 23 | return true 24 | } 25 | 26 | func applicationDidEnterBackground(_ application: UIApplication) { 27 | savedLocationStore.save() 28 | } 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Classes/.DS_Store -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/Alert.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Alert.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/14/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Alert { 12 | 13 | enum Significance { 14 | case warning, watch, advisory, statement, forecast, outlook, synopsys, unknown 15 | } 16 | 17 | let description: String 18 | let date: Date 19 | let expires: Date 20 | let message: String 21 | let phenomena: String 22 | let significance: Significance 23 | 24 | } 25 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/Astronomy.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Astronomy.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/25/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Astronomy { 12 | 13 | let sunrise: Date 14 | let sunset: Date 15 | let moonrise: Date 16 | let moonset: Date 17 | let moonIllumination: Double 18 | let moonAge: Int 19 | let moonPhaseDescription: String 20 | let moonHemisphere: String 21 | 22 | } 23 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/Condition+Icon.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Condition+Icon.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/26/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Condition { 12 | 13 | func icon(night: Bool = false) -> String { 14 | switch self { 15 | case .chanceflurries, .chancesnow, .chancesleet, .flurries, .sleet, .snow: 16 | return "Snow" 17 | case .chancerain, .rain: 18 | return "Mist" 19 | case .chancetstorms, .tstorms: 20 | return "Thunder-cloud" 21 | case .clear, .sunny: 22 | return night ? "Moon" : "Sun" 23 | case .cloudy: 24 | return "Cloudy" 25 | case .fog, .hazy: 26 | return "Haze" 27 | case .mostlycloudy, .partlysunny: 28 | return night ? "Mostly-sunny-night" : "Partly-sunny-day" 29 | case .partlycloudy, .mostlysunny: 30 | return night ? "Mostly-sunny-night" : "Mostly-sunny-day" 31 | case .unknown: 32 | return "" 33 | } 34 | 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/Condition.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConditionsIcon.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/14/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum Condition { 12 | case chanceflurries, chancerain, chancesleet, chancesnow, chancetstorms, clear, cloudy, flurries, fog, hazy, mostlycloudy, mostlysunny, partlycloudy, partlysunny, sleet, rain, snow, sunny, tstorms, unknown 13 | 14 | static func from(string: String) -> Condition { 15 | switch string { 16 | case "chanceflurries": return .chanceflurries 17 | case "chancerain": return .chancerain 18 | case "chancesleet": return .chancesleet 19 | case "chancesnow": return .chancesnow 20 | case "chancetstorms": return .chancetstorms 21 | case "clear": return .clear 22 | case "cloudy": return .cloudy 23 | case "flurries": return .flurries 24 | case "fog": return .fog 25 | case "hazy": return .hazy 26 | case "mostlycloudy": return .mostlycloudy 27 | case "mostlysunny": return .mostlysunny 28 | case "partlycloudy": return .partlycloudy 29 | case "partlysunny": return .partlysunny 30 | case "sleet": return .sleet 31 | case "rain": return .rain 32 | case "snow": return .snow 33 | case "sunny": return .sunny 34 | case "tstorms": return .tstorms 35 | default: return .unknown 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/ConditionsSection+Forecast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConditionsSection+Forecast.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 12/11/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension ConditionsSection { 12 | 13 | static func from(forecast: Forecast?) -> ConditionsSection? { 14 | guard let observation = forecast?.observation, 15 | let today = forecast?.daily?.sorted(by: { $0.date < $1.date }).first, 16 | let astronomy = forecast?.astronomy 17 | else { return nil } 18 | 19 | return ConditionsSection( 20 | temperature: Int(observation.temp), 21 | high: today.high, 22 | low: today.low, 23 | condition: observation.condition, 24 | feelsLike: observation.feelslike, 25 | wind: observation.wind, 26 | date: observation.date, 27 | humidity: observation.humidity, 28 | dewpoint: observation.dewpoint, 29 | pressure: observation.pressure, 30 | visibility: observation.visibility, 31 | precip_1hr: observation.precip_1hr, 32 | description: observation.weather, 33 | timeOfDay: Date().timeOfDay(sunrise: astronomy.sunrise, sunset: astronomy.sunset) 34 | ) 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/ConditionsSection+IGListDiffable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConditionsSection+IGListDiffable.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 12/11/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IGListKit 11 | 12 | extension ConditionsSection: IGListDiffable { 13 | 14 | func diffIdentifier() -> NSObjectProtocol { 15 | return (description + date.description) as NSString 16 | } 17 | 18 | func isEqual(toDiffableObject object: IGListDiffable?) -> Bool { 19 | if self === object { return true } 20 | guard let rhs = object as? ConditionsSection else { return false } 21 | return temperature == rhs.temperature 22 | && high == rhs.high 23 | && low == rhs.low 24 | && condition == rhs.condition 25 | && feelsLike == rhs.feelsLike 26 | && wind == rhs.wind 27 | && date == rhs.date 28 | && humidity == rhs.humidity 29 | && dewpoint == rhs.dewpoint 30 | && pressure == rhs.pressure 31 | && visibility == rhs.visibility 32 | && precip_1hr == rhs.precip_1hr 33 | && description == rhs.description 34 | && timeOfDay == rhs.timeOfDay 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/ConditionsSection+ViewModels.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConditionsSection+ViewModels.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 12/11/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension ConditionsSection { 12 | 13 | var viewModels: [Any] { 14 | var models = [Any]() 15 | 16 | let conditions = ConditionsCellViewModel( 17 | temperature: temperature, 18 | high: high, 19 | low: low, 20 | conditionImageName: condition.icon(night: timeOfDay == .night), 21 | feelsLike: feelsLike 22 | ) 23 | models.append(conditions) 24 | 25 | models.append(ConditionsDetailCellViewModel(title: "Conditions", detail: description)) 26 | models.append(ConditionsDetailCellViewModel(title: "Feels Like", detail: String(format: "%zi°", feelsLike))) 27 | 28 | let windString = wind.speed > 0 ? String(format: "%.0f mph %@", wind.speed, wind.direction) : "--" 29 | models.append(ConditionsDetailCellViewModel(title: "Wind", detail: windString)) 30 | 31 | models.append(ConditionsDetailCellViewModel(title: "Humidity", detail: String(format: "%@", humidity))) 32 | models.append(ConditionsDetailCellViewModel(title: "Dewpoint", detail: String(format: "%.0f°", dewpoint))) 33 | models.append(ConditionsDetailCellViewModel(title: "Pressure", detail: String(format: "%.2f", pressure))) 34 | models.append(ConditionsDetailCellViewModel(title: "Rainfall (1h)", detail: String(format: "%.0f in", precip_1hr))) 35 | 36 | return models 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/ConditionsSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConditionsSection.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 12/11/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ConditionsSection { 12 | 13 | let temperature: Int 14 | let high: Int 15 | let low: Int 16 | let condition: Condition 17 | let feelsLike: Int 18 | let wind: Wind 19 | let date: Date 20 | let humidity: String 21 | let dewpoint: Double 22 | let pressure: Double 23 | let visibility: Double 24 | let precip_1hr: Double 25 | let description: String 26 | let timeOfDay: TimeOfDay 27 | 28 | init( 29 | temperature: Int, 30 | high: Int, 31 | low: Int, 32 | condition: Condition, 33 | feelsLike: Int, 34 | wind: Wind, 35 | date: Date, 36 | humidity: String, 37 | dewpoint: Double, 38 | pressure: Double, 39 | visibility: Double, 40 | precip_1hr: Double, 41 | description: String, 42 | timeOfDay: TimeOfDay 43 | ) { 44 | self.temperature = temperature 45 | self.high = high 46 | self.low = low 47 | self.condition = condition 48 | self.feelsLike = feelsLike 49 | self.wind = wind 50 | self.date = date 51 | self.humidity = humidity 52 | self.dewpoint = dewpoint 53 | self.pressure = pressure 54 | self.visibility = visibility 55 | self.precip_1hr = precip_1hr 56 | self.description = description 57 | self.timeOfDay = timeOfDay 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/DailyForecastSection+Forecast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DailyForecastSection+Forecast.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/21/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension DailyForecastSection { 12 | 13 | static func from(forecast: Forecast) -> DailyForecastSection? { 14 | guard let date = forecast.observation?.date, 15 | let daily = forecast.daily?.sorted(by: { $0.date < $1.date }) 16 | else { return nil } 17 | 18 | let calendar = Calendar.current 19 | let components: Set = [.day] 20 | let observationDay = calendar.dateComponents(components, from: date).day 21 | 22 | var viewModels = [ForecastDayCellViewModel]() 23 | let limit = 10 24 | for day in daily { 25 | let count = viewModels.count 26 | // dont include daily forecasts when already have observations 27 | guard count < limit else { break } 28 | guard observationDay != calendar.dateComponents(components, from: day.date).day else { continue } 29 | 30 | let isFirst = count == 0 31 | let isLast = count == limit - 2 || day == daily.last 32 | let position: ForecastDayCellPosition = isFirst ? .top : isLast ? .bottom : .none 33 | 34 | let viewModel = ForecastDayCellViewModel( 35 | date: day.date, 36 | high: 37 | day.high, 38 | low: day.low, 39 | conditionImageName: day.condition.icon(), 40 | chancePrecip: day.pop, 41 | position: position 42 | ) 43 | viewModels.append(viewModel) 44 | } 45 | return DailyForecastSection( 46 | currentDate: date, 47 | viewModels: viewModels 48 | ) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/DailyForecastSection+IGListDiffable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DailyForecastSection+IGListDiffable.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/20/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IGListKit 11 | 12 | extension DailyForecastSection: IGListDiffable { 13 | 14 | func diffIdentifier() -> NSObjectProtocol { 15 | return "daily" as NSString 16 | } 17 | 18 | func isEqual(toDiffableObject object: IGListDiffable?) -> Bool { 19 | if self === object { return true } 20 | guard let object = object as? DailyForecastSection else { return false } 21 | return viewModels == object.viewModels 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/DailyForecastSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DailyForecastSection.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/20/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class DailyForecastSection { 12 | 13 | let currentDate: Date 14 | let viewModels: [ForecastDayCellViewModel] 15 | 16 | init(currentDate: Date, viewModels: [ForecastDayCellViewModel]) { 17 | self.currentDate = currentDate 18 | self.viewModels = viewModels 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/EmbeddedSection+Forecast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmbeddedSection+Forecast.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/24/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension EmbeddedSection { 12 | 13 | static func from(forecast: Forecast) -> EmbeddedSection? { 14 | guard let hourly = forecast.hourly?.sorted(by: { $0.date < $1.date }), 15 | let sunrise = forecast.astronomy?.sunrise, 16 | let sunset = forecast.astronomy?.sunset 17 | else { return nil } 18 | 19 | var viewModels = [ForecastHourCellViewModel]() 20 | let limit = 12 21 | for hour in hourly { 22 | let timeOfDay = hour.date.timeOfDay(sunrise: sunrise, sunset: sunset) 23 | let viewModel = ForecastHourCellViewModel( 24 | date: hour.date, 25 | temp: hour.temp, 26 | conditionImageName: hour.condition.icon(night: timeOfDay == .night), 27 | chancePrecip: hour.pop 28 | ) 29 | viewModels.append(viewModel) 30 | if viewModels.count >= limit { break } 31 | } 32 | return EmbeddedSection(identifier: "hourly" as NSString, items: viewModels) 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/EmbeddedSection+IGListDiffable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmbeddedSection+IGListDiffable.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/24/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IGListKit 11 | 12 | extension EmbeddedSection: IGListDiffable { 13 | 14 | func diffIdentifier() -> NSObjectProtocol { 15 | return identifier 16 | } 17 | 18 | func isEqual(toDiffableObject object: IGListDiffable?) -> Bool { 19 | if self === object { return true } 20 | guard let object = object as? EmbeddedSection else { return false } 21 | return identifier.isEqual(object.identifier) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/EmbeddedSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmbeddedSection.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/24/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IGListKit 11 | 12 | class EmbeddedSection { 13 | 14 | let identifier: NSObjectProtocol 15 | let items: [IGListDiffable] 16 | 17 | init(identifier: NSObjectProtocol, items: [IGListDiffable]) { 18 | self.identifier = identifier 19 | self.items = items 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/Forecast+JSONConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Forecast+JSONConvertible.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/19/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Forecast: JSONConvertible { 12 | 13 | static func fromJSON(json: [String : Any]) -> Forecast? { 14 | let observation: Observation? 15 | if let json = json["current_observation"] as? [String: Any] { 16 | observation = Observation.fromJSON(json: json) 17 | } else { 18 | observation = nil 19 | } 20 | 21 | let location: Location? 22 | if let json = json["location"] as? [String: Any] { 23 | location = Location.fromJSON(json: json) 24 | } else { 25 | location = nil 26 | } 27 | 28 | let alerts: [Alert]? = (json["alerts"] as? [ [String: Any] ])?.flatMap({ (json: [String: Any]) in 29 | return Alert.fromJSON(json: json) 30 | }) 31 | 32 | let daily: [ForecastDay]? = (keypath(dict: json, path: "forecast.simpleforecast.forecastday") as [ [String: Any] ]?)?.flatMap({ (json: [String: Any]) in 33 | return ForecastDay.fromJSON(json: json) 34 | }) 35 | 36 | let hourly: [ForecastHourly]? = (json["hourly_forecast"] as? [ [String: Any] ])?.flatMap({ (json: [String: Any]) in 37 | return ForecastHourly.fromJSON(json: json) 38 | }) 39 | 40 | let astronomy: Astronomy? 41 | if let json = json["moon_phase"] as? [String: Any] { 42 | astronomy = Astronomy.fromJSON(json: json) 43 | } else { 44 | astronomy = nil 45 | } 46 | 47 | return Forecast( 48 | location: location, 49 | observation: observation, 50 | alerts: alerts, 51 | daily: daily, 52 | hourly: hourly, 53 | astronomy: astronomy 54 | ) 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/Forecast.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Forecast.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/19/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Forecast { 12 | 13 | let location: Location? 14 | let observation: Observation? 15 | let alerts: [Alert]? 16 | let daily: [ForecastDay]? 17 | let hourly: [ForecastHourly]? 18 | let astronomy: Astronomy? 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/ForecastDay+Equatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastDay+Equatable.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 12/10/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension ForecastDay: Equatable { 12 | 13 | public static func ==(lhs: ForecastDay, rhs: ForecastDay) -> Bool { 14 | return lhs.date == rhs.date 15 | && lhs.high == rhs.high 16 | && lhs.low == rhs.low 17 | && lhs.description == rhs.description 18 | && lhs.pop == rhs.pop 19 | && lhs.qpf_allday == rhs.qpf_allday 20 | && lhs.qpf_day == rhs.qpf_day 21 | && lhs.qpf_night == rhs.qpf_night 22 | && lhs.snow_allday == rhs.snow_allday 23 | && lhs.snow_day == rhs.snow_day 24 | && lhs.snow_night == rhs.snow_night 25 | && lhs.avehumidity == rhs.avehumidity 26 | && lhs.maxhumidity == rhs.maxhumidity 27 | && lhs.minhumidity == rhs.minhumidity 28 | && lhs.condition == rhs.condition 29 | && lhs.max_wind == rhs.max_wind 30 | && lhs.average_wind == rhs.average_wind 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/ForecastDay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastDay.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/14/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ForecastDay { 12 | 13 | let date: Date 14 | let high: Int 15 | let low: Int 16 | let description: String 17 | let pop: Double 18 | let qpf_allday: Double 19 | let qpf_day: Double 20 | let qpf_night: Double 21 | let snow_allday: Double 22 | let snow_day: Double 23 | let snow_night: Double 24 | let avehumidity: Int 25 | let maxhumidity: Int 26 | let minhumidity: Int 27 | let condition: Condition 28 | let max_wind: Wind? 29 | let average_wind: Wind? 30 | 31 | } 32 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/ForecastHourly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastHourly.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/14/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ForecastHourly { 12 | 13 | let date: Date 14 | let temp: Int 15 | let dewpoint: Int 16 | let description: String 17 | let wx: String 18 | let uvi: Int 19 | let humidity: Int 20 | let windchill: Int 21 | let heatindex: Int 22 | let feelslike: Int 23 | let qpf: Double 24 | let snow: Double 25 | let pop: Double 26 | let mslp: Double 27 | let wind: Wind 28 | let condition: Condition 29 | 30 | } 31 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/JSONConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JSONConvertible.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/15/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | protocol JSONConvertible { 12 | static func fromJSON(json: [String: Any]) -> Self? 13 | } 14 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/Location+JSONConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Location+JSONConvertible.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/15/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Location: JSONConvertible { 12 | 13 | static func fromJSON(json: [String : Any]) -> Location? { 14 | guard let type = json["type"] as? String, 15 | let country = json["country"] as? String, 16 | let country_iso3166 = json["country_iso3166"] as? String, 17 | let country_name = json["country_name"] as? String, 18 | let state = json["state"] as? String, 19 | let city = json["city"] as? String, 20 | let tz_short = json["tz_short"] as? String, 21 | let tz_long = json["tz_long"] as? String, 22 | let lat = (json["lat"] as? NSString)?.doubleValue, 23 | let lon = (json["lon"] as? NSString)?.doubleValue, 24 | let zip = (json["zip"] as? NSString)?.integerValue, 25 | let magic = json["magic"] as? String, 26 | let wmo = json["wmo"] as? String, 27 | let l = json["l"] as? String, 28 | let requesturl = json["requesturl"] as? String, 29 | let wuiurl = json["wuiurl"] as? String 30 | else { return nil } 31 | return Location( 32 | type: type, 33 | country: country, 34 | country_iso3166: country_iso3166, 35 | country_name: country_name, 36 | state: state, 37 | city: city, 38 | tz_short: tz_short, 39 | tz_long: tz_long, 40 | lat: lat, 41 | lon: lon, 42 | zip: zip, 43 | magic: magic, 44 | wmo: wmo, 45 | l: l, 46 | requesturl: requesturl, 47 | wuiurl: wuiurl 48 | ) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/Location.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Location.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/14/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Location { 12 | 13 | let type: String 14 | let country: String 15 | let country_iso3166: String 16 | let country_name: String 17 | let state: String 18 | let city: String 19 | let tz_short: String 20 | let tz_long: String 21 | let lat: Double 22 | let lon: Double 23 | let zip: Int 24 | let magic: String 25 | let wmo: String 26 | let l: String 27 | let requesturl: String 28 | let wuiurl: String 29 | 30 | } 31 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/MKLocalSearchCompletion+IGListDiffable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MKLocalSearchCompletion+IGListDiffable.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/8/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IGListKit 11 | import MapKit 12 | 13 | extension MKLocalSearchCompletion: IGListDiffable { 14 | 15 | public func diffIdentifier() -> NSObjectProtocol { 16 | return (title + subtitle) as NSString 17 | } 18 | 19 | public func isEqual(toDiffableObject object: IGListDiffable?) -> Bool { 20 | if self === object { return true } 21 | guard let object = object as? MKLocalSearchCompletion else { return false } 22 | return title == object.title 23 | && subtitle == object.subtitle 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/Observation+JSONConvertible.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Conditions+JSONConvertible.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/16/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Observation: JSONConvertible { 12 | 13 | static func fromJSON(json: [String : Any]) -> Observation? { 14 | guard let epoch_string = json["observation_epoch"] as? String, 15 | let epoch_interval = TimeInterval(epoch_string), 16 | let weather = json["weather"] as? String, 17 | let temp = json["temp_f"] as? Double, 18 | let humidity = json["relative_humidity"] as? String, 19 | let wdir = json["wind_dir"] as? String, 20 | let wspd = json["wind_mph"] as? Double, 21 | let pressure = (json["pressure_in"] as? NSString)?.doubleValue, 22 | let dewpoint = json["dewpoint_f"] as? Double, 23 | let feelslike = (json["feelslike_f"] as? NSString)?.integerValue, 24 | let visibility = (json["visibility_mi"] as? NSString)?.doubleValue, 25 | let uvi = (json["UV"] as? NSString)?.integerValue, 26 | let precip_1hr = (json["precip_1hr_in"] as? NSString)?.doubleValue, 27 | let precip_day = (json["precip_today_in"] as? NSString)?.doubleValue, 28 | let icon_string = json["icon"] as? String 29 | else { return nil } 30 | 31 | let condition = Condition.from(string: icon_string) 32 | let wind = Wind(speed: max(wspd, 0), direction: wdir) 33 | 34 | return Observation( 35 | date: Date(timeIntervalSince1970: epoch_interval), 36 | weather: weather, 37 | temp: temp, 38 | humidity: humidity, 39 | wind: wind, 40 | pressure: pressure, 41 | dewpoint: dewpoint, 42 | feelslike: feelslike, 43 | visibility: max(visibility, 0), 44 | uvi: uvi, 45 | precip_1hr: max(precip_1hr, 0), 46 | precip_day: max(precip_day, 0), 47 | condition: condition 48 | ) 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/Observation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Conditions.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/16/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Observation { 12 | 13 | let date: Date 14 | let weather: String 15 | let temp: Double 16 | let humidity: String 17 | let wind: Wind 18 | let pressure: Double 19 | let dewpoint: Double 20 | let feelslike: Int 21 | let visibility: Double 22 | let uvi: Int 23 | let precip_1hr: Double 24 | let precip_day: Double 25 | let condition: Condition 26 | 27 | } 28 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/RadarSection+IGListDiffable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RadarSection+IGListDiffable.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/27/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IGListKit 11 | 12 | extension RadarSection: IGListDiffable { 13 | 14 | func diffIdentifier() -> NSObjectProtocol { 15 | return self 16 | } 17 | 18 | func isEqual(toDiffableObject object: IGListDiffable?) -> Bool { 19 | return isEqual(object) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/RadarSection.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RadarSection.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/27/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CoreLocation 11 | import MapKit 12 | 13 | class RadarSection: NSObject { 14 | 15 | let center: CLLocationCoordinate2D 16 | 17 | init(center: CLLocationCoordinate2D) { 18 | self.center = center 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/SavedLocation+Equatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SavedLocation+Equatable.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/8/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // SavedLocation inherits from NSObject so not using Equatable protocol 12 | func ==(lhs: SavedLocation, rhs: SavedLocation) -> Bool { 13 | return lhs.userLocation == rhs.userLocation 14 | && lhs.latitude == rhs.latitude 15 | && lhs.longitude == rhs.longitude 16 | && lhs.name == rhs.name 17 | } 18 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/SavedLocation+IGListDiffable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SavedLocation+IGListDiffable.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 12/18/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IGListKit 11 | 12 | extension SavedLocation: IGListDiffable { 13 | 14 | func diffIdentifier() -> NSObjectProtocol { 15 | return name as NSString 16 | } 17 | 18 | func isEqual(toDiffableObject object: IGListDiffable?) -> Bool { 19 | if self === object { return true } 20 | guard let rhs = object as? SavedLocation else { return false } 21 | return name == rhs.name 22 | && latitude == rhs.latitude 23 | && longitude == rhs.longitude 24 | && userLocation == rhs.userLocation 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/SavedLocation+NSEncoding.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SavedLocation+NSEncoding.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 12/18/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let SavedLocationNameKey = "name" 12 | let SavedLocationLatitudeKey = "latitude" 13 | let SavedLocationLongitudeKey = "longitude" 14 | let SavedLocationUserLocationKey = "userLocation" 15 | 16 | extension SavedLocation: NSCoding { 17 | 18 | convenience init?(coder aDecoder: NSCoder) { 19 | guard let name = aDecoder.decodeObject(forKey: SavedLocationNameKey) as? String 20 | else { return nil } 21 | let latitude = aDecoder.decodeDouble(forKey: SavedLocationLatitudeKey) 22 | let longitude = aDecoder.decodeDouble(forKey: SavedLocationLongitudeKey) 23 | let userLocation = aDecoder.decodeBool(forKey: SavedLocationUserLocationKey) 24 | self.init(name: name, latitude: latitude, longitude: longitude, userLocation: userLocation) 25 | } 26 | 27 | func encode(with aCoder: NSCoder) { 28 | aCoder.encode(name, forKey: SavedLocationNameKey) 29 | aCoder.encode(latitude, forKey: SavedLocationLatitudeKey) 30 | aCoder.encode(longitude, forKey: SavedLocationLongitudeKey) 31 | aCoder.encode(userLocation, forKey: SavedLocationUserLocationKey) 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/SavedLocation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SavedLocation.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 12/18/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | final class SavedLocation: NSObject { 12 | 13 | let name: String 14 | let latitude: Double 15 | let longitude: Double 16 | let userLocation: Bool 17 | 18 | init(name: String, latitude: Double, longitude: Double, userLocation: Bool) { 19 | self.name = name 20 | self.latitude = latitude 21 | self.longitude = longitude 22 | self.userLocation = userLocation 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Models/Wind.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Wind.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/15/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct Wind { 12 | 13 | let speed: Double 14 | let direction: String 15 | 16 | } 17 | 18 | extension Wind: Equatable { 19 | 20 | public static func ==(lhs: Wind, rhs: Wind) -> Bool { 21 | return lhs.speed == rhs.speed 22 | && lhs.direction == rhs.direction 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/SectionControllers/DailyForecastSectionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DailyForecastSectionController.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/20/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import IGListKit 11 | 12 | class DailyForecastSectionController: IGListSectionController, IGListSectionType { 13 | 14 | var model: DailyForecastSection? 15 | 16 | override init() { 17 | super.init() 18 | inset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8) 19 | } 20 | 21 | // MARK: IGListSectionType 22 | 23 | func numberOfItems() -> Int { 24 | return model?.viewModels.count ?? 0 25 | } 26 | 27 | func sizeForItem(at index: Int) -> CGSize { 28 | guard let size = collectionContext?.containerSize else { return .zero } 29 | return CGSize(width: size.width - inset.left - inset.right, height: 35) 30 | } 31 | 32 | func cellForItem(at index: Int) -> UICollectionViewCell { 33 | guard let context = collectionContext, 34 | let viewModel = model?.viewModels[index], 35 | let cell = context.dequeueReusableCellFromStoryboard(withIdentifier: "ForecastDayCell", for: self, at: index) as? ForecastDayCell 36 | else { return UICollectionViewCell() } 37 | cell.configure(viewModel: viewModel) 38 | return cell 39 | } 40 | 41 | func didUpdate(to object: Any) { 42 | model = object as? DailyForecastSection 43 | } 44 | 45 | func didSelectItem(at index: Int) {} 46 | 47 | } 48 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/SectionControllers/ErrorSectionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorSectionController.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/4/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import IGListKit 11 | 12 | protocol ErrorSectionControllerDelegate: class { 13 | func errorSectionControllerDidTapRetry(errorSectionController: ErrorSectionController) 14 | } 15 | 16 | class ErrorSectionController: IGListSectionController, IGListSectionType, RetryCellDelegate { 17 | 18 | weak var delegate: ErrorSectionControllerDelegate? 19 | var model: ErrorViewModel? 20 | 21 | // MARK: IGListSectionType 22 | 23 | func numberOfItems() -> Int { 24 | return 1 25 | } 26 | 27 | func sizeForItem(at index: Int) -> CGSize { 28 | return collectionContext?.containerSize ?? CGSize() 29 | } 30 | 31 | func cellForItem(at index: Int) -> UICollectionViewCell { 32 | guard let cell = collectionContext?.dequeueReusableCellFromStoryboard(withIdentifier: "RetryCell", for: self, at: index) as? RetryCell 33 | else { return UICollectionViewCell() } 34 | cell.delegate = self 35 | cell.titleLabel.text = model?.title 36 | cell.messageLabel.text = model?.message 37 | return cell 38 | } 39 | 40 | func didUpdate(to object: Any) { 41 | model = object as? ErrorViewModel 42 | } 43 | 44 | func didSelectItem(at index: Int) {} 45 | 46 | // MARK: RetryCellDelegate 47 | 48 | func retryCellDidTapRetry(retryCell: RetryCell) { 49 | delegate?.errorSectionControllerDidTapRetry(errorSectionController: self) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/SectionControllers/HourlyForecastSectionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HourlyForecastSectionController.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/24/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import IGListKit 11 | 12 | class HourlyForecastSectionController: IGListSectionController, IGListSectionType { 13 | 14 | var viewModel: ForecastHourCellViewModel? 15 | 16 | override init() { 17 | super.init() 18 | inset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 15) 19 | } 20 | 21 | // MARK: IGListSectionType 22 | 23 | func numberOfItems() -> Int { 24 | return 1 25 | } 26 | 27 | func sizeForItem(at index: Int) -> CGSize { 28 | guard let height = collectionContext?.containerSize.height else { return .zero } 29 | return CGSize(width: height, height: height) 30 | } 31 | 32 | func cellForItem(at index: Int) -> UICollectionViewCell { 33 | guard let context = collectionContext, 34 | let viewModel = viewModel, 35 | let cell = context.dequeueReusableCellFromStoryboard(withIdentifier: "ForecastHourCell", for: self, at: index) as? ForecastHourCell 36 | else { return UICollectionViewCell() } 37 | cell.configure(viewModel: viewModel) 38 | return cell 39 | } 40 | 41 | func didUpdate(to object: Any) { 42 | viewModel = object as? ForecastHourCellViewModel 43 | } 44 | 45 | func didSelectItem(at index: Int) {} 46 | 47 | } 48 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/SectionControllers/SearchResultSectionController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultSectionController.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/8/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import IGListKit 11 | import MapKit 12 | 13 | func SearchResultSectionController() -> IGListSingleSectionController { 14 | let configureBlock = { (item: Any, cell: UICollectionViewCell) in 15 | guard let cell = cell as? SearchResultCell, 16 | let result = item as? MKLocalSearchCompletion else { return } 17 | cell.label.text = result.title 18 | 19 | cell.detailLabel.text = result.subtitle 20 | if result.subtitle.characters.count > 0 { 21 | cell.labelVerticalConstraint.constant = -6 22 | } else { 23 | cell.labelVerticalConstraint.constant = 0 24 | } 25 | } 26 | 27 | let sizeBlock = { (item: Any, context: IGListCollectionContext?) -> CGSize in 28 | guard let context = context else { return CGSize() } 29 | return CGSize(width: context.containerSize.width, height: 55) 30 | } 31 | return IGListSingleSectionController( 32 | storyboardCellIdentifier: "SearchResultCell", 33 | configureBlock: configureBlock, 34 | sizeBlock: sizeBlock 35 | ) 36 | 37 | } 38 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Systems/API.swift: -------------------------------------------------------------------------------- 1 | // 2 | // API.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/13/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let API_KEY = "36c847c1ef056205" 12 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Systems/Date+Daytime.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Date+Daytime.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/26/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum TimeOfDay { 12 | case day, night, unknown 13 | } 14 | 15 | extension Date { 16 | 17 | func timeOfDay(sunrise: Date? = nil, sunset: Date? = nil, calendar: Calendar = .current) -> TimeOfDay { 18 | let components: Set = [.day, .hour] 19 | 20 | let daytime: Bool 21 | let dateComponents = calendar.dateComponents(components, from: self) 22 | if let sunrise = sunrise, 23 | let sunset = sunset, 24 | let day = dateComponents.day, 25 | calendar.dateComponents(components, from: sunrise).day == day, 26 | calendar.dateComponents(components, from: sunset).day == day { 27 | daytime = self > sunrise && self < sunset 28 | } else if let hour = dateComponents.hour { 29 | daytime = hour > 6 && hour < 18 30 | } else { 31 | return .unknown 32 | } 33 | return daytime ? .day : .night 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Systems/KeypathParsing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeypathParsing.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/15/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | public func keypath(dict: [String: Any], path: String) -> T? { 12 | let keys: [String] = path.components(separatedBy: ".") 13 | var next = dict 14 | for key in keys { 15 | let entry = next[key] 16 | if let d = entry as? [String: Any] { 17 | next = d 18 | } else if let v = entry as? T, key == keys.last { 19 | return v 20 | } 21 | } 22 | return nil 23 | } 24 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Systems/MKCoordinateRegion+Bounds.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MKCoordinateRegion+Bounds.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/27/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MapKit 11 | 12 | extension MKCoordinateRegion { 13 | 14 | var bounds: CGRect { 15 | return CGRect( 16 | x: center.latitude - span.latitudeDelta / 2.0, 17 | y: center.longitude - span.longitudeDelta / 2.0, 18 | width: span.latitudeDelta, 19 | height: span.longitudeDelta 20 | ) 21 | } 22 | 23 | // http://stackoverflow.com/a/15683034/940936 24 | var mapRect: MKMapRect { 25 | let a = MKMapPointForCoordinate(CLLocationCoordinate2D(latitude: center.latitude + span.latitudeDelta / 2.0, 26 | longitude: center.longitude - span.longitudeDelta / 2.0)) 27 | let b = MKMapPointForCoordinate(CLLocationCoordinate2D(latitude: center.latitude - span.latitudeDelta / 2.0, 28 | longitude: center.longitude + span.longitudeDelta / 2.0)) 29 | return MKMapRectMake(min(a.x, b.x), min(a.y, b.y), abs(a.x - b.x), abs(a.y - b.y)) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Systems/MKCoordinateRegion+Convenience.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MKCoordinateRegion+Convenience.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/14/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import MapKit 10 | import UIKit 11 | 12 | extension MKCoordinateRegion { 13 | 14 | init(center: CLLocationCoordinate2D, width: CLLocationDegrees, size: CGSize) { 15 | let ratio = size.width / size.height 16 | let span = MKCoordinateSpan(latitudeDelta: width, longitudeDelta: Double(ratio) * width) 17 | self.init(center: center, span: span) 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Systems/URL+DiskCacheKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL+DiskCacheKey.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/19/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | 13 | var diskCacheKey: String { 14 | let str = absoluteString 15 | let length = Int(CC_MD5_DIGEST_LENGTH) 16 | var r = [UInt8](repeating: 0, count: length) 17 | 18 | if let d = str.data(using: String.Encoding.utf8) { 19 | _ = d.withUnsafeBytes { (body: UnsafePointer) in 20 | CC_MD5(body, CC_LONG(d.count), &r) 21 | } 22 | } 23 | 24 | let ext = pathExtension == "" ? "" : "." + pathExtension 25 | return String(format: "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%", 26 | r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], 27 | r[15]) + ext 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Systems/URLBuilder+Wunderground.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLBuilder+WeatherUnderground.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/2/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func WundergroundURL( 12 | apiKey: String, 13 | paths: [String]? = nil, 14 | query: [String: String]? = nil 15 | ) -> URL? { 16 | let apiPaths = ["api", apiKey] + (paths ?? []) 17 | return URLBuilder(base: "http://api.wunderground.com", paths: apiPaths, query: query) 18 | } 19 | 20 | func WundergroundForecastURL( 21 | apiKey: String, 22 | lat: Double, 23 | lon: Double 24 | ) -> URL? { 25 | let paths = [ 26 | // order doesn't matter 27 | "forecast", 28 | "geolookup", 29 | "conditions", 30 | "forecast10day", 31 | "alerts", 32 | "hourly", 33 | "astronomy", 34 | // order matters 35 | "q", 36 | String(format: "%.4f,%.4f.json", lat, lon)] // "40.71,-74.json" 37 | return WundergroundURL(apiKey: apiKey, paths: paths) 38 | } 39 | 40 | func WundergroundRadarURL( 41 | apiKey: String, 42 | minLat: Double, 43 | maxLat: Double, 44 | minLon: Double, 45 | maxLon: Double, 46 | width: Double, 47 | height: Double 48 | ) -> URL? { 49 | let paths = [ 50 | "radar", 51 | "image.png" 52 | ] 53 | let query = [ 54 | "maxlat": String(format: "%.4f", maxLat), 55 | "maxlon": String(format: "%.4f", maxLon), 56 | "minlat": String(format: "%.4f", minLat), 57 | "minlon": String(format: "%.4f", minLon), 58 | "width": String(format: "%.0f", width), 59 | "height": String(format: "%.0f", height), 60 | "rainsnow": "1", 61 | ] 62 | return WundergroundURL(apiKey: apiKey, paths: paths, query: query) 63 | } 64 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Systems/URLBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLBuilder.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/2/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func URLBuilder(base: String, paths: [String]? = nil, query: [String: String]? = nil) -> URL? { 12 | guard let baseURL = URL(string: base) else { preconditionFailure("Base not convertible to URL: " + base) } 13 | var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) 14 | components?.queryItems = query? 15 | .map { (k, v) in URLQueryItem(name: k, value: v) } 16 | 17 | if NSClassFromString("XCTestCase") != nil { 18 | components?.queryItems = components?.queryItems?.sorted { $0.name < $1.name } 19 | } 20 | 21 | components?.path = "/" + (paths?.joined(separator: "/") ?? "") 22 | return components?.url 23 | } 24 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Systems/URLSession+Convenience.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URLSession+Convenience.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/2/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension URLSession { 12 | 13 | func fetch( 14 | url: URL, 15 | request: URLSessionDataTaskResponse, 16 | completion: @escaping (URLSessionResult) -> Void 17 | ) -> URLSessionDataTask { 18 | print("Fetching parsable URL: \(url.absoluteString)") 19 | 20 | let task = dataTask(with: url, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) in 21 | let result = request.handle(data: data, error: error) 22 | DispatchQueue.main.async { 23 | completion(result) 24 | } 25 | }) 26 | task.resume() 27 | 28 | return task 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Systems/ViewPulser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewPulser.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/26/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ViewPulser { 12 | 13 | let view: UIView 14 | 15 | init(view: UIView) { 16 | self.view = view 17 | view.isHidden = true 18 | } 19 | 20 | var enabled = true 21 | 22 | func pulse() { 23 | guard enabled, !view.isHidden else { return } 24 | 25 | let pulseAnimation = CABasicAnimation(keyPath: "opacity") 26 | pulseAnimation.duration = 0.5 27 | pulseAnimation.fromValue = 0 28 | pulseAnimation.toValue = 1 29 | pulseAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) 30 | 31 | let group = CAAnimationGroup() 32 | group.duration = 2 33 | group.repeatCount = Float.greatestFiniteMagnitude 34 | group.animations = [pulseAnimation] 35 | 36 | view.layer.add(group, forKey: "animateOpacity") 37 | } 38 | 39 | func disable() { 40 | enabled = false 41 | view.isHidden = true 42 | view.layer.removeAllAnimations() 43 | } 44 | 45 | func enable() { 46 | enabled = true 47 | view.isHidden = false 48 | pulse() 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewControllers/AlertsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AlertsViewController.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/26/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class AlertsViewController: UIViewController { 12 | 13 | @IBOutlet weak var textView: UITextView! 14 | 15 | var alerts: [Alert]? 16 | 17 | override func viewDidLoad() { 18 | super.viewDidLoad() 19 | let descriptions: [String]? = alerts?.flatMap({ $0.message }) 20 | textView.text = descriptions?.joined(separator: "\n") 21 | textView.contentOffset = .zero 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewControllers/SavedLocationsViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SavedLocationsViewController.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 12/18/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import IGListKit 11 | 12 | class SavedLocationsViewController: UIViewController, IGListAdapterDataSource, SavedLocationStoreListener { 13 | 14 | @IBOutlet weak var collectionView: IGListCollectionView! 15 | 16 | lazy var adapter: IGListAdapter = { 17 | return IGListAdapter(updater: IGListAdapterUpdater(), viewController: self, workingRangeSize: 0) 18 | }() 19 | 20 | var store: SavedLocationStore? 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | adapter.collectionView = collectionView 25 | adapter.dataSource = self 26 | 27 | store?.add(listener: self) 28 | } 29 | 30 | override func prepare(for segue: UIStoryboardSegue, sender: Any?) { 31 | if let nav = segue.destination as? UINavigationController, 32 | let controller = nav.viewControllers.first as? SearchViewController { 33 | controller.store = store 34 | } 35 | } 36 | 37 | // MARK: IGListAdapterDataSource 38 | 39 | func objects(for listAdapter: IGListAdapter) -> [IGListDiffable] { 40 | return store?.locations ?? [] 41 | } 42 | 43 | func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { 44 | guard let store = store else { return IGListSectionController() } 45 | return SavedLocationSectionController(store: store) 46 | } 47 | 48 | func emptyView(for listAdapter: IGListAdapter) -> UIView? { 49 | return nil 50 | } 51 | 52 | // MARK: SavedLocationStoreListener 53 | 54 | func storeDidUpdate(store: SavedLocationStore) { 55 | adapter.performUpdates(animated: true) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/ConditionsCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConditionsCellViewModel.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/19/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ConditionsCellViewModel { 12 | 13 | let temperature: Int 14 | let high: Int 15 | let low: Int 16 | let conditionImageName: String 17 | let feelsLike: Int 18 | 19 | var temperatureLabelText: String { 20 | return String(format: "%zi°", temperature) 21 | } 22 | 23 | var highLowLabelText: NSAttributedString { 24 | return highLowAttributedString(high: high, low: low, size: 18) 25 | } 26 | 27 | var feelsLikeText: String? { 28 | return temperature != feelsLike ? String(format: "(%zi°)", feelsLike) : nil 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/ConditionsDetailCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConditionsDetailCellViewModel.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 12/11/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ConditionsDetailCellViewModel { 12 | 13 | let title: String 14 | let detail: String 15 | 16 | } 17 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/ErrorViewModel+IGListDiffable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorViewModel+IGListDiffable.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/4/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import IGListKit 10 | 11 | extension ErrorViewModel: IGListDiffable { 12 | 13 | func diffIdentifier() -> NSObjectProtocol { 14 | return title as NSString 15 | } 16 | 17 | func isEqual(toDiffableObject object: IGListDiffable?) -> Bool { 18 | if self === object { return true } 19 | guard let object = object as? ErrorViewModel else { return false } 20 | return type == object.type 21 | && title == object.title 22 | && message == object.message 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/ErrorViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ErrorViewModel.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/4/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ErrorViewModelType { 12 | case location 13 | case network 14 | } 15 | 16 | class ErrorViewModel { 17 | 18 | let title: String 19 | let message: String 20 | let type: ErrorViewModelType 21 | 22 | init(title: String, message: String, type: ErrorViewModelType) { 23 | self.title = title 24 | self.message = message 25 | self.type = type 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/ForecastDayCellViewModel+Equatable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastDayCellViewModel+Equatable.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/21/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension ForecastDayCellViewModel: Equatable { 12 | 13 | public static func ==(lhs: ForecastDayCellViewModel, rhs: ForecastDayCellViewModel) -> Bool { 14 | return lhs.date == rhs.date 15 | && lhs.high == rhs.high 16 | && lhs.low == rhs.low 17 | && lhs.conditionImageName == rhs.conditionImageName 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/ForecastDayCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastDayCellViewModel.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/20/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ForecastDayCellPosition { 12 | case top, bottom, none 13 | } 14 | 15 | struct ForecastDayCellViewModel { 16 | 17 | static private let dateFormatter: DateFormatter = { 18 | let df = DateFormatter() 19 | df.dateFormat = "EEEE" 20 | return df 21 | }() 22 | 23 | let date: Date 24 | let high: Int 25 | let low: Int 26 | let conditionImageName: String 27 | let chancePrecip: Double 28 | let position: ForecastDayCellPosition 29 | 30 | var dateString: String { 31 | return ForecastDayCellViewModel.dateFormatter.string(from: date) 32 | } 33 | 34 | var highLowConditionsString: NSAttributedString { 35 | return highLowAttributedString(high: high, low: low, size: 15) 36 | } 37 | 38 | var precipString: String? { 39 | return chancePrecip >= 0.2 ? String(format: "%.0f%%", chancePrecip * 100.0) : nil 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/ForecastHourCellViewModel+IGListDiffable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastHourCellViewModel+IGListDiffable.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/24/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IGListKit 11 | 12 | extension ForecastHourCellViewModel: IGListDiffable { 13 | 14 | func diffIdentifier() -> NSObjectProtocol { 15 | return date as NSDate 16 | } 17 | 18 | func isEqual(toDiffableObject object: IGListDiffable?) -> Bool { 19 | if self === object { return true } 20 | guard let object = object as? ForecastHourCellViewModel else { return false } 21 | return date == object.date && temp == object.temp && conditionImageName == object.conditionImageName 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/ForecastHourCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastHourCellViewModel.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/24/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class ForecastHourCellViewModel { 12 | 13 | static private let dateFormatter: DateFormatter = { 14 | let df = DateFormatter() 15 | df.dateFormat = "h a" 16 | return df 17 | }() 18 | 19 | let date: Date 20 | let temp: Int 21 | let conditionImageName: String 22 | let chancePrecip: Double 23 | 24 | init(date: Date, temp: Int, conditionImageName: String, chancePrecip: Double) { 25 | self.date = date 26 | self.temp = temp 27 | self.conditionImageName = conditionImageName 28 | self.chancePrecip = chancePrecip 29 | } 30 | 31 | var dateString: String { 32 | return ForecastHourCellViewModel.dateFormatter.string(from: date) 33 | } 34 | 35 | var detailsAttributedString: NSAttributedString { 36 | let fontSize: CGFloat = 15 37 | let mAttrString = NSMutableAttributedString( 38 | string: String(format: "%zi°", temp), 39 | attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: fontSize, weight: UIFontWeightMedium)] 40 | ) 41 | if chancePrecip >= 0.2 { 42 | let percipAttrStr = NSAttributedString( 43 | string: String(format: " %.0f%%", round(chancePrecip * 10.0) * 10.0), 44 | attributes: [ 45 | NSFontAttributeName: UIFont.systemFont(ofSize: fontSize, weight: UIFontWeightLight), 46 | NSForegroundColorAttributeName: UIColor(red: 73/255.0, green: 130/255.0, blue: 193/255.0, alpha: 1) 47 | ] 48 | ) 49 | mAttrString.append(percipAttrStr) 50 | } 51 | return mAttrString 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/ForecastHourlyDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastHourlyDataSource.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/24/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import IGListKit 11 | 12 | class ForecastHourlyDataSource: EmbeddedAdapterDataSource { 13 | 14 | func listAdapter(_ listAdapter: IGListAdapter, sectionControllerFor object: Any) -> IGListSectionController { 15 | if object is ForecastHourCellViewModel { 16 | return HourlyForecastSectionController() 17 | } 18 | return IGListSectionController() 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/RadarOverlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RadarOverlay.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/27/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MapKit 11 | 12 | class RadarOverlay: NSObject, MKOverlay { 13 | 14 | let coordinate: CLLocationCoordinate2D 15 | let boundingMapRect: MKMapRect 16 | 17 | init(coordinate: CLLocationCoordinate2D, boundingMapRect: MKMapRect) { 18 | self.coordinate = coordinate 19 | self.boundingMapRect = boundingMapRect 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/SavedLocationCellViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SavedLocationCellViewModel.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/14/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import MapKit 11 | 12 | struct SavedLocationCellViewModel { 13 | let text: String 14 | let userLocation: Bool 15 | let region: MKCoordinateRegion 16 | } 17 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/ViewModels/WeatherNavigationViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherNavigationViewModel.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/4/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func WeatherNavigationTitle(location: SavedLocation, forecast: Forecast?) -> String? { 12 | if location.userLocation == true { 13 | if let forecast = forecast { 14 | return forecast.location?.city 15 | } else { 16 | return NSLocalizedString("Loading...", comment: "") 17 | } 18 | } else { 19 | return location.name 20 | } 21 | } 22 | 23 | func WeatherNavigationShouldDisplayAlerts(forecast: Forecast?) -> Bool { 24 | return (forecast?.alerts?.count ?? 0) > 0 25 | } 26 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/BorderedButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BorderedButton.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/4/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class BorderedButton: UIButton { 12 | 13 | lazy var shapeLayer: CAShapeLayer = { 14 | let l = CAShapeLayer() 15 | l.lineWidth = 1 16 | l.fillColor = nil 17 | self.layer.addSublayer(l) 18 | return l 19 | }() 20 | 21 | override func layoutSubviews() { 22 | super.layoutSubviews() 23 | shapeLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: 6).cgPath 24 | shapeLayer.strokeColor = tintColor.cgColor 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/ConditionsCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConditionsCell.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/19/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ConditionsCell: RoundedCollectionViewCell { 12 | 13 | @IBOutlet weak private var temperatureLabel: UILabel! 14 | @IBOutlet weak private var highLowLabel: UILabel! 15 | @IBOutlet weak var feelsLikeLabel: UILabel! 16 | @IBOutlet weak var iconImageView: UIImageView! 17 | @IBOutlet weak var arrowImageView: UIImageView! 18 | 19 | func configure(viewModel: ConditionsCellViewModel) { 20 | cornerOptions = .all 21 | temperatureLabel.text = viewModel.temperatureLabelText 22 | highLowLabel.attributedText = viewModel.highLowLabelText 23 | iconImageView.image = UIImage(named: viewModel.conditionImageName) 24 | feelsLikeLabel.text = viewModel.feelsLikeText 25 | } 26 | 27 | func setExpanded(expanded: Bool, animated: Bool = false) { 28 | cornerOptions = expanded ? .top : .all 29 | updateMaskIfNeeded() 30 | 31 | let color: UIColor 32 | if expanded { 33 | color = UIColor(red: 53/255.0, green: 55/255.0, blue: 63/255.0, alpha: 1) 34 | } else { 35 | color = UIColor(red: 30/255.0, green: 32/255.0, blue: 41/255.0, alpha: 1) 36 | } 37 | backgroundColor = color 38 | 39 | UIView.animate( 40 | withDuration: (animated ? 0.8 : 0), 41 | delay: 0, 42 | usingSpringWithDamping: 0.6, 43 | initialSpringVelocity: 0, 44 | options: [], 45 | animations: { 46 | self.arrowImageView.transform = expanded ? CGAffineTransform(rotationAngle: CGFloat.pi) : .identity 47 | }) 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/ConditionsDetailCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ConditionsDetailCell.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 12/11/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ConditionsDetailCell: RoundedCollectionViewCell { 12 | 13 | @IBOutlet weak var detailLabel: UILabel! 14 | @IBOutlet weak var titleLabel: UILabel! 15 | 16 | func configure(viewModel: ConditionsDetailCellViewModel) { 17 | titleLabel.text = viewModel.title 18 | detailLabel.text = viewModel.detail 19 | } 20 | 21 | func setIsLast(isLast: Bool) { 22 | cornerOptions = isLast ? .bottom : [] 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/EmbeddedCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EmbeddedCollectionViewCell.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/24/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import IGListKit 11 | 12 | class EmbeddedCollectionViewCell: UICollectionViewCell { 13 | 14 | @IBOutlet weak var collectionView: IGListCollectionView! { 15 | didSet { 16 | collectionView.bounces = true 17 | collectionView.alwaysBounceVertical = false 18 | collectionView.alwaysBounceHorizontal = true 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/ForecastDayCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastDayCell.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/20/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ForecastDayCell: RoundedCollectionViewCell { 12 | 13 | @IBOutlet weak var dayLabel: UILabel! 14 | @IBOutlet weak var highLowConditionsLabel: UILabel! 15 | @IBOutlet weak var iconImageView: UIImageView! 16 | @IBOutlet weak var precipLabel: UILabel! 17 | 18 | func configure(viewModel: ForecastDayCellViewModel) { 19 | dayLabel.text = viewModel.dateString 20 | highLowConditionsLabel.attributedText = viewModel.highLowConditionsString 21 | iconImageView.image = UIImage(named: viewModel.conditionImageName) 22 | 23 | switch viewModel.position { 24 | case .top: cornerOptions = .top 25 | case .bottom: cornerOptions = .bottom 26 | case .none: cornerOptions = [] 27 | } 28 | 29 | if let precip = viewModel.precipString { 30 | precipLabel.isHidden = false 31 | precipLabel.text = precip 32 | } else { 33 | precipLabel.isHidden = true 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/ForecastHourCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ForecastHourCell.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/24/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ForecastHourCell: RoundedCollectionViewCell { 12 | 13 | @IBOutlet weak var timeLabel: UILabel! 14 | @IBOutlet weak var tempLabel: UILabel! 15 | @IBOutlet weak var iconImageView: UIImageView! 16 | 17 | func configure(viewModel: ForecastHourCellViewModel) { 18 | cornerOptions = .all 19 | timeLabel.text = viewModel.dateString 20 | tempLabel.attributedText = viewModel.detailsAttributedString 21 | iconImageView.image = UIImage(named: viewModel.conditionImageName) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/HighLowAttributedString.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HighLowAttributedString.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/27/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func highLowAttributedString(high: Int, low: Int, size: CGFloat = 15) -> NSAttributedString { 12 | let font = UIFont.systemFont(ofSize: size, weight: UIFontWeightMedium) 13 | let mstr = NSMutableAttributedString( 14 | string: String(format: "%zi°", high), 15 | attributes: [ 16 | NSFontAttributeName: font, 17 | NSForegroundColorAttributeName: UIColor.white 18 | ]) 19 | mstr.append(NSAttributedString( 20 | string: String(format: " %zi°", low), 21 | attributes: [ 22 | NSFontAttributeName: font, 23 | NSForegroundColorAttributeName: UIColor(white: 0.733333333, alpha: 1) 24 | ])) 25 | return mstr 26 | } 27 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/LoadingView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LoadingView.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/4/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class LoadingView: UIView { 12 | 13 | let spinner = UIActivityIndicatorView(activityIndicatorStyle: .white) 14 | 15 | override init(frame: CGRect) { 16 | super.init(frame: frame) 17 | spinner.startAnimating() 18 | addSubview(spinner) 19 | } 20 | 21 | required init?(coder aDecoder: NSCoder) { 22 | fatalError("init(coder:) has not been implemented") 23 | } 24 | 25 | override func layoutSubviews() { 26 | super.layoutSubviews() 27 | spinner.center = CGPoint(x: bounds.midX, y: bounds.midY) 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/MapCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MapCell.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/27/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MapKit 11 | 12 | class MapCell: UICollectionViewCell { 13 | 14 | @IBOutlet weak var mapView: MKMapView! 15 | 16 | } 17 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/RadarOverlayView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RadarOverlayView.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/27/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MapKit 11 | 12 | class RadarOverlayView: MKOverlayRenderer { 13 | 14 | let image: UIImage 15 | 16 | init(overlay: MKOverlay, image: UIImage) { 17 | self.image = image 18 | super.init(overlay: overlay) 19 | } 20 | 21 | override func draw(_ mapRect: MKMapRect, zoomScale: MKZoomScale, in context: CGContext) { 22 | // https://www.raywenderlich.com/87008/overlay-views-mapkit-swift-tutorial 23 | guard let imageRef = image.cgImage else { return } 24 | let theMapRect = overlay.boundingMapRect 25 | let theRect = rect(for: theMapRect) 26 | context.scaleBy(x: 1, y: -1) 27 | context.translateBy(x: 0.0, y: -theRect.size.height) 28 | context.setAlpha(0.5) 29 | context.draw(imageRef, in: theRect) 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/RetryCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RetryCell.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/4/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol RetryCellDelegate: class { 12 | func retryCellDidTapRetry(retryCell: RetryCell) 13 | } 14 | 15 | class RetryCell: UICollectionViewCell { 16 | 17 | @IBOutlet weak var titleLabel: UILabel! 18 | @IBOutlet weak var messageLabel: UILabel! 19 | @IBOutlet weak var retryButton: BorderedButton! 20 | 21 | weak var delegate: RetryCellDelegate? 22 | 23 | @IBAction func onRetry(_ sender: Any) { 24 | delegate?.retryCellDidTapRetry(retryCell: self) 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/SavedLocationContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SavedLocationContentView.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/14/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import MapKit 11 | 12 | class SavedLocationContentView: UIView { 13 | 14 | let mapView: MKMapView = { 15 | let view = MKMapView() 16 | view.showsScale = false 17 | view.showsCompass = false 18 | view.showsTraffic = false 19 | view.showsBuildings = false 20 | view.showsUserLocation = false 21 | view.showsPointsOfInterest = false 22 | view.isUserInteractionEnabled = false 23 | return view 24 | }() 25 | 26 | let label: UILabel = { 27 | let view = UILabel() 28 | view.font = UIFont.boldSystemFont(ofSize: 15) 29 | view.textColor = .white 30 | view.backgroundColor = .clear 31 | return view 32 | }() 33 | 34 | let disclosure = UIImageView(image: #imageLiteral(resourceName: "Disclosure")) 35 | 36 | override init(frame: CGRect) { 37 | super.init(frame: frame) 38 | layer.cornerRadius = 8 39 | clipsToBounds = true 40 | addSubview(mapView) 41 | addSubview(label) 42 | addSubview(disclosure) 43 | backgroundColor = UIColor(red: 30/255.0, green: 32/255.0, blue: 41/255.0, alpha: 1) 44 | } 45 | 46 | required init?(coder aDecoder: NSCoder) { 47 | fatalError("init(coder:) has not been implemented") 48 | } 49 | 50 | override func layoutSubviews() { 51 | super.layoutSubviews() 52 | let divide = bounds.divided(atDistance: 40, from: .minYEdge) 53 | label.frame = divide.slice.insetBy(dx: 15, dy: 0) 54 | mapView.frame = divide.remainder 55 | disclosure.center = CGPoint(x: bounds.width - disclosure.bounds.width / 2 - 15, y: label.center.y) 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/SavedLocationDeleteView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SavedLocationDeleteView.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/15/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SavedLocationDeleteView: UIView { 12 | 13 | let label: UILabel = { 14 | let view = UILabel() 15 | view.backgroundColor = .clear 16 | view.text = NSLocalizedString("Delete", comment: "") 17 | view.textAlignment = .center 18 | view.textColor = .white 19 | view.font = UIFont.systemFont(ofSize: 13) 20 | view.sizeToFit() 21 | return view 22 | }() 23 | 24 | let imageView = UIImageView(image: #imageLiteral(resourceName: "Delete")) 25 | 26 | override init(frame: CGRect) { 27 | super.init(frame: frame) 28 | addSubview(label) 29 | addSubview(imageView) 30 | } 31 | 32 | required init?(coder aDecoder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | 36 | override func layoutSubviews() { 37 | super.layoutSubviews() 38 | let height = imageView.bounds.height + 8 + label.bounds.height 39 | imageView.center = CGPoint(x: bounds.midX, y: (bounds.height - height + imageView.bounds.height) / 2) 40 | label.center = CGPoint(x: bounds.midX, y: (bounds.height + height - label.bounds.height) / 2) 41 | } 42 | 43 | override func touchesBegan(_ touches: Set, with event: UIEvent?) { 44 | super.touchesBegan(touches, with: event) 45 | alpha = 0.5 46 | } 47 | 48 | override func touchesEnded(_ touches: Set, with event: UIEvent?) { 49 | super.touchesEnded(touches, with: event) 50 | alpha = 1 51 | } 52 | 53 | override func touchesMoved(_ touches: Set, with event: UIEvent?) { 54 | super.touchesMoved(touches, with: event) 55 | alpha = 1 56 | } 57 | 58 | override func touchesCancelled(_ touches: Set, with event: UIEvent?) { 59 | super.touchesCancelled(touches, with: event) 60 | alpha = 1 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /SimpleWeather/Classes/Views/SearchResultCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SearchResultCell.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 1/8/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class SearchResultCell: UICollectionViewCell { 12 | 13 | @IBOutlet weak var label: UILabel! 14 | @IBOutlet weak var detailLabel: UILabel! 15 | 16 | @IBOutlet weak var labelVerticalConstraint: NSLayoutConstraint! 17 | 18 | } 19 | -------------------------------------------------------------------------------- /SimpleWeather/Resources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/.DS_Store -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x-1.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x-1.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x-2.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x-1.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@1x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Arrow.imageset/Arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Arrow.imageset/Arrow.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Arrow.imageset/Arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Arrow.imageset/Arrow@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Arrow.imageset/Arrow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Arrow.imageset/Arrow@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Arrow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Arrow.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Arrow@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Arrow@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Cloudy.imageset/Cloudy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Cloudy.imageset/Cloudy.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Cloudy.imageset/Cloudy@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Cloudy.imageset/Cloudy@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Cloudy.imageset/Cloudy@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Cloudy.imageset/Cloudy@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Cloudy.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Cloudy.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Cloudy@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Cloudy@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Delete.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Delete.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Delete@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Delete@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Delete.imageset/Delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Delete.imageset/Delete.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Delete.imageset/Delete@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Delete.imageset/Delete@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Delete.imageset/Delete@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Delete.imageset/Delete@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Disclosure.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Disclosure.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Disclosure@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Disclosure@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Disclosure.imageset/Disclosure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Disclosure.imageset/Disclosure.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Disclosure.imageset/Disclosure@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Disclosure.imageset/Disclosure@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Disclosure.imageset/Disclosure@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Disclosure.imageset/Disclosure@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Haze.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Haze.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Haze@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Haze@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Haze.imageset/Haze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Haze.imageset/Haze.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Haze.imageset/Haze@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Haze.imageset/Haze@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Haze.imageset/Haze@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Haze.imageset/Haze@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mist.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Mist.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Mist@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Mist@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mist.imageset/Mist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Mist.imageset/Mist.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mist.imageset/Mist@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Mist.imageset/Mist@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mist.imageset/Mist@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Mist.imageset/Mist@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Moon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Moon.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Moon@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Moon@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Moon.imageset/Moon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Moon.imageset/Moon.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Moon.imageset/Moon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Moon.imageset/Moon@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Moon.imageset/Moon@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Moon.imageset/Moon@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-day.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Mostly-sunny-day.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Mostly-sunny-day@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Mostly-sunny-day@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-day.imageset/Mostly-sunny-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-day.imageset/Mostly-sunny-day.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-day.imageset/Mostly-sunny-day@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-day.imageset/Mostly-sunny-day@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-day.imageset/Mostly-sunny-day@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-day.imageset/Mostly-sunny-day@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-night.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Mostly-sunny-night.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Mostly-sunny-night@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Mostly-sunny-night@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-night.imageset/Mostly-sunny-night.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-night.imageset/Mostly-sunny-night.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-night.imageset/Mostly-sunny-night@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-night.imageset/Mostly-sunny-night@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-night.imageset/Mostly-sunny-night@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Mostly-sunny-night.imageset/Mostly-sunny-night@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Partly-sunny-day.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Partly-sunny-day.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Partly-sunny-day@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Partly-sunny-day@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Partly-sunny-day.imageset/Partly-sunny-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Partly-sunny-day.imageset/Partly-sunny-day.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Partly-sunny-day.imageset/Partly-sunny-day@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Partly-sunny-day.imageset/Partly-sunny-day@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Partly-sunny-day.imageset/Partly-sunny-day@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Partly-sunny-day.imageset/Partly-sunny-day@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Snow.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Snow.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Snow@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Snow@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Snow.imageset/Snow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Snow.imageset/Snow.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Snow.imageset/Snow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Snow.imageset/Snow@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Snow.imageset/Snow@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Snow.imageset/Snow@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Sun.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Sun.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Sun@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Sun@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Sun.imageset/Sun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Sun.imageset/Sun.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Sun.imageset/Sun@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Sun.imageset/Sun@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Sun.imageset/Sun@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Sun.imageset/Sun@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Thunder-cloud.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Thunder-cloud.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Thunder-cloud@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Thunder-cloud@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Thunder-cloud.imageset/Thunder-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Thunder-cloud.imageset/Thunder-cloud.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Thunder-cloud.imageset/Thunder-cloud@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Thunder-cloud.imageset/Thunder-cloud@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Thunder-cloud.imageset/Thunder-cloud@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Thunder-cloud.imageset/Thunder-cloud@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Wind.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Wind.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Wind@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "Wind@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Wind.imageset/Wind.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Wind.imageset/Wind.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Wind.imageset/Wind@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Wind.imageset/Wind@2x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Assets.xcassets/Wind.imageset/Wind@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnystrom/SimpleWeather/2138788038358ffabb01fd6bda6a0b1277cedcc1/SimpleWeather/Resources/Assets.xcassets/Wind.imageset/Wind@3x.png -------------------------------------------------------------------------------- /SimpleWeather/Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | Weather+ 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | NSAppTransportSecurity 26 | 27 | NSAllowsArbitraryLoads 28 | 29 | 30 | NSLocationUsageDescription 31 | This helps us find the weather at your location. 32 | NSLocationWhenInUseUsageDescription 33 | This helps us find the weather at your location. 34 | UILaunchStoryboardName 35 | LaunchScreen 36 | UIMainStoryboardFile 37 | Main 38 | UIRequiredDeviceCapabilities 39 | 40 | armv7 41 | 42 | UIStatusBarStyle 43 | UIStatusBarStyleLightContent 44 | UISupportedInterfaceOrientations 45 | 46 | UIInterfaceOrientationPortrait 47 | 48 | UISupportedInterfaceOrientations~ipad 49 | 50 | UIInterfaceOrientationPortrait 51 | UIInterfaceOrientationPortraitUpsideDown 52 | UIInterfaceOrientationLandscapeLeft 53 | UIInterfaceOrientationLandscapeRight 54 | 55 | UIViewControllerBasedStatusBarAppearance 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /SimpleWeather/SimpleWeather-Briding-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleWeather-Briding-Header.h.h 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/19/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | -------------------------------------------------------------------------------- /SimpleWeatherTests/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SimpleWeatherTests/KeypathParsingTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // KeypathParsingTests.swift 3 | // SimpleWeather 4 | // 5 | // Created by Ryan Nystrom on 11/15/16. 6 | // Copyright © 2016 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class KeypathParsingTests: XCTestCase { 12 | 13 | func test_whenKeypathExists_withProperValueCast_thatValueReturned() { 14 | let dict = [ "a": [ "b" : "1" ] ] 15 | let result: String? = keypath(dict: dict, path: "a.b") 16 | XCTAssertEqual(result, "1") 17 | } 18 | 19 | func test_whenKeypathExists_withIncorrectValueCast_thatValueReturned() { 20 | let dict = [ "a": [ "b" : "1" ] ] 21 | let result: Int? = keypath(dict: dict, path: "a.b") 22 | XCTAssertNil(result) 23 | } 24 | 25 | func test_whenKeypathNotDeepEnough_thatNilReturned() { 26 | let dict = [ "a": [ "b" : "1" ] ] 27 | let result: String? = keypath(dict: dict, path: "a") 28 | XCTAssertNil(result) 29 | } 30 | 31 | func test_whenKeypathDoesNotExist_withTooDeep_thatNilReturned() { 32 | let dict = [ "a": [ "b" : "1" ] ] 33 | let result: String? = keypath(dict: dict, path: "a.b.c") 34 | XCTAssertNil(result) 35 | } 36 | 37 | func test_whenKeypathDoesNotExist_withCorrectDepth_thatValueReturned() { 38 | let dict = [ "a": [ "b" : "1" ] ] 39 | let result: String? = keypath(dict: dict, path: "a.c") 40 | XCTAssertNil(result) 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /SimpleWeatherUITests/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 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /SimpleWeatherUITests/SimpleWeatherUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SimpleWeatherUITests.swift 3 | // SimpleWeatherUITests 4 | // 5 | // Created by Ryan Nystrom on 1/15/17. 6 | // Copyright © 2017 Ryan Nystrom. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | 11 | class SimpleWeatherUITests: XCTestCase { 12 | 13 | override func setUp() { 14 | super.setUp() 15 | 16 | continueAfterFailure = false 17 | 18 | let app = XCUIApplication() 19 | setupSnapshot(app) 20 | app.launch() 21 | } 22 | 23 | override func tearDown() { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | super.tearDown() 26 | } 27 | 28 | func test_weather_screen() { 29 | XCUIApplication().collectionViews.scrollViews.otherElements.containing(.staticText, identifier:"New York").children(matching: .other).element.children(matching: .other).element.children(matching: .map).element.tap() 30 | sleep(2) 31 | snapshot("01WeatherScreen") 32 | } 33 | 34 | func test_locations_screen() { 35 | sleep(2) 36 | snapshot("02LocationsScreen") 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | # The Appfile can be used to specify information that's used across all fastlane 2 | # tools, like your username or the app's bundle identifier. 3 | # 4 | # For more details, check out the documentation at: 5 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Appfile.md 6 | 7 | app_identifier "com.whoisryannystrom.weather" # the bundle identifier of your app 8 | apple_id "rnystrom@whoisryannystrom.com" # Your Apple ID 9 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # More documentation about how to customize your build 2 | # can be found here: 3 | # https://docs.fastlane.tools 4 | fastlane_version "1.109.0" 5 | 6 | # This value helps us track success metrics for Fastfiles 7 | # we automatically generate. Feel free to remove this line 8 | # once you get things running smoothly! 9 | generated_fastfile_id "959e5fcd-45e0-4847-b130-f09163fab837" 10 | 11 | default_platform :ios 12 | 13 | # Fastfile actions accept additional configuration, but 14 | # don't worry, fastlane will prompt you for required 15 | # info which you can add here later 16 | lane :beta do 17 | cert 18 | sigh 19 | gym(scheme: "SimpleWeather", 20 | workspace: "SimpleWeather.xcworkspace") 21 | pilot(skip_waiting_for_build_processing: true) 22 | end 23 | 24 | lane :appstore do 25 | snapshot 26 | match(type: "appstore") 27 | gym(scheme: "SimpleWeather", 28 | workspace: "SimpleWeather.xcworkspace") 29 | appstore 30 | end 31 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ================ 3 | # Installation 4 | ``` 5 | sudo gem install fastlane 6 | ``` 7 | # Available Actions 8 | ### beta 9 | ``` 10 | fastlane beta 11 | ``` 12 | 13 | 14 | ---- 15 | 16 | This README.md is auto-generated and will be re-generated every time [fastlane](https://fastlane.tools) is run. 17 | More information about fastlane can be found on [https://fastlane.tools](https://fastlane.tools). 18 | The documentation of fastlane can be found on [GitHub](https://github.com/fastlane/fastlane/tree/master/fastlane). 19 | -------------------------------------------------------------------------------- /fastlane/Snapfile: -------------------------------------------------------------------------------- 1 | # Uncomment the lines below you want to change by removing the # in the beginning 2 | 3 | # A list of devices you want to take the screenshots from 4 | devices([ 5 | "iPhone 6", 6 | "iPhone 6 Plus", 7 | "iPhone 5" 8 | ]) 9 | 10 | languages([ 11 | "en-US" 12 | ]) 13 | 14 | # The name of the scheme which contains the UI Tests 15 | scheme "SimpleWeather" 16 | 17 | # Where should the resulting screenshots be stored? 18 | # output_directory "./screenshots" 19 | 20 | # clear_previous_screenshots true # remove the '#' to clear all previously generated screenshots before creating new ones 21 | 22 | # Choose which project/workspace to use 23 | # project "./Project.xcodeproj" 24 | workspace "./SimpleWeather.xcworkspace" 25 | 26 | # Arguments to pass to the app on launch. See https://github.com/fastlane/snapshot#launch-arguments 27 | # launch_arguments(["-favColor red"]) 28 | 29 | # For more information about all available options run 30 | # fastlane snapshot --help 31 | -------------------------------------------------------------------------------- /report.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | --------------------------------------------------------------------------------