├── ViperWeather ├── Assets.xcassets │ ├── Contents.json │ └── AppIcon.appiconset │ │ ├── Icon-76.png │ │ ├── Icon-60@2x.png │ │ ├── Icon-60@3x.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-83.5@2x.png │ │ ├── Icon-Small-40.png │ │ ├── Icon-Small@2x-1.png │ │ ├── Icon-Small@2x.png │ │ ├── Icon-Small@3x.png │ │ ├── Icon-Small-40@2x.png │ │ ├── Icon-Small-40@3x.png │ │ ├── Icon-Small-40@2x-1.png │ │ └── Contents.json ├── ViperWeather-Bridging-Header.h ├── Constants.swift ├── UITextField+SwiftAppearance.h ├── Weather.swift ├── UITextField+SwiftAppearance.m ├── ServiceLocatorAssembler.swift ├── DetailRouter.swift ├── RootInteractor.swift ├── ServiceLocatorContainer.swift ├── RouterHelper.swift ├── ChildRouter.swift ├── ChildDataManager.swift ├── ChildInteractor.swift ├── City.swift ├── ListInteractor.swift ├── DetailListDetailDataManager.swift ├── DetailContainerDataManager.swift ├── DetailListDetailInteractor.swift ├── DetailContainerInteractor.swift ├── NavigationController.swift ├── ChildViewController.swift ├── RootRouter.swift ├── AddRouter.swift ├── RootAssembler.swift ├── ChildPresenter.swift ├── Root.storyboard ├── DetailContainerViewController.swift ├── DetailListDetailRouter.swift ├── AddInteractor.swift ├── CityTableViewCell.swift ├── ChildAssembler.swift ├── RootPresenter.swift ├── DetailListRouter.swift ├── Base.lproj │ ├── Main.storyboard │ └── LaunchScreen.storyboard ├── DetailListDetailAssembler.swift ├── WeatherTableViewCell.swift ├── DetailInteractor.swift ├── DetailListDetailPresenter.swift ├── DetailListDetailViewController.swift ├── RootViewController.swift ├── DetailListInteractor.swift ├── ListRouter.swift ├── AddAssembler.swift ├── ListPresenter.swift ├── ListDataManager.swift ├── RootContainer.swift ├── DetailContainerRouter.swift ├── DetailContainer.swift ├── DetailContainerAssembler.swift ├── DetailContainerPresenter.swift ├── DetailPresenter.swift ├── ListAssembler.swift ├── ChildContainer.swift ├── AddContainer.swift ├── ListTableViewController.xib ├── AddTableViewController.xib ├── DetailListViewController.xib ├── DetailListPresenter.swift ├── DetailAssembler.swift ├── DetailListDetailContainer.swift ├── DetailListContainer.swift ├── ListContainer.swift ├── AddDataManager.swift ├── AddPresenter.swift ├── DetailListAssembler.swift ├── DetailContainerContainer.swift ├── Info.plist ├── List.storyboard ├── RootViewController.xib ├── MaterialColor.swift ├── DetailViewController.swift ├── DetailListViewController.swift ├── DetailDataManager.swift ├── AppDelegate.swift ├── DetailListDataManager.swift ├── AddTableViewController.swift ├── WeatherTableViewCell.xib ├── DetailViewController.xib ├── Detail.storyboard ├── CityTableViewCell.xib └── ListTableViewController.swift ├── ViperWeather.xcodeproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── ViperWeather.xcworkspace └── contents.xcworkspacedata ├── Podfile ├── Podfile.lock ├── README.md ├── .gitignore └── LICENSE /ViperWeather/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-76.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-60@3x.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-83.5@2x.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small-40.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x-1.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small@3x.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@3x.png -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CoolCodeFactory/ViperWeather/HEAD/ViperWeather/Assets.xcassets/AppIcon.appiconset/Icon-Small-40@2x-1.png -------------------------------------------------------------------------------- /ViperWeather/ViperWeather-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | // 2 | // Use this file to import your target's public headers that you would like to expose to Swift. 3 | // 4 | 5 | #import "UITextField+SwiftAppearance.h" 6 | -------------------------------------------------------------------------------- /ViperWeather.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ViperWeather.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ViperWeather/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 02/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | let googleMapKey = <#googleMapKey#> 12 | let openWeatherMapKey = <#openWeatherMapKey#> 13 | -------------------------------------------------------------------------------- /ViperWeather/UITextField+SwiftAppearance.h: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField+SwiftAppearance.h 3 | // ViperWeather 4 | // 5 | // Created by Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UITextField (SwiftAppearance) 12 | 13 | + (instancetype)appearanceWhenContainedWithin:(Class)containerClass; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ViperWeather/Weather.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Weather.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | 12 | struct Weather { 13 | let dt: Double 14 | let temp: Double 15 | let pressure: Double 16 | let icon: String 17 | 18 | var tempString: String { 19 | return String(temp) + "℃" 20 | } 21 | } -------------------------------------------------------------------------------- /ViperWeather/UITextField+SwiftAppearance.m: -------------------------------------------------------------------------------- 1 | // 2 | // UITextField+SwiftAppearance.m 3 | // ViperWeather 4 | // 5 | // Created by Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | #import "UITextField+SwiftAppearance.h" 10 | 11 | @implementation UITextField (SwiftAppearance) 12 | 13 | + (instancetype)appearanceWhenContainedWithin:(Class)containerClass { 14 | if ([self conformsToProtocol:@protocol(UIAppearance)]) { 15 | return [self appearanceWhenContainedIn:containerClass, nil]; 16 | } 17 | return nil; 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ViperWeather/ServiceLocatorAssembler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceLocatorAssembler.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 24/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import Foundation 12 | import Swinject 13 | 14 | class ServiceLocatorAssembler: Assembler { 15 | 16 | init() { 17 | let container = Container(parent: nil) 18 | super.init(container: container) 19 | 20 | self.applyAssembly(ServiceLocatorContainer()) 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /ViperWeather/DetailRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailRouter.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Swinject 11 | 12 | 13 | protocol DetailRouterInputProtocol: class { 14 | func dismissDetailViewController(viewController viewController: UIViewController) 15 | } 16 | 17 | protocol DetailParentRouterProtocol: class { 18 | 19 | } 20 | 21 | class DetailRouter: DetailRouterInputProtocol { 22 | 23 | func dismissDetailViewController(viewController viewController: UIViewController) { 24 | viewController.dismissViewControllerAnimated(true, completion: nil) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '8.0' 3 | # Uncomment this line if you're using Swift 4 | use_frameworks! 5 | 6 | target 'ViperWeather' do 7 | 8 | 9 | pod 'Swinject', '~> 1.1.0' 10 | pod 'Alamofire', '~> 3.5.0' 11 | 12 | pod 'RealmSwift', '~> 0.98.5' 13 | pod 'SwiftFetchedResultsController', '~> 3.0.1' 14 | 15 | pod 'SwiftyJSON', '~> 2.3.2' 16 | 17 | # Will used soon 18 | pod 'SnapKit', '~> 0.22.0' 19 | 20 | end 21 | 22 | # Use Legacy Swift Language Version 2.3 23 | post_install do |installer| 24 | installer.pods_project.targets.each do |target| 25 | target.build_configurations.each do |configuration| 26 | configuration.build_settings['SWIFT_VERSION'] = "2.3" 27 | end 28 | end 29 | end -------------------------------------------------------------------------------- /ViperWeather/RootInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootInteractor.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 20/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import Foundation 12 | 13 | protocol RootInteractorInputProtocol: class { 14 | 15 | weak var presenter: RootInteractorOutputProtocol! { get set } 16 | 17 | } 18 | 19 | protocol RootInteractorOutputProtocol: class { 20 | 21 | } 22 | 23 | class RootInteractor { 24 | 25 | weak var presenter: RootInteractorOutputProtocol! 26 | 27 | } 28 | 29 | extension RootInteractor: RootInteractorInputProtocol { 30 | 31 | } 32 | -------------------------------------------------------------------------------- /ViperWeather/ServiceLocatorContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ServiceLocatorContainer.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 24/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import Foundation 12 | import Swinject 13 | 14 | class ServiceLocatorContainer: AssemblyType { 15 | 16 | func assemble(container: Container) { 17 | container.register(RootAssembler.self) { r in 18 | let delegete = (UIApplication.sharedApplication().delegate as! AppDelegate) 19 | return RootAssembler(parentAssembler: delegete.serviceLocatorAssembler) 20 | } 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /ViperWeather/RouterHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootHelper.swift 3 | // Architecture 4 | // 5 | // Created by Dmitriy Utmanov on 26/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | class RouterHelper { 13 | 14 | class func navigationController() -> UINavigationController { 15 | let delegate = UIApplication.sharedApplication().delegate as! AppDelegate 16 | let rootNVC = delegate.window!.rootViewController as! UINavigationController 17 | return rootNVC 18 | } 19 | 20 | class func setRootViewController(viewController: UIViewController, animated: Bool) { 21 | self.navigationController().setViewControllers([viewController], animated: animated) 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /ViperWeather/ChildRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChildRouter.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | protocol ChildRouterInputProtocol: class { 16 | func dismissChildViewController(viewController viewController: UIViewController) 17 | } 18 | 19 | protocol ChildParentRouterProtocol: class { 20 | 21 | } 22 | 23 | class ChildRouter: ChildRouterInputProtocol { 24 | 25 | func dismissChildViewController(viewController viewController: UIViewController) { 26 | viewController.dismissViewControllerAnimated(true, completion: nil) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /ViperWeather/ChildDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChildDataManager.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import Foundation 12 | import RealmSwift 13 | import Alamofire 14 | 15 | 16 | protocol ChildDataManagerInputProtocol: class { 17 | 18 | weak var interactor: ChildDataManagerOutputProtocol! { get set } 19 | } 20 | 21 | protocol ChildDataManagerOutputProtocol: class { 22 | 23 | var dataManager: ChildDataManagerInputProtocol! { get set } 24 | } 25 | 26 | 27 | class ChildDataManager { 28 | 29 | weak var interactor: ChildDataManagerOutputProtocol! 30 | } 31 | 32 | extension ChildDataManager: ChildDataManagerInputProtocol { 33 | 34 | } 35 | -------------------------------------------------------------------------------- /ViperWeather/ChildInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChildInteractor.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import Foundation 12 | 13 | 14 | protocol ChildInteractorInputProtocol: class { 15 | 16 | weak var presenter: ChildInteractorOutputProtocol! { get set } 17 | } 18 | 19 | protocol ChildInteractorOutputProtocol: class { 20 | 21 | } 22 | 23 | class ChildInteractor { 24 | 25 | weak var presenter: ChildInteractorOutputProtocol! 26 | 27 | var dataManager: ChildDataManagerInputProtocol! 28 | } 29 | 30 | extension ChildInteractor: ChildInteractorInputProtocol { 31 | 32 | } 33 | 34 | extension ChildInteractor: ChildDataManagerOutputProtocol { 35 | 36 | } -------------------------------------------------------------------------------- /ViperWeather/City.swift: -------------------------------------------------------------------------------- 1 | // 2 | // City.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 01/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | 12 | struct City { 13 | let title: String 14 | let ID: String 15 | let placeID: String 16 | var lat: Double = 0.0 17 | var lng: Double = 0.0 18 | 19 | func isLocationEnable() -> Bool { 20 | if self.lat == 0.0 && self.lng == 0.0 { 21 | return false 22 | } 23 | return true 24 | } 25 | } 26 | 27 | 28 | class CityEntity: Object { 29 | dynamic var title: String = "" 30 | dynamic var ID: String = "" 31 | dynamic var placeID: String = "" 32 | dynamic var lat: Double = 0.0 33 | dynamic var lng: Double = 0.0 34 | 35 | override static func primaryKey() -> String? { 36 | return "placeID" 37 | } 38 | } -------------------------------------------------------------------------------- /ViperWeather/ListInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListInteractor.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | protocol ListInteractorInputProtocol: class { 13 | 14 | weak var presenter: ListInteractorOutputProtocol! { get set } 15 | 16 | func removeCity(city: City) 17 | } 18 | 19 | protocol ListInteractorOutputProtocol: class { 20 | 21 | } 22 | 23 | class ListInteractor { 24 | 25 | weak var presenter: ListInteractorOutputProtocol! 26 | 27 | var dataManager: ListDataManagerInputProtocol! 28 | } 29 | 30 | extension ListInteractor: ListInteractorInputProtocol { 31 | 32 | func removeCity(city: City) { 33 | dataManager.removeCityFromPersistentStore(city) 34 | } 35 | } 36 | 37 | extension ListInteractor: ListDataManagerOutputProtocol { 38 | 39 | } -------------------------------------------------------------------------------- /ViperWeather/DetailListDetailDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListDetailDataManager.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import Foundation 12 | import RealmSwift 13 | import Alamofire 14 | 15 | 16 | protocol DetailListDetailDataManagerInputProtocol: class { 17 | 18 | weak var interactor: DetailListDetailDataManagerOutputProtocol! { get set } 19 | } 20 | 21 | protocol DetailListDetailDataManagerOutputProtocol: class { 22 | 23 | var dataManager: DetailListDetailDataManagerInputProtocol! { get set } 24 | } 25 | 26 | 27 | class DetailListDetailDataManager { 28 | 29 | weak var interactor: DetailListDetailDataManagerOutputProtocol! 30 | } 31 | 32 | extension DetailListDetailDataManager: DetailListDetailDataManagerInputProtocol { 33 | 34 | } 35 | -------------------------------------------------------------------------------- /ViperWeather/DetailContainerDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailContainerDataManager.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import Foundation 12 | import RealmSwift 13 | import Alamofire 14 | 15 | 16 | protocol DetailContainerDataManagerInputProtocol: class { 17 | 18 | weak var interactor: DetailContainerDataManagerOutputProtocol! { get set } 19 | } 20 | 21 | protocol DetailContainerDataManagerOutputProtocol: class { 22 | 23 | var dataManager: DetailContainerDataManagerInputProtocol! { get set } 24 | } 25 | 26 | 27 | class DetailContainerDataManager { 28 | 29 | weak var interactor: DetailContainerDataManagerOutputProtocol! 30 | 31 | } 32 | 33 | extension DetailContainerDataManager: DetailContainerDataManagerInputProtocol { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /ViperWeather/DetailListDetailInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListDetailInteractor.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import Foundation 12 | 13 | 14 | protocol DetailListDetailInteractorInputProtocol: class { 15 | 16 | weak var presenter: DetailListDetailInteractorOutputProtocol! { get set } 17 | } 18 | 19 | protocol DetailListDetailInteractorOutputProtocol: class { 20 | 21 | } 22 | 23 | class DetailListDetailInteractor { 24 | 25 | weak var presenter: DetailListDetailInteractorOutputProtocol! 26 | 27 | var dataManager: DetailListDetailDataManagerInputProtocol! 28 | } 29 | 30 | extension DetailListDetailInteractor: DetailListDetailInteractorInputProtocol { 31 | 32 | } 33 | 34 | extension DetailListDetailInteractor: DetailListDetailDataManagerOutputProtocol { 35 | 36 | } -------------------------------------------------------------------------------- /ViperWeather/DetailContainerInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailContainerInteractor.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import Foundation 12 | 13 | 14 | protocol DetailContainerInteractorInputProtocol: class { 15 | 16 | weak var presenter: DetailContainerInteractorOutputProtocol! { get set } 17 | } 18 | 19 | protocol DetailContainerInteractorOutputProtocol: class { 20 | 21 | } 22 | 23 | class DetailContainerInteractor { 24 | 25 | weak var presenter: DetailContainerInteractorOutputProtocol! 26 | 27 | var dataManager: DetailContainerDataManagerInputProtocol! 28 | 29 | } 30 | 31 | extension DetailContainerInteractor: DetailContainerInteractorInputProtocol { 32 | 33 | } 34 | 35 | extension DetailContainerInteractor: DetailContainerDataManagerOutputProtocol { 36 | 37 | } -------------------------------------------------------------------------------- /ViperWeather/NavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationController.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitri Utmanov on 02/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class NavigationController: UINavigationController { 12 | 13 | override func viewDidLoad() { 14 | super.viewDidLoad() 15 | } 16 | 17 | override func didReceiveMemoryWarning() { 18 | super.didReceiveMemoryWarning() 19 | // Dispose of any resources that can be recreated. 20 | } 21 | 22 | 23 | /* 24 | // MARK: - Navigation 25 | 26 | // In a storyboard-based application, you will often want to do a little preparation before navigation 27 | override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 28 | // Get the new view controller using segue.destinationViewController. 29 | // Pass the selected object to the new view controller. 30 | } 31 | */ 32 | 33 | } 34 | -------------------------------------------------------------------------------- /ViperWeather/ChildViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChildViewController.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | 13 | 14 | class ChildViewController: UIViewController { 15 | 16 | // MARK: - VIPER Properties 17 | var presenter: ChildPresenterProtocol! 18 | 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | // Do any additional setup after loading the view. 23 | } 24 | 25 | override func viewWillAppear(animated: Bool) { 26 | super.viewWillAppear(animated) 27 | // Update data. 28 | } 29 | 30 | override func didReceiveMemoryWarning() { 31 | super.didReceiveMemoryWarning() 32 | // Dispose of any resources that can be recreated. 33 | } 34 | 35 | } 36 | 37 | extension ChildViewController: ChildInterfaceProtocol { 38 | 39 | } 40 | -------------------------------------------------------------------------------- /ViperWeather/RootRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootRouter.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 20/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | protocol RootRouterInputProtocol: class { 16 | func openListViewController(fromViewController fromViewController: UIViewController) 17 | 18 | var listAssembler: ListAssembler! { get set } 19 | } 20 | 21 | protocol RootParentRouterProtocol: class { 22 | 23 | } 24 | 25 | class RootRouter: RootRouterInputProtocol { 26 | 27 | var listAssembler: ListAssembler! 28 | 29 | func openListViewController(fromViewController fromViewController: UIViewController) { 30 | listAssembler.presentListViewController(fromViewController: fromViewController) 31 | } 32 | 33 | } 34 | 35 | extension RootRouter: ListParentRouterProtocol { 36 | 37 | } 38 | -------------------------------------------------------------------------------- /ViperWeather/AddRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddRouter.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 19/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import UIKit 12 | 13 | protocol AddRouterInputProtocol: class { 14 | func closeAddViewController(viewController viewController: UIViewController) 15 | } 16 | 17 | protocol AddParentRouterProtocol: class { 18 | 19 | } 20 | 21 | class AddRouter: AddRouterInputProtocol { 22 | 23 | func closeAddViewController(viewController viewController: UIViewController) { 24 | let idiom = UIDevice.currentDevice().userInterfaceIdiom 25 | switch idiom { 26 | case .Phone: 27 | viewController.navigationController!.dismissViewControllerAnimated(true, completion: nil) 28 | 29 | case .Pad: 30 | viewController.navigationController!.popViewControllerAnimated(true) 31 | 32 | default: 33 | fatalError("Device is not supported yet") 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ViperWeather/RootAssembler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootAssembly.swift 3 | // Architecture 4 | // 5 | // Created by Dmitri Utmanov on 20/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Swinject 11 | 12 | 13 | class RootAssembler: Assembler { 14 | 15 | init(parentAssembler: Assembler) { 16 | try! super.init(assemblies: [RootContainer()], parentAssembler: parentAssembler) 17 | } 18 | 19 | } 20 | 21 | extension RootAssembler { 22 | 23 | func presentRootViewController(fromViewController fromViewController: UIViewController) { 24 | guard let navigationController = fromViewController as? UINavigationController else { 25 | return 26 | } 27 | let viewController = storyboard().instantiateInitialViewController()! as! RootViewController 28 | 29 | navigationController.pushViewController(viewController, animated: false) 30 | } 31 | 32 | func storyboard() -> SwinjectStoryboard { 33 | return SwinjectStoryboard.create(name: "Root", bundle: NSBundle.mainBundle(), container: resolver) 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /ViperWeather/ChildPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChildPresenter.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | protocol ChildPresenterProtocol: class { 16 | 17 | } 18 | 19 | protocol ChildInterfaceProtocol: class { 20 | 21 | var presenter: ChildPresenterProtocol! { get set } 22 | } 23 | 24 | class ChildPresenter { 25 | 26 | weak private var interface: ChildInterfaceProtocol! 27 | private let interactor: ChildInteractorInputProtocol 28 | private let router: ChildRouterInputProtocol 29 | 30 | 31 | init(interface: ChildInterfaceProtocol, interactor: ChildInteractorInputProtocol, router: ChildRouterInputProtocol) { 32 | self.interface = interface 33 | self.interactor = interactor 34 | self.router = router 35 | } 36 | 37 | } 38 | 39 | 40 | extension ChildPresenter: ChildPresenterProtocol { 41 | 42 | } 43 | 44 | extension ChildPresenter: ChildInteractorOutputProtocol { 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /ViperWeather/Root.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Alamofire (3.5.1) 3 | - RBQFetchedResultsController (3.0.1): 4 | - Realm (>= 0.95) 5 | - Realm (0.98.5): 6 | - Realm/Headers (= 0.98.5) 7 | - Realm/Headers (0.98.5) 8 | - RealmSwift (0.98.5): 9 | - Realm (= 0.98.5) 10 | - SnapKit (0.22.0) 11 | - SwiftFetchedResultsController (3.0.1): 12 | - RBQFetchedResultsController (>= 3.0.1) 13 | - RealmSwift (>= 0.96) 14 | - SwiftyJSON (2.3.2) 15 | - Swinject (1.1.0) 16 | 17 | DEPENDENCIES: 18 | - Alamofire (~> 3.5.0) 19 | - RealmSwift 20 | - SnapKit (~> 0.22.0) 21 | - SwiftFetchedResultsController 22 | - SwiftyJSON 23 | - Swinject 24 | 25 | SPEC CHECKSUMS: 26 | Alamofire: 0dfba1184a543e2aa160f4e39cac4e8aba48d223 27 | RBQFetchedResultsController: 4b8a80647859241abdb3da04b486a6ad02dd5093 28 | Realm: db08379969e45cea2edd1c3c0a090fe9e22d2d2f 29 | RealmSwift: cb11ad1c35e15dc982f2dbf5afe2a3124193a358 30 | SnapKit: 0dd2fd157330f1ea11fd84da13e6be8a7a22bae0 31 | SwiftFetchedResultsController: 646da58a5cde2293b692f5c8283d1db001d720bc 32 | SwiftyJSON: 04ccea08915aa0109039157c7974cf0298da292a 33 | Swinject: 514e56e46e3cd362f2cca07bb6ad1fdbed9abbfa 34 | 35 | PODFILE CHECKSUM: e50c0a70a4092310dc9579bc3e1802498e9b9af2 36 | 37 | COCOAPODS: 1.1.1 38 | -------------------------------------------------------------------------------- /ViperWeather/DetailContainerViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailContainerViewController.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DetailContainerViewController: UIViewController { 12 | 13 | // MARK: - VIPER Properties 14 | var presenter: DetailContainerPresenterProtocol! 15 | 16 | var city: City! 17 | 18 | @IBOutlet weak var detailContentView: UIView! 19 | @IBOutlet weak var detailListContentView: UIView! 20 | 21 | 22 | override func viewDidLoad() { 23 | super.viewDidLoad() 24 | 25 | // Do any additional setup after loading the view. 26 | self.title = city.title 27 | 28 | self.presenter.presentDetailViewControllerInView(detailContentView, city: city) 29 | self.presenter.presentDetailListViewControllerInView(detailListContentView, city: city) 30 | } 31 | 32 | override func didReceiveMemoryWarning() { 33 | super.didReceiveMemoryWarning() 34 | // Dispose of any resources that can be recreated. 35 | } 36 | } 37 | 38 | extension DetailContainerViewController: DetailContainerInterfaceProtocol { 39 | 40 | } 41 | -------------------------------------------------------------------------------- /ViperWeather/DetailListDetailRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListDetailRouter.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | protocol DetailListDetailRouterInputProtocol: class { 16 | func dismissDetailListDetailViewController(viewController viewController: UIViewController) 17 | func presentChildViewController(fromViewController fromViewController: UIViewController) 18 | 19 | var childAssembler: ChildAssembler! { get set } 20 | } 21 | 22 | protocol DetailListDetailParentRouterProtocol: class { 23 | 24 | } 25 | 26 | class DetailListDetailRouter: DetailListDetailRouterInputProtocol { 27 | 28 | var childAssembler: ChildAssembler! 29 | 30 | 31 | func dismissDetailListDetailViewController(viewController viewController: UIViewController) { 32 | viewController.dismissViewControllerAnimated(true, completion: nil) 33 | } 34 | 35 | func presentChildViewController(fromViewController fromViewController: UIViewController) { 36 | childAssembler.presentChildViewController(fromViewController: fromViewController) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ViperWeather/AddInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddInteractor.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 19/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import Foundation 12 | 13 | 14 | protocol AddInteractorInputProtocol: class { 15 | 16 | weak var presenter: AddInteractorOutputProtocol! { get set } 17 | 18 | func getCitiesWithName(name: String) 19 | func saveCity(city: City) 20 | } 21 | 22 | protocol AddInteractorOutputProtocol: class { 23 | 24 | func foundCities(cities: [City]) 25 | } 26 | 27 | class AddInteractor { 28 | 29 | weak var presenter: AddInteractorOutputProtocol! 30 | 31 | var dataManager: AddDataManagerInputProtocol! 32 | } 33 | 34 | extension AddInteractor: AddInteractorInputProtocol { 35 | 36 | func getCitiesWithName(name: String) { 37 | self.dataManager.fetchCitiesWithName(name) { [weak self] (cities) -> () in 38 | self?.presenter.foundCities(cities) 39 | } 40 | } 41 | 42 | func saveCity(city: City) { 43 | self.dataManager.saveCityToPersistentStore(city) 44 | } 45 | } 46 | 47 | extension AddInteractor: AddDataManagerOutputProtocol { 48 | 49 | } -------------------------------------------------------------------------------- /ViperWeather/CityTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddTableViewCell.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 02/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CityTableViewCell: UITableViewCell { 12 | 13 | var city: City? { 14 | didSet { 15 | titleLabel.text = city?.title 16 | IDLabel.text = city?.ID 17 | placeIDLabel.text = city?.placeID 18 | } 19 | } 20 | 21 | @IBOutlet weak var titleLabel: UILabel! 22 | @IBOutlet weak var IDLabel: UILabel! 23 | @IBOutlet weak var placeIDLabel: UILabel! 24 | 25 | override func awakeFromNib() { 26 | super.awakeFromNib() 27 | // Initialization code 28 | let selectedBackgroundView = UIView(frame: CGRect.zero) 29 | selectedBackgroundView.backgroundColor = MaterialColor.lightBlueColor() 30 | self.selectedBackgroundView = selectedBackgroundView 31 | } 32 | 33 | override func setSelected(selected: Bool, animated: Bool) { 34 | super.setSelected(selected, animated: animated) 35 | 36 | // Configure the view for the selected state 37 | } 38 | 39 | override func prepareForReuse() { 40 | super.prepareForReuse() 41 | 42 | city = nil 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /ViperWeather/ChildAssembler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChildAssembler.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | class ChildAssembler: Assembler { 16 | 17 | required init(parentAssembler: Assembler) { 18 | try! super.init(assemblies: [ChildContainer()], parentAssembler: parentAssembler) 19 | } 20 | } 21 | 22 | extension ChildAssembler { 23 | 24 | func presentChildViewController(fromViewController fromViewController: UIViewController) { 25 | let viewController = self.viewController() 26 | // setup viewController 27 | 28 | fromViewController.navigationController!.pushViewController(viewController, animated: true) 29 | } 30 | 31 | func storyboard() -> UIStoryboard { 32 | let storyboardName = "Detail" 33 | let bundle = NSBundle.mainBundle() 34 | return SwinjectStoryboard.create(name: storyboardName, bundle: bundle, container: resolver) 35 | } 36 | 37 | func viewController() -> ChildViewController { 38 | return storyboard().instantiateViewControllerWithIdentifier("ChildViewControllerID") as! ChildViewController 39 | } 40 | } -------------------------------------------------------------------------------- /ViperWeather/RootPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootPresenter.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 20/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import UIKit 12 | 13 | protocol RootPresenterProtocol: class { 14 | func openListViewController() 15 | } 16 | 17 | protocol RootInterfaceProtocol: class { 18 | 19 | var presenter: RootPresenterProtocol! { get set } 20 | 21 | } 22 | 23 | class RootPresenter { 24 | 25 | weak private var interface: RootInterfaceProtocol? 26 | private let interactor: RootInteractorInputProtocol 27 | private let router: RootRouterInputProtocol 28 | 29 | init(interface: RootInterfaceProtocol, interactor: RootInteractorInputProtocol, router: RootRouterInputProtocol) { 30 | self.interface = interface 31 | self.interactor = interactor 32 | self.router = router 33 | } 34 | 35 | } 36 | 37 | extension RootPresenter: RootPresenterProtocol { 38 | 39 | func openListViewController() { 40 | self.router.openListViewController(fromViewController: interface as! UIViewController) 41 | } 42 | 43 | } 44 | 45 | extension RootPresenter: RootInteractorOutputProtocol { 46 | 47 | } 48 | -------------------------------------------------------------------------------- /ViperWeather/DetailListRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListRouter.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | protocol DetailListRouterInputProtocol: class { 16 | func dismissDetailListViewController(viewController viewController: UIViewController) 17 | func presentDetailListDetailViewController(fromViewController fromViewController: UIViewController) 18 | 19 | var detailListDetailAssembler: DetailListDetailAssembler! { get set } 20 | } 21 | 22 | protocol DetailListParentRouterProtocol: class { 23 | 24 | } 25 | 26 | class DetailListRouter: DetailListRouterInputProtocol { 27 | 28 | var detailListDetailAssembler: DetailListDetailAssembler! 29 | 30 | 31 | func dismissDetailListViewController(viewController viewController: UIViewController) { 32 | viewController.dismissViewControllerAnimated(true, completion: nil) 33 | } 34 | 35 | func presentDetailListDetailViewController(fromViewController fromViewController: UIViewController) { 36 | detailListDetailAssembler.presentDetailListDetailViewController(fromViewController: fromViewController) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /ViperWeather/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | #Viper App Template inspired by Rambler 3 | 4 | ## Features 5 | 6 | - [x] Swinject - Dependency Injection 7 | - [x] RealmSwift - Persistent Storage 8 | - [x] SwiftFetchedResultsController (NSFetchedResultsController for Realm written in Swift) 9 | - [x] Alamofire - Networking 10 | - [x] UIStoryboard + XIB 11 | - [x] UIAppearance 12 | - [ ] MaterialColor -> CocoaPods 13 | - [ ] SnapKit 14 | 15 | 16 | ## How to use 17 | 18 | 1. Go to: https://console.developers.google.com/apis/credentials 19 | 2. Sign In 20 | 3. Registrate Server API key 21 | 4. Paste key to Constants.swift -> googleMapKey 22 | 5. Go to https://home.openweathermap.org/ or http://openweathermap.org/api 23 | 6. Sign In 24 | 7. Paste key to Constants.swift -> openWeatherMapKey 25 | 8. Install CocoaPods 26 | 9. Go to application folder 27 | 10. Run "pod install" in terminal 28 | 11. Run application and enjoy! 29 | 30 | # Feedback 31 | ### I've found a bug, or have a feature request 32 | 33 | Please raise a GitHub issue. 34 | 35 | ### I'm blown away! 36 | 37 | ViperWeather is a non-profit, community driven project. We only ask that if you've found it useful to star us on Github. If you've written a related blog or tutorial, or published a new app based on ViperWeather codebase, we'd certainly be happy to hear about that too. 38 | -------------------------------------------------------------------------------- /ViperWeather/DetailListDetailAssembler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListDetailAssembler.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | class DetailListDetailAssembler: Assembler { 16 | 17 | required init(parentAssembler: Assembler) { 18 | try! super.init(assemblies: [DetailListDetailContainer()], parentAssembler: parentAssembler) 19 | } 20 | } 21 | 22 | extension DetailListDetailAssembler { 23 | 24 | func presentDetailListDetailViewController(fromViewController fromViewController: UIViewController) { 25 | let viewController = self.viewController() 26 | // setup viewController 27 | 28 | fromViewController.navigationController!.pushViewController(viewController, animated: true) 29 | } 30 | 31 | func storyboard() -> UIStoryboard { 32 | let storyboardName = "Detail" 33 | let bundle = NSBundle.mainBundle() 34 | return SwinjectStoryboard.create(name: storyboardName, bundle: bundle, container: resolver) 35 | } 36 | 37 | func viewController() -> DetailListDetailViewController { 38 | return storyboard().instantiateViewControllerWithIdentifier("DetailListDetailViewControllerID") as! DetailListDetailViewController 39 | } 40 | } -------------------------------------------------------------------------------- /ViperWeather/WeatherTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WeatherTableViewCell.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class WeatherTableViewCell: UITableViewCell { 12 | 13 | var weather: Weather? { 14 | didSet { 15 | if let weather = weather { 16 | let dateFormatter = NSDateFormatter() 17 | dateFormatter.dateStyle = .MediumStyle 18 | dateFormatter.timeStyle = .MediumStyle 19 | dateLabel.text = dateFormatter.stringFromDate(NSDate(timeIntervalSince1970: weather.dt)) 20 | 21 | weatherLabel.text = weather.tempString 22 | } else { 23 | dateLabel.text = nil 24 | weatherLabel.text = nil 25 | } 26 | } 27 | } 28 | 29 | @IBOutlet weak var dateLabel: UILabel! 30 | @IBOutlet weak var weatherLabel: UILabel! 31 | 32 | 33 | override func awakeFromNib() { 34 | super.awakeFromNib() 35 | // Initialization code 36 | } 37 | 38 | override func setSelected(selected: Bool, animated: Bool) { 39 | super.setSelected(selected, animated: animated) 40 | 41 | // Configure the view for the selected state 42 | } 43 | 44 | override func prepareForReuse() { 45 | super.prepareForReuse() 46 | 47 | weather = nil 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ViperWeather/DetailInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailInteractor.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | protocol DetailInteractorInputProtocol: class { 13 | 14 | weak var presenter: DetailInteractorOutputProtocol! { get set } 15 | 16 | func getDetailCity(city: City) 17 | func getWeatherForCity(city: City) 18 | } 19 | 20 | protocol DetailInteractorOutputProtocol: class { 21 | 22 | func foundDetailCity(city: City) 23 | func foundWeatherForCity(weather: [Weather], city: City) 24 | } 25 | 26 | class DetailInteractor { 27 | 28 | weak var presenter: DetailInteractorOutputProtocol! 29 | 30 | var dataManager: DetailDataManagerInputProtocol! 31 | 32 | } 33 | 34 | extension DetailInteractor: DetailInteractorInputProtocol { 35 | 36 | func getDetailCity(city: City) { 37 | self.dataManager.getDetailCity(city) { [weak self] (city) -> () in 38 | self?.dataManager.updateCityInPersistentStore(city) 39 | self?.presenter.foundDetailCity(city) 40 | } 41 | } 42 | 43 | func getWeatherForCity(city: City) { 44 | self.dataManager.getWeatherForCity(city) { [weak self] (weather) -> () in 45 | self?.presenter.foundWeatherForCity(weather, city: city) 46 | } 47 | } 48 | 49 | } 50 | 51 | extension DetailInteractor: DetailDataManagerOutputProtocol { 52 | 53 | } -------------------------------------------------------------------------------- /ViperWeather/DetailListDetailPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListDetailPresenter.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | protocol DetailListDetailPresenterProtocol: class { 16 | func presentChildViewController() 17 | } 18 | 19 | protocol DetailListDetailInterfaceProtocol: class { 20 | 21 | var presenter: DetailListDetailPresenterProtocol! { get set } 22 | } 23 | 24 | class DetailListDetailPresenter { 25 | 26 | weak private var interface: DetailListDetailInterfaceProtocol! 27 | private let interactor: DetailListDetailInteractorInputProtocol 28 | private let router: DetailListDetailRouterInputProtocol 29 | 30 | 31 | init(interface: DetailListDetailInterfaceProtocol, interactor: DetailListDetailInteractorInputProtocol, router: DetailListDetailRouterInputProtocol) { 32 | self.interface = interface 33 | self.interactor = interactor 34 | self.router = router 35 | } 36 | 37 | } 38 | 39 | 40 | extension DetailListDetailPresenter: DetailListDetailPresenterProtocol { 41 | 42 | func presentChildViewController() { 43 | self.router.presentChildViewController(fromViewController: self.interface as! UIViewController) 44 | } 45 | } 46 | 47 | extension DetailListDetailPresenter: DetailListDetailInteractorOutputProtocol { 48 | 49 | } 50 | 51 | -------------------------------------------------------------------------------- /ViperWeather/DetailListDetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListDetailViewController.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | 13 | 14 | class DetailListDetailViewController: UIViewController { 15 | 16 | // MARK: - VIPER Properties 17 | var presenter: DetailListDetailPresenterProtocol! 18 | 19 | 20 | override func viewDidLoad() { 21 | super.viewDidLoad() 22 | // Do any additional setup after loading the view. 23 | } 24 | 25 | override func viewWillAppear(animated: Bool) { 26 | super.viewWillAppear(animated) 27 | // Update data. 28 | } 29 | 30 | 31 | // MARK: ChildViewController Presentation Action 32 | 33 | // Uncomment this snippet for present new ViewController after current ViewController called viewDidAppear func 34 | // or paste line to another func (e.g. tap on UIBitton -> IBAction) 35 | override func viewDidAppear(animated: Bool) { 36 | super.viewDidAppear(animated) 37 | 38 | self.presenter.presentChildViewController() 39 | } 40 | 41 | 42 | override func didReceiveMemoryWarning() { 43 | super.didReceiveMemoryWarning() 44 | // Dispose of any resources that can be recreated. 45 | } 46 | 47 | } 48 | 49 | extension DetailListDetailViewController: DetailListDetailInterfaceProtocol { 50 | 51 | } 52 | -------------------------------------------------------------------------------- /ViperWeather/RootViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootViewController.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 20/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import UIKit 12 | 13 | 14 | class RootViewController: UIViewController { 15 | 16 | var presenter: RootPresenterProtocol! 17 | 18 | @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView! 19 | 20 | override var nibName: String? { 21 | get { 22 | let classString = String(self.dynamicType) 23 | return classString 24 | } 25 | } 26 | override var nibBundle: NSBundle? { 27 | get { 28 | return NSBundle.mainBundle() 29 | } 30 | } 31 | 32 | override func viewDidLoad() { 33 | super.viewDidLoad() 34 | 35 | self.navigationController!.setNavigationBarHidden(true, animated: false) 36 | 37 | activityIndicatorView.color = MaterialColor.cyanColor() 38 | } 39 | 40 | override func viewDidAppear(animated: Bool) { 41 | super.viewDidAppear(animated) 42 | 43 | // Imitation of login request or fetch some data 44 | let time = dispatch_time(DISPATCH_TIME_NOW, Int64(1.0 * Double(NSEC_PER_SEC))) 45 | dispatch_after(time, dispatch_get_main_queue()) { [weak self] () -> Void in 46 | self?.presenter.openListViewController() 47 | } 48 | } 49 | } 50 | 51 | extension RootViewController: RootInterfaceProtocol { 52 | 53 | } 54 | -------------------------------------------------------------------------------- /ViperWeather/DetailListInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListInteractor.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import Foundation 12 | 13 | 14 | protocol DetailListInteractorInputProtocol: class { 15 | 16 | weak var presenter: DetailListInteractorOutputProtocol! { get set } 17 | 18 | func getDetailCity(city: City) 19 | func getWeatherForCity(city: City) 20 | } 21 | 22 | protocol DetailListInteractorOutputProtocol: class { 23 | 24 | func foundDetailCity(city: City) 25 | func foundWeatherForCity(weather: [Weather], city: City) 26 | } 27 | 28 | class DetailListInteractor { 29 | 30 | weak var presenter: DetailListInteractorOutputProtocol! 31 | 32 | var dataManager: DetailListDataManagerInputProtocol! 33 | 34 | } 35 | 36 | extension DetailListInteractor: DetailListInteractorInputProtocol { 37 | 38 | func getDetailCity(city: City) { 39 | self.dataManager.getDetailCity(city) { [weak self] (city) -> () in 40 | self?.dataManager.updateCityInPersistentStore(city) 41 | self?.presenter.foundDetailCity(city) 42 | } 43 | } 44 | 45 | func getWeatherForCity(city: City) { 46 | self.dataManager.getWeatherForCity(city) { [weak self] (weather) -> () in 47 | self?.presenter.foundWeatherForCity(weather, city: city) 48 | } 49 | } 50 | } 51 | 52 | extension DetailListInteractor: DetailListDataManagerOutputProtocol { 53 | 54 | } -------------------------------------------------------------------------------- /ViperWeather/ListRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListRouter.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Swinject 11 | 12 | 13 | protocol ListRouterInputProtocol: class { 14 | func presentAddViewController(fromViewController fromViewController: UIViewController) 15 | func presentDetailViewController(fromViewController fromViewController: UIViewController, city: City) 16 | 17 | func dismissListViewController(viewController viewController: UIViewController) 18 | 19 | var detailContainerAssembler: DetailContainerAssembler! { get set } 20 | var addAssembler: AddAssembler! { get set } 21 | } 22 | 23 | protocol ListParentRouterProtocol: class { 24 | 25 | } 26 | 27 | class ListRouter: ListRouterInputProtocol { 28 | 29 | var detailContainerAssembler: DetailContainerAssembler! 30 | var addAssembler: AddAssembler! 31 | 32 | 33 | func dismissListViewController(viewController viewController: UIViewController) { 34 | viewController.dismissViewControllerAnimated(true, completion: nil) 35 | } 36 | 37 | func presentAddViewController(fromViewController fromViewController: UIViewController) { 38 | addAssembler.presentAddViewController(fromViewController: fromViewController) 39 | } 40 | 41 | func presentDetailViewController(fromViewController fromViewController: UIViewController, city: City) { 42 | detailContainerAssembler.presentDetailContainerViewController(fromViewController: fromViewController, city: city) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.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 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | 26 | ## Obj-C/Swift specific 27 | *.hmap 28 | *.ipa 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/docs/Gitignore.md 61 | 62 | fastlane/report.xml 63 | fastlane/screenshots 64 | -------------------------------------------------------------------------------- /ViperWeather/AddAssembler.swift: -------------------------------------------------------------------------------- 1 | 2 | // AddAssembler.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 19/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | class AddAssembler: Assembler { 15 | 16 | init(parentAssembler: Assembler) { 17 | try! super.init(assemblies: [AddContainer()], parentAssembler: parentAssembler) 18 | } 19 | } 20 | 21 | extension AddAssembler { 22 | 23 | func presentAddViewController(fromViewController fromViewController: UIViewController) { 24 | let viewController = storyboard().instantiateViewControllerWithIdentifier("AddTableViewControllerID") as! AddTableViewController 25 | 26 | viewController.presenter.delegate = fromViewController as? AddViewControllerDelegate 27 | 28 | let idiom = UIDevice.currentDevice().userInterfaceIdiom 29 | switch idiom { 30 | case .Phone: 31 | let navigationController = UINavigationController(rootViewController: viewController) 32 | fromViewController.presentViewController(navigationController, animated: true, completion: nil) 33 | 34 | case .Pad: 35 | fromViewController.navigationController!.pushViewController(viewController, animated: true) 36 | 37 | default: 38 | fatalError("Device is not supported yet") 39 | } 40 | } 41 | 42 | func storyboard() -> SwinjectStoryboard { 43 | return SwinjectStoryboard.create(name: "List", bundle: NSBundle.mainBundle(), container: resolver) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ViperWeather/ListPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListPresenter.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Swinject 11 | 12 | 13 | protocol ListPresenterProtocol: class { 14 | 15 | func showDetailCity(city: City) 16 | func addNew() 17 | func exit() 18 | func removeCity(city: City) 19 | } 20 | 21 | protocol ListInterfaceProtocol: class { 22 | 23 | var presenter: ListPresenterProtocol! { get set } 24 | } 25 | 26 | class ListPresenter { 27 | 28 | weak private var interface: ListInterfaceProtocol! 29 | private let interactor: ListInteractorInputProtocol 30 | private let router: ListRouterInputProtocol 31 | 32 | 33 | init(interface: ListInterfaceProtocol, interactor: ListInteractorInputProtocol, router: ListRouterInputProtocol) { 34 | self.interface = interface 35 | self.interactor = interactor 36 | self.router = router 37 | } 38 | 39 | } 40 | 41 | 42 | extension ListPresenter: ListPresenterProtocol { 43 | 44 | func showDetailCity(city: City) { 45 | self.router.presentDetailViewController(fromViewController: self.interface as! UIViewController, city: city) 46 | } 47 | 48 | func removeCity(city: City) { 49 | self.interactor.removeCity(city) 50 | } 51 | 52 | func addNew() { 53 | self.router.presentAddViewController(fromViewController: self.interface as! UIViewController) 54 | } 55 | 56 | func exit() { 57 | self.router.dismissListViewController(viewController: self.interface as! UIViewController) 58 | } 59 | } 60 | 61 | extension ListPresenter: ListInteractorOutputProtocol { 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /ViperWeather/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /ViperWeather/ListDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListDataManager.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitri Utmanov on 02/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | import Alamofire 12 | 13 | 14 | protocol ListDataManagerInputProtocol: class { 15 | 16 | weak var interactor: ListDataManagerOutputProtocol! { get set } 17 | 18 | func fetchCitiesFromPersistentStore(callback: ([City]) -> ()) 19 | func removeCityFromPersistentStore(city: City) 20 | } 21 | 22 | protocol ListDataManagerOutputProtocol: class { 23 | 24 | var dataManager: ListDataManagerInputProtocol! { get set } 25 | } 26 | 27 | 28 | class ListDataManager { 29 | 30 | weak var interactor: ListDataManagerOutputProtocol! 31 | } 32 | 33 | extension ListDataManager: ListDataManagerInputProtocol { 34 | 35 | func fetchCitiesFromPersistentStore(callback: ([City]) -> ()) { 36 | let realm = try! Realm() 37 | 38 | let cityEntities = realm.objects(CityEntity) 39 | 40 | var cities: [City] = [] 41 | for cityEntity in cityEntities { 42 | let city = City(title: cityEntity.title, ID: cityEntity.ID, placeID: cityEntity.placeID, lat: cityEntity.lat, lng: cityEntity.lng) 43 | cities.append(city) 44 | } 45 | callback(cities) 46 | } 47 | 48 | func removeCityFromPersistentStore(city: City) { 49 | let realm = try! Realm() 50 | 51 | realm.beginWrite() 52 | 53 | let predicate = NSPredicate(format: "ID = %@", argumentArray: [city.ID]) 54 | let cityEntities = realm.objects(CityEntity).filter(predicate) 55 | 56 | realm.deleteWithNotification(cityEntities) 57 | 58 | try! realm.commitWrite() 59 | } 60 | 61 | } -------------------------------------------------------------------------------- /ViperWeather/RootContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RootContainer.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 20/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | class RootContainer: AssemblyType { 15 | 16 | func assemble(container: Container) { 17 | container.registerForStoryboard(RootViewController.self) { (r, c) -> () in 18 | container.register(RootPresenterProtocol.self) { [weak c] r in 19 | guard let c = c else { fatalError("Contoller is nil") } 20 | 21 | let interface = c 22 | let interactor = r.resolve(RootInteractorInputProtocol.self)! 23 | let router = r.resolve(RootRouterInputProtocol.self)! 24 | 25 | let presenter = RootPresenter(interface: interface, interactor: interactor, router: router) 26 | interactor.presenter = presenter 27 | 28 | return presenter 29 | } 30 | c.presenter = r.resolve(RootPresenterProtocol.self) 31 | } 32 | 33 | container.register(RootInteractorInputProtocol.self) { r in 34 | return RootInteractor() 35 | } 36 | 37 | container.register(RootRouterInputProtocol.self) { r in 38 | let router = RootRouter() 39 | router.listAssembler = r.resolve(ListAssembler.self)! 40 | return router 41 | } 42 | 43 | container.register(ListAssembler.self) { r in 44 | let parentAssembler = r.resolve(RootAssembler.self)! 45 | return ListAssembler(parentAssembler: parentAssembler) 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /ViperWeather/DetailContainerRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailContainerRouter.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | protocol DetailContainerRouterInputProtocol: class { 16 | func dismissDetailContainerViewController(viewController viewController: UIViewController) 17 | 18 | func presentDetailViewController(viewController viewController: UIViewController, view: UIView, city: City) 19 | func presentDetailListViewController(viewController viewController: UIViewController, view: UIView, city: City) 20 | 21 | var detailAssembler: DetailAssembler! { get set } 22 | var detailListAssembler: DetailListAssembler! { get set } 23 | } 24 | 25 | protocol DetailContainerParentRouterProtocol: class { 26 | 27 | } 28 | 29 | class DetailContainerRouter: DetailContainerRouterInputProtocol { 30 | 31 | var detailAssembler: DetailAssembler! 32 | var detailListAssembler: DetailListAssembler! 33 | 34 | 35 | func dismissDetailContainerViewController(viewController viewController: UIViewController) { 36 | viewController.dismissViewControllerAnimated(true, completion: nil) 37 | } 38 | 39 | func presentDetailViewController(viewController viewController: UIViewController, view: UIView, city: City) { 40 | detailAssembler.presentDetailViewController(fromViewController: viewController, inView: view, city: city) 41 | } 42 | 43 | func presentDetailListViewController(viewController viewController: UIViewController, view: UIView, city: City) { 44 | detailListAssembler.presentDetailListViewController(fromViewController: viewController, inView: view, city: city) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /ViperWeather/DetailContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailContainer.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Swinject 11 | 12 | 13 | class DetailContainer: AssemblyType { 14 | 15 | func assemble(container: Container) { 16 | container.registerForStoryboard(DetailViewController.self) { (r, c) -> () in 17 | container.register(DetailPresenterProtocol.self) { [weak c] r in 18 | guard let c = c else { fatalError("Contoller is nil") } 19 | 20 | let interface = c 21 | let interactor = r.resolve(DetailInteractorInputProtocol.self)! 22 | let router = r.resolve(DetailRouterInputProtocol.self)! 23 | 24 | let presenter = DetailPresenter(interface: interface, interactor: interactor, router: router) 25 | interactor.presenter = presenter 26 | 27 | return presenter 28 | } 29 | c.presenter = r.resolve(DetailPresenterProtocol.self) 30 | } 31 | 32 | container.register(DetailInteractorInputProtocol.self) { r in 33 | let interactor = DetailInteractor() 34 | let dataManager = r.resolve(DetailDataManagerInputProtocol.self)! 35 | interactor.dataManager = dataManager 36 | dataManager.interactor = interactor 37 | return interactor 38 | } 39 | 40 | container.register(DetailRouterInputProtocol.self) { (r) in 41 | let router = DetailRouter() 42 | return router 43 | } 44 | 45 | container.register(DetailDataManagerInputProtocol.self) { (r) in 46 | let dataManager = DetailDataManager() 47 | return dataManager 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ViperWeather/DetailContainerAssembler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailContainerAssembler.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | class DetailContainerAssembler: Assembler { 16 | 17 | required init(parentAssembler: Assembler) { 18 | try! super.init(assemblies: [DetailContainerContainer()], parentAssembler: parentAssembler) 19 | } 20 | } 21 | 22 | extension DetailContainerAssembler { 23 | 24 | func presentDetailContainerViewController(fromViewController fromViewController: UIViewController, city: City) { 25 | let viewController = self.viewController() 26 | viewController.city = city 27 | 28 | let idiom = UIDevice.currentDevice().userInterfaceIdiom 29 | switch idiom { 30 | case .Phone: 31 | fromViewController.navigationController!.pushViewController(viewController, animated: true) 32 | 33 | case .Pad: 34 | let navigationController = fromViewController.splitViewController?.viewControllers[1] as! UINavigationController 35 | navigationController.setViewControllers([viewController], animated: false) 36 | 37 | default: 38 | fatalError("Device is not supported yet") 39 | } 40 | 41 | } 42 | 43 | func storyboard() -> UIStoryboard { 44 | let storyboardName = "Detail" 45 | let bundle = NSBundle.mainBundle() 46 | return SwinjectStoryboard.create(name: storyboardName, bundle: bundle, container: resolver) 47 | } 48 | 49 | func viewController() -> DetailContainerViewController { 50 | return storyboard().instantiateInitialViewController() as! DetailContainerViewController 51 | } 52 | } -------------------------------------------------------------------------------- /ViperWeather/DetailContainerPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailContainerPresenter.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | protocol DetailContainerPresenterProtocol: class { 16 | 17 | func presentDetailViewControllerInView(view: UIView, city: City) 18 | func presentDetailListViewControllerInView(view: UIView, city: City) 19 | } 20 | 21 | protocol DetailContainerInterfaceProtocol: class { 22 | 23 | var presenter: DetailContainerPresenterProtocol! { get set } 24 | } 25 | 26 | class DetailContainerPresenter { 27 | 28 | weak private var interface: DetailContainerInterfaceProtocol! 29 | private let interactor: DetailContainerInteractorInputProtocol 30 | private let router: DetailContainerRouterInputProtocol 31 | 32 | 33 | init(interface: DetailContainerInterfaceProtocol, interactor: DetailContainerInteractorInputProtocol, router: DetailContainerRouterInputProtocol) { 34 | self.interface = interface 35 | self.interactor = interactor 36 | self.router = router 37 | } 38 | } 39 | 40 | 41 | extension DetailContainerPresenter: DetailContainerPresenterProtocol { 42 | 43 | func presentDetailViewControllerInView(view: UIView, city: City) { 44 | self.router.presentDetailViewController(viewController: self.interface as! UIViewController, view: view, city: city) 45 | } 46 | 47 | func presentDetailListViewControllerInView(view: UIView, city: City) { 48 | self.router.presentDetailListViewController(viewController: self.interface as! UIViewController, view: view, city: city) 49 | } 50 | } 51 | 52 | extension DetailContainerPresenter: DetailContainerInteractorOutputProtocol { 53 | 54 | } 55 | 56 | -------------------------------------------------------------------------------- /ViperWeather/DetailPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailPresenter.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Swinject 11 | 12 | 13 | protocol DetailPresenterProtocol: class { 14 | 15 | func getDetailCity(city: City) 16 | func getWeatherForCity(city: City) 17 | } 18 | 19 | protocol DetailInterfaceProtocol: class { 20 | 21 | var presenter: DetailPresenterProtocol! { get set } 22 | 23 | func showEmpty() 24 | func showCity(city: City) 25 | func showWeatherForCity(weather: [Weather], city: City) 26 | } 27 | 28 | class DetailPresenter { 29 | 30 | weak private var interface: DetailInterfaceProtocol! 31 | private let interactor: DetailInteractorInputProtocol 32 | private let router: DetailRouterInputProtocol 33 | 34 | 35 | init(interface: DetailInterfaceProtocol, interactor: DetailInteractorInputProtocol, router: DetailRouterInputProtocol) { 36 | self.interface = interface 37 | self.interactor = interactor 38 | self.router = router 39 | } 40 | } 41 | 42 | 43 | extension DetailPresenter: DetailPresenterProtocol { 44 | 45 | func getDetailCity(city: City) { 46 | self.interactor.getDetailCity(city) 47 | } 48 | 49 | func getWeatherForCity(city: City) { 50 | self.interactor.getWeatherForCity(city) 51 | } 52 | } 53 | 54 | extension DetailPresenter: DetailInteractorOutputProtocol { 55 | 56 | func foundDetailCity(city: City) { 57 | guard city.isLocationEnable() == true else { 58 | self.interface.showEmpty() 59 | return 60 | } 61 | self.interface.showCity(city) 62 | } 63 | 64 | func foundWeatherForCity(weather: [Weather], city: City) { 65 | self.interface.showWeatherForCity(weather, city: city) 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /ViperWeather/ListAssembler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListAssembler.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Swinject 11 | 12 | 13 | class ListAssembler: Assembler { 14 | 15 | required init(parentAssembler: Assembler) { 16 | try! super.init(assemblies: [ListContainer()], parentAssembler: parentAssembler) 17 | } 18 | } 19 | 20 | extension ListAssembler { 21 | 22 | func presentListViewController(fromViewController fromViewController: UIViewController) { 23 | let viewController = storyboard().instantiateViewControllerWithIdentifier("ListTableViewControllerID") as! ListTableViewController 24 | 25 | let navigationController = UINavigationController(rootViewController: viewController) 26 | 27 | let idiom = UIDevice.currentDevice().userInterfaceIdiom 28 | switch idiom { 29 | case .Phone: 30 | fromViewController.presentViewController(navigationController, animated: true, completion: nil) 31 | 32 | case .Pad: 33 | let splitViewController = UISplitViewController() 34 | let detailNavigationController = UINavigationController() 35 | splitViewController.viewControllers = [navigationController, detailNavigationController] 36 | 37 | splitViewController.presentsWithGesture = false 38 | splitViewController.preferredDisplayMode = .AllVisible 39 | 40 | fromViewController.presentViewController(splitViewController, animated: true, completion: nil) 41 | 42 | default: 43 | fatalError("Device is not supported yet") 44 | } 45 | } 46 | 47 | func storyboard() -> UIStoryboard { 48 | return SwinjectStoryboard.create(name: "List", bundle: NSBundle.mainBundle(), container: resolver) 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /ViperWeather/ChildContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChildContainer.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | class ChildContainer: AssemblyType { 16 | 17 | func assemble(container: Container) { 18 | container.registerForStoryboard(ChildViewController.self) { (r, c) -> () in 19 | container.register(ChildPresenterProtocol.self) { [weak c] r in 20 | guard let c = c else { fatalError("ViewController is nil") } 21 | 22 | let interface = c 23 | let interactor = r.resolve(ChildInteractorInputProtocol.self)! 24 | let router = r.resolve(ChildRouterInputProtocol.self)! 25 | 26 | let presenter = ChildPresenter(interface: interface, interactor: interactor, router: router) 27 | interactor.presenter = presenter 28 | 29 | return presenter 30 | } 31 | c.presenter = r.resolve(ChildPresenterProtocol.self) 32 | } 33 | 34 | container.register(ChildInteractorInputProtocol.self) { r in 35 | let interactor = ChildInteractor() 36 | let dataManager = r.resolve(ChildDataManagerInputProtocol.self)! 37 | interactor.dataManager = dataManager 38 | dataManager.interactor = interactor 39 | return interactor 40 | } 41 | 42 | container.register(ChildRouterInputProtocol.self) { (r) in 43 | let router = ChildRouter() 44 | return router 45 | } 46 | 47 | container.register(ChildDataManagerInputProtocol.self) { (r) in 48 | let dataManager = ChildDataManager() 49 | return dataManager 50 | } 51 | 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /ViperWeather/AddContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddContainer.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 19/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | class AddContainer: AssemblyType { 15 | 16 | func assemble(container: Container) { 17 | container.registerForStoryboard(AddTableViewController.self) { (r, c) -> () in 18 | container.register(AddPresenterProtocol.self) { [weak c] r in 19 | guard let c = c else { fatalError("Contoller is nil") } 20 | 21 | let interface = c 22 | let interactor = r.resolve(AddInteractorInputProtocol.self)! 23 | let router = r.resolve(AddRouterInputProtocol.self)! 24 | 25 | let presenter = AddPresenter(interface: interface, interactor: interactor, router: router) 26 | interactor.presenter = presenter 27 | 28 | return presenter 29 | } 30 | c.presenter = r.resolve(AddPresenterProtocol.self) 31 | } 32 | 33 | container.register(AddInteractorInputProtocol.self) { r in 34 | let interactor = AddInteractor() 35 | let dataManager = r.resolve(AddDataManagerInputProtocol.self)! 36 | interactor.dataManager = dataManager 37 | dataManager.interactor = interactor 38 | return interactor 39 | } 40 | 41 | container.register(AddRouterInputProtocol.self) { (r) in 42 | let router = AddRouter() 43 | return router 44 | } 45 | 46 | container.register(AddDataManagerInputProtocol.self) { (r) in 47 | let dataManager = AddDataManager() 48 | return dataManager 49 | } 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /ViperWeather/ListTableViewController.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /ViperWeather/AddTableViewController.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /ViperWeather/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-Small@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-Small@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "40x40", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-Small-40@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-Small-40@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "60x60", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-60@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-60@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "29x29", 41 | "idiom" : "ipad", 42 | "filename" : "Icon-Small.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "29x29", 47 | "idiom" : "ipad", 48 | "filename" : "Icon-Small@2x-1.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "40x40", 53 | "idiom" : "ipad", 54 | "filename" : "Icon-Small-40.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "40x40", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-Small-40@2x-1.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "76x76", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-76.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "76x76", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-76@2x.png", 73 | "scale" : "2x" 74 | }, 75 | { 76 | "size" : "83.5x83.5", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-83.5@2x.png", 79 | "scale" : "2x" 80 | } 81 | ], 82 | "info" : { 83 | "version" : 1, 84 | "author" : "xcode" 85 | } 86 | } -------------------------------------------------------------------------------- /ViperWeather/DetailListViewController.xib: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /ViperWeather/DetailListPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListPresenter.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | protocol DetailListPresenterProtocol: class { 16 | 17 | func getDetailCity(city: City) 18 | func getWeatherForCity(city: City) 19 | 20 | func presentDetailListDetailViewController(testString: String) 21 | } 22 | 23 | protocol DetailListInterfaceProtocol: class { 24 | 25 | var presenter: DetailListPresenterProtocol! { get set } 26 | 27 | func showEmpty() 28 | func showCity(city: City) 29 | func showWeatherForCity(weather: [Weather], city: City) 30 | } 31 | 32 | class DetailListPresenter { 33 | 34 | weak private var interface: DetailListInterfaceProtocol! 35 | private let interactor: DetailListInteractorInputProtocol 36 | private let router: DetailListRouterInputProtocol 37 | 38 | 39 | init(interface: DetailListInterfaceProtocol, interactor: DetailListInteractorInputProtocol, router: DetailListRouterInputProtocol) { 40 | self.interface = interface 41 | self.interactor = interactor 42 | self.router = router 43 | } 44 | } 45 | 46 | 47 | extension DetailListPresenter: DetailListPresenterProtocol { 48 | 49 | func getDetailCity(city: City) { 50 | self.interactor.getDetailCity(city) 51 | } 52 | 53 | func getWeatherForCity(city: City) { 54 | self.interactor.getWeatherForCity(city) 55 | } 56 | 57 | func presentDetailListDetailViewController(testString: String) { 58 | self.router.presentDetailListDetailViewController(fromViewController: self.interface as! UIViewController) 59 | } 60 | } 61 | 62 | extension DetailListPresenter: DetailListInteractorOutputProtocol { 63 | 64 | func foundDetailCity(city: City) { 65 | guard city.isLocationEnable() == true else { 66 | self.interface.showEmpty() 67 | return 68 | } 69 | self.interface.showCity(city) 70 | } 71 | 72 | func foundWeatherForCity(weather: [Weather], city: City) { 73 | self.interface.showWeatherForCity(weather, city: city) 74 | } 75 | } 76 | 77 | -------------------------------------------------------------------------------- /ViperWeather/DetailAssembler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailAssembler.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Swinject 11 | 12 | 13 | class DetailAssembler: Assembler { 14 | 15 | required init(parentAssembler: Assembler) { 16 | try! super.init(assemblies: [DetailContainer()], parentAssembler: parentAssembler) 17 | } 18 | } 19 | 20 | extension DetailAssembler { 21 | 22 | func presentDetailViewController(fromViewController fromViewController: UIViewController, city: City) { 23 | let viewController = self.viewController() 24 | viewController.city = city 25 | 26 | fromViewController.navigationController!.pushViewController(viewController, animated: true) 27 | } 28 | 29 | func presentDetailViewController(fromViewController fromViewController: UIViewController, inView view: UIView, city: City) { 30 | let childViewController = self.viewController() 31 | childViewController.city = city 32 | 33 | fromViewController.addChildViewController(childViewController) 34 | view.addSubview(childViewController.view) 35 | 36 | childViewController.view.translatesAutoresizingMaskIntoConstraints = false 37 | 38 | let childView = childViewController.view 39 | let views: [String : AnyObject] = ["childView": childView] 40 | 41 | var childViewLayoutConstraint: [NSLayoutConstraint] = [] 42 | childViewLayoutConstraint += NSLayoutConstraint.constraintsWithVisualFormat("|-(0)-[childView]-(0)-|", options: [], metrics: nil, views: views) 43 | childViewLayoutConstraint += NSLayoutConstraint.constraintsWithVisualFormat("V:|-(0)-[childView]-(0)-|", options: [], metrics: nil, views: views) 44 | NSLayoutConstraint.activateConstraints(childViewLayoutConstraint) 45 | 46 | childViewController.didMoveToParentViewController(fromViewController) 47 | } 48 | 49 | func storyboard() -> SwinjectStoryboard { 50 | return SwinjectStoryboard.create(name: "Detail", bundle: NSBundle.mainBundle(), container: resolver) 51 | } 52 | 53 | func viewController() -> DetailViewController { 54 | return storyboard().instantiateViewControllerWithIdentifier("DetailViewControllerID") as! DetailViewController 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /ViperWeather/DetailListDetailContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListDetailContainer.swift 3 | // ViperWeather 4 | // 5 | // Created Dima on 16/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | class DetailListDetailContainer: AssemblyType { 16 | 17 | func assemble(container: Container) { 18 | container.registerForStoryboard(DetailListDetailViewController.self) { (r, c) -> () in 19 | container.register(DetailListDetailPresenterProtocol.self) { [weak c] r in 20 | guard let c = c else { fatalError("ViewController is nil") } 21 | 22 | let interface = c 23 | let interactor = r.resolve(DetailListDetailInteractorInputProtocol.self)! 24 | let router = r.resolve(DetailListDetailRouterInputProtocol.self)! 25 | 26 | let presenter = DetailListDetailPresenter(interface: interface, interactor: interactor, router: router) 27 | interactor.presenter = presenter 28 | 29 | return presenter 30 | } 31 | c.presenter = r.resolve(DetailListDetailPresenterProtocol.self) 32 | } 33 | 34 | container.register(DetailListDetailInteractorInputProtocol.self) { r in 35 | let interactor = DetailListDetailInteractor() 36 | let dataManager = r.resolve(DetailListDetailDataManagerInputProtocol.self)! 37 | interactor.dataManager = dataManager 38 | dataManager.interactor = interactor 39 | return interactor 40 | } 41 | 42 | container.register(DetailListDetailRouterInputProtocol.self) { (r) in 43 | let router = DetailListDetailRouter() 44 | router.childAssembler = r.resolve(ChildAssembler.self)! 45 | return router 46 | } 47 | 48 | container.register(DetailListDetailDataManagerInputProtocol.self) { (r) in 49 | let dataManager = DetailListDetailDataManager() 50 | return dataManager 51 | } 52 | 53 | 54 | container.register(ChildAssembler.self) { r in 55 | let parentAssembler = r.resolve(DetailListDetailAssembler.self)! 56 | return ChildAssembler(parentAssembler: parentAssembler) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ViperWeather/DetailListContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListContainer.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | class DetailListContainer: AssemblyType { 16 | 17 | func assemble(container: Container) { 18 | container.registerForStoryboard(DetailListViewController.self) { (r, c) -> () in 19 | container.register(DetailListPresenterProtocol.self) { [weak c] r in 20 | guard let c = c else { fatalError("Contoller is nil") } 21 | 22 | let interface = c 23 | let interactor = r.resolve(DetailListInteractorInputProtocol.self)! 24 | let router = r.resolve(DetailListRouterInputProtocol.self)! 25 | 26 | let presenter = DetailListPresenter(interface: interface, interactor: interactor, router: router) 27 | interactor.presenter = presenter 28 | 29 | return presenter 30 | } 31 | c.presenter = r.resolve(DetailListPresenterProtocol.self) 32 | } 33 | 34 | container.register(DetailListInteractorInputProtocol.self) { r in 35 | let interactor = DetailListInteractor() 36 | interactor.dataManager = r.resolve(DetailListDataManagerInputProtocol.self)! 37 | return interactor 38 | } 39 | container.register(DetailListDataManagerOutputProtocol.self) { r in 40 | r.resolve(DetailListInteractorInputProtocol.self) as! DetailListDataManagerOutputProtocol 41 | } 42 | 43 | container.register(DetailListRouterInputProtocol.self) { (r) in 44 | let router = DetailListRouter() 45 | router.detailListDetailAssembler = r.resolve(DetailListDetailAssembler.self)! 46 | return router 47 | } 48 | 49 | container.register(DetailListDataManagerInputProtocol.self) { (r) in 50 | let dataManager = DetailListDataManager() 51 | return dataManager 52 | } 53 | 54 | container.register(DetailListDetailAssembler.self) { r in 55 | let parentAssembler = r.resolve(DetailListAssembler.self)! 56 | return DetailListDetailAssembler(parentAssembler: parentAssembler) 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ViperWeather/ListContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListContainer.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import Swinject 11 | 12 | 13 | class ListContainer: AssemblyType { 14 | 15 | func assemble(container: Container) { 16 | container.registerForStoryboard(ListTableViewController.self) { (r, c) -> () in 17 | container.register(ListPresenterProtocol.self) { [weak c] r in 18 | guard let c = c else { fatalError("Contoller is nil") } 19 | 20 | let interface = c 21 | let interactor = r.resolve(ListInteractorInputProtocol.self)! 22 | let router = r.resolve(ListRouterInputProtocol.self)! 23 | 24 | let presenter = ListPresenter(interface: interface, interactor: interactor, router: router) 25 | interactor.presenter = presenter 26 | 27 | return presenter 28 | } 29 | c.presenter = r.resolve(ListPresenterProtocol.self) 30 | } 31 | 32 | container.register(ListInteractorInputProtocol.self) { r in 33 | let interactor = ListInteractor() 34 | let dataManager = r.resolve(ListDataManagerInputProtocol.self)! 35 | interactor.dataManager = dataManager 36 | dataManager.interactor = interactor 37 | return interactor 38 | } 39 | 40 | container.register(ListRouterInputProtocol.self) { (r) in 41 | let router = ListRouter() 42 | router.addAssembler = r.resolve(AddAssembler.self)! 43 | router.detailContainerAssembler = r.resolve(DetailContainerAssembler.self)! 44 | return router 45 | } 46 | 47 | container.register(ListDataManagerInputProtocol.self) { (r) in 48 | let dataManager = ListDataManager() 49 | return dataManager 50 | } 51 | 52 | container.register(AddAssembler.self) { r in 53 | let parentAssembler = r.resolve(ListAssembler.self)! 54 | return AddAssembler(parentAssembler: parentAssembler) 55 | } 56 | container.register(DetailContainerAssembler.self) { r in 57 | let parentAssembler = r.resolve(ListAssembler.self)! 58 | return DetailContainerAssembler(parentAssembler: parentAssembler) 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ViperWeather/AddDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddDataManager.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitri Utmanov on 01/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | import Alamofire 12 | import SwiftFetchedResultsController 13 | 14 | 15 | protocol AddDataManagerInputProtocol: class { 16 | 17 | weak var interactor: AddDataManagerOutputProtocol! { get set } 18 | 19 | func fetchCitiesWithName(name: String, callback: ([City]) -> ()) 20 | func saveCityToPersistentStore(city: City) 21 | } 22 | 23 | protocol AddDataManagerOutputProtocol: class { 24 | 25 | var dataManager: AddDataManagerInputProtocol! { get set } 26 | } 27 | 28 | 29 | class AddDataManager { 30 | 31 | weak var interactor: AddDataManagerOutputProtocol! 32 | } 33 | 34 | extension AddDataManager: AddDataManagerInputProtocol { 35 | 36 | func fetchCitiesWithName(name: String, callback: ([City]) -> ()) { 37 | let method = Alamofire.Method.GET 38 | let url = "https://maps.googleapis.com/maps/api/place/autocomplete/json" 39 | let parameters = ["input": "\(name)", "types": "(cities)", "key": googleMapKey] 40 | 41 | Alamofire.Manager.sharedInstance.request(method, url, parameters: parameters, encoding: ParameterEncoding.URL, headers: nil).responseJSON { (response) -> Void in 42 | switch response.result { 43 | case .Success(let JSON): 44 | let predictions = JSON["predictions"] as! [[String: AnyObject]] 45 | var cities: [City] = [] 46 | for prediction in predictions { 47 | let city = City(title: prediction["description"] as! String, ID: prediction["id"] as! String, placeID: prediction["place_id"] as! String, lat: 0.0, lng: 0.0) 48 | cities.append(city) 49 | } 50 | callback(cities) 51 | 52 | case .Failure(let error): 53 | print(error) 54 | callback([]) 55 | } 56 | } 57 | } 58 | 59 | func saveCityToPersistentStore(city: City) { 60 | let realm = try! Realm() 61 | realm.beginWrite() 62 | 63 | let cityEntity = CityEntity() 64 | cityEntity.title = city.title 65 | cityEntity.ID = city.ID 66 | cityEntity.placeID = city.placeID 67 | 68 | realm.addWithNotification(cityEntity, update: false) 69 | 70 | try! realm.commitWrite() 71 | } 72 | } -------------------------------------------------------------------------------- /ViperWeather/AddPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddPresenter.swift 3 | // Architecture 4 | // 5 | // Created Dmitri Utmanov on 19/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated by Swift-Viper templates. Find latest version at https://github.com/Nikita2k/SwiftViper 9 | // 10 | 11 | import UIKit 12 | 13 | 14 | protocol AddViewControllerDelegate: class { 15 | 16 | func addViewControllerDidSelectCity(city: City) 17 | } 18 | 19 | protocol AddPresenterProtocol: class { 20 | 21 | weak var delegate: AddViewControllerDelegate? { get set } 22 | 23 | func cancel() 24 | func getCitiesWithName(name: String?) 25 | func selectCity(city: City) 26 | func selectAndSaveCity(city: City) 27 | } 28 | 29 | protocol AddInterfaceProtocol: class { 30 | 31 | var presenter: AddPresenterProtocol! { get set } 32 | 33 | func showEmpty() 34 | func showCities(cities: [City]) 35 | } 36 | 37 | class AddPresenter { 38 | 39 | weak private var interface: AddInterfaceProtocol! 40 | private let interactor: AddInteractorInputProtocol 41 | private let router: AddRouterInputProtocol 42 | 43 | weak var delegate: AddViewControllerDelegate? 44 | 45 | 46 | init(interface: AddInterfaceProtocol, interactor: AddInteractorInputProtocol, router: AddRouterInputProtocol) { 47 | self.interface = interface 48 | self.interactor = interactor 49 | self.router = router 50 | } 51 | } 52 | 53 | extension AddPresenter: AddPresenterProtocol { 54 | 55 | func cancel() { 56 | self.router.closeAddViewController(viewController: self.interface as! UIViewController) 57 | } 58 | 59 | func getCitiesWithName(name: String?) { 60 | guard let name = name else { 61 | self.interface.showEmpty() 62 | return 63 | } 64 | guard name.isEmpty != true else { 65 | self.interface.showEmpty() 66 | return 67 | } 68 | self.interactor.getCitiesWithName(name) 69 | } 70 | 71 | func selectAndSaveCity(city: City) { 72 | self.interactor.saveCity(city) 73 | self.router.closeAddViewController(viewController: interface as! UIViewController) 74 | } 75 | 76 | func selectCity(city: City) { 77 | self.delegate?.addViewControllerDidSelectCity(city) 78 | self.router.closeAddViewController(viewController: interface as! UIViewController) 79 | } 80 | } 81 | 82 | extension AddPresenter: AddInteractorOutputProtocol { 83 | 84 | func foundCities(cities: [City]) { 85 | self.interface.showCities(cities) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /ViperWeather/DetailListAssembler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListAssembler.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | class DetailListAssembler: Assembler { 16 | 17 | required init(parentAssembler: Assembler) { 18 | try! super.init(assemblies: [DetailListContainer()], parentAssembler: parentAssembler) 19 | } 20 | } 21 | 22 | extension DetailListAssembler { 23 | 24 | func presentDetailListViewController(fromViewController fromViewController: UIViewController, city: City) { 25 | 26 | let viewController = self.viewController() 27 | viewController.city = city 28 | 29 | fromViewController.navigationController!.pushViewController(viewController, animated: true) 30 | } 31 | 32 | func presentDetailListViewController(fromViewController fromViewController: UIViewController, inView view: UIView, city: City) { 33 | let childViewController = self.viewController() 34 | childViewController.city = city 35 | 36 | fromViewController.addChildViewController(childViewController) 37 | childViewController.view.frame = view.frame 38 | view.addSubview(childViewController.view) 39 | 40 | childViewController.view.translatesAutoresizingMaskIntoConstraints = false 41 | 42 | let childView = childViewController.view 43 | let views: [String : AnyObject] = ["childView": childView] 44 | 45 | var childViewLayoutConstraint: [NSLayoutConstraint] = [] 46 | childViewLayoutConstraint += NSLayoutConstraint.constraintsWithVisualFormat("|-(0)-[childView]-(0)-|", options: [], metrics: nil, views: views) 47 | childViewLayoutConstraint += NSLayoutConstraint.constraintsWithVisualFormat("V:|-(0)-[childView]-(0)-|", options: [], metrics: nil, views: views) 48 | NSLayoutConstraint.activateConstraints(childViewLayoutConstraint) 49 | 50 | childViewController.didMoveToParentViewController(fromViewController) 51 | } 52 | 53 | func storyboard() -> SwinjectStoryboard { 54 | let storyboardName = "Detail" 55 | let bundle = NSBundle.mainBundle() 56 | return SwinjectStoryboard.create(name: storyboardName, bundle: bundle, container: resolver) 57 | } 58 | 59 | func viewController() -> DetailListViewController { 60 | return storyboard().instantiateViewControllerWithIdentifier("DetailListViewControllerID") as! DetailListViewController 61 | } 62 | 63 | } -------------------------------------------------------------------------------- /ViperWeather/DetailContainerContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailContainerContainer.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | import Swinject 13 | 14 | 15 | class DetailContainerContainer: AssemblyType { 16 | 17 | func assemble(container: Container) { 18 | container.registerForStoryboard(DetailContainerViewController.self) { (r, c) -> () in 19 | container.register(DetailContainerPresenterProtocol.self) { [weak c] r in 20 | guard let c = c else { fatalError("Contoller is nil") } 21 | 22 | let interface = c 23 | let interactor = r.resolve(DetailContainerInteractorInputProtocol.self)! 24 | let router = r.resolve(DetailContainerRouterInputProtocol.self)! 25 | 26 | let presenter = DetailContainerPresenter(interface: interface, interactor: interactor, router: router) 27 | interactor.presenter = presenter 28 | 29 | return presenter 30 | } 31 | c.presenter = r.resolve(DetailContainerPresenterProtocol.self) 32 | } 33 | 34 | container.register(DetailContainerInteractorInputProtocol.self) { r in 35 | let interactor = DetailContainerInteractor() 36 | let dataManager = r.resolve(DetailContainerDataManagerInputProtocol.self)! 37 | interactor.dataManager = dataManager 38 | dataManager.interactor = interactor 39 | return interactor 40 | } 41 | 42 | container.register(DetailContainerRouterInputProtocol.self) { (r) in 43 | let router = DetailContainerRouter() 44 | router.detailAssembler = r.resolve(DetailAssembler.self)! 45 | router.detailListAssembler = r.resolve(DetailListAssembler.self)! 46 | return router 47 | } 48 | 49 | container.register(DetailContainerDataManagerInputProtocol.self) { (r) in 50 | let dataManager = DetailContainerDataManager() 51 | return dataManager 52 | } 53 | 54 | container.register(DetailAssembler.self) { r in 55 | let parentAssembler = r.resolve(DetailContainerAssembler.self)! 56 | return DetailAssembler(parentAssembler: parentAssembler) 57 | } 58 | container.register(DetailListAssembler.self) { r in 59 | let parentAssembler = r.resolve(DetailContainerAssembler.self)! 60 | return DetailListAssembler(parentAssembler: parentAssembler) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /ViperWeather/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en_US 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 7 23 | Fabric 24 | 25 | APIKey 26 | ff2a57e35b77effb71f8b54dbecfdf36754b0962 27 | Kits 28 | 29 | 30 | KitInfo 31 | 32 | KitName 33 | Crashlytics 34 | 35 | 36 | 37 | LSApplicationCategoryType 38 | 39 | LSRequiresIPhoneOS 40 | 41 | NSAppTransportSecurity 42 | 43 | NSExceptionDomains 44 | 45 | api.openweathermap.org 46 | 47 | NSTemporaryExceptionAllowsInsecureHTTPLoads 48 | 49 | 50 | openweathermap.org 51 | 52 | NSTemporaryExceptionAllowsInsecureHTTPLoads 53 | 54 | 55 | 56 | 57 | UILaunchStoryboardName 58 | LaunchScreen 59 | UIMainStoryboardFile 60 | Main 61 | UIRequiredDeviceCapabilities 62 | 63 | armv7 64 | 65 | UIStatusBarStyle 66 | UIStatusBarStyleLightContent 67 | UIStatusBarTintParameters 68 | 69 | UINavigationBar 70 | 71 | Style 72 | UIBarStyleDefault 73 | Translucent 74 | 75 | 76 | 77 | UISupportedInterfaceOrientations 78 | 79 | UIInterfaceOrientationPortrait 80 | UIInterfaceOrientationLandscapeLeft 81 | UIInterfaceOrientationLandscapeRight 82 | 83 | UISupportedInterfaceOrientations~ipad 84 | 85 | UIInterfaceOrientationPortrait 86 | UIInterfaceOrientationPortraitUpsideDown 87 | UIInterfaceOrientationLandscapeLeft 88 | UIInterfaceOrientationLandscapeRight 89 | 90 | UIViewControllerBasedStatusBarAppearance 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /ViperWeather/List.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /ViperWeather/RootViewController.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ViperWeather/MaterialColor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MaterialColor.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitri Utmanov on 02/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class MaterialColor: UIColor { 12 | 13 | 14 | private class func colorWithHex(hex: Int, alpha: CGFloat = 1.0) -> UIColor { 15 | let red = CGFloat((hex & 0xFF0000) >> 16) / 255.0 16 | let green = CGFloat((hex & 0xFF00) >> 8) / 255.0 17 | let blue = CGFloat((hex & 0xFF)) / 255.0 18 | return UIColor(red:red, green:green, blue:blue, alpha:alpha) 19 | } 20 | 21 | 22 | 23 | // Some convenience methods to create colors. These colors will be as calibrated as possible. 24 | // These colors are cached. 25 | override class func darkGrayColor() -> UIColor { 26 | return MaterialColor.colorWithHex(0x424242) 27 | } 28 | override class func lightGrayColor() -> UIColor { 29 | return MaterialColor.colorWithHex(0xBDBDBD) 30 | } 31 | override class func blackColor() -> UIColor { 32 | return MaterialColor.colorWithHex(0x212121) 33 | } 34 | override class func grayColor() -> UIColor { 35 | return MaterialColor.colorWithHex(0x9E9E9E) 36 | } 37 | override class func redColor() -> UIColor { 38 | return MaterialColor.colorWithHex(0xF44336) 39 | } 40 | override class func greenColor() -> UIColor { 41 | return MaterialColor.colorWithHex(0x4CAF50) 42 | } 43 | override class func blueColor() -> UIColor { 44 | return MaterialColor.colorWithHex(0x2196F3) 45 | } 46 | override class func cyanColor() -> UIColor { 47 | return MaterialColor.colorWithHex(0x00BCD4) 48 | } 49 | override class func yellowColor() -> UIColor { 50 | return MaterialColor.colorWithHex(0xFFEB3B) 51 | } 52 | override class func magentaColor() -> UIColor { 53 | return MaterialColor.colorWithHex(0xF06292) 54 | } 55 | override class func orangeColor() -> UIColor { 56 | return MaterialColor.colorWithHex(0xFF9800) 57 | } 58 | override class func purpleColor() -> UIColor { 59 | return MaterialColor.colorWithHex(0x9C27B0) 60 | } 61 | override class func brownColor() -> UIColor { 62 | return MaterialColor.colorWithHex(0x795548) 63 | } 64 | 65 | class func deepPurpleColor() -> UIColor { 66 | return MaterialColor.colorWithHex(0x673AB7) 67 | } 68 | class func pinkColor() -> UIColor { 69 | return MaterialColor.colorWithHex(0xE91E63) 70 | } 71 | class func indigoColor() -> UIColor { 72 | return MaterialColor.colorWithHex(0x3F51B5) 73 | } 74 | class func lightBlueColor() -> UIColor { 75 | return MaterialColor.colorWithHex(0x03A9F4) 76 | } 77 | class func tealColor() -> UIColor { 78 | return MaterialColor.colorWithHex(0x009688) 79 | } 80 | class func lightGreenColor() -> UIColor { 81 | return MaterialColor.colorWithHex(0x8BC34A) 82 | } 83 | class func limeColor() -> UIColor { 84 | return MaterialColor.colorWithHex(0xCDDC39) 85 | } 86 | class func amberColor() -> UIColor { 87 | return MaterialColor.colorWithHex(0xFFC107) 88 | } 89 | class func deepOrangeColor() -> UIColor { 90 | return MaterialColor.colorWithHex(0xFF5722) 91 | } 92 | class func blueGrayColor() -> UIColor { 93 | return MaterialColor.colorWithHex(0x607D8B) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /ViperWeather/DetailViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailViewController.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitri Utmanov on 02/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class DetailViewController: UIViewController { 12 | 13 | // MARK: - VIPER Properties 14 | var presenter: DetailPresenterProtocol! 15 | 16 | var city: City? 17 | 18 | @IBOutlet weak var weatherLabel: UILabel! 19 | @IBOutlet weak var weatherIconImageView: UIImageView! 20 | @IBOutlet weak var weatherContainerView: UIView! 21 | 22 | @IBOutlet weak var activityIndicatorView: UIActivityIndicatorView! 23 | 24 | 25 | override var nibName: String? { 26 | get { 27 | let classString = String(self.dynamicType) 28 | return classString 29 | } 30 | } 31 | override var nibBundle: NSBundle? { 32 | get { 33 | return NSBundle.mainBundle() 34 | } 35 | } 36 | 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | // Do any additional setup after loading the view. 42 | activityIndicatorView.color = MaterialColor.cyanColor() 43 | } 44 | 45 | override func viewWillAppear(animated: Bool) { 46 | super.viewWillAppear(animated) 47 | 48 | if let city = city { 49 | if city.isLocationEnable() == false { 50 | self.weatherContainerView.hidden = true 51 | self.activityIndicatorView.hidden = false 52 | self.presenter.getDetailCity(city) 53 | } else { 54 | self.weatherContainerView.hidden = true 55 | self.activityIndicatorView.hidden = false 56 | self.presenter.getWeatherForCity(city) 57 | } 58 | } 59 | } 60 | override func didReceiveMemoryWarning() { 61 | super.didReceiveMemoryWarning() 62 | // Dispose of any resources that can be recreated. 63 | } 64 | } 65 | 66 | extension DetailViewController: DetailInterfaceProtocol { 67 | 68 | func showEmpty() { 69 | self.city = nil 70 | } 71 | 72 | func showCity(city: City) { 73 | self.city = city 74 | 75 | self.weatherContainerView.hidden = true 76 | self.activityIndicatorView.hidden = false 77 | 78 | self.presenter.getWeatherForCity(city) 79 | } 80 | 81 | func showWeatherForCity(weather: [Weather], city: City) { 82 | self.weatherContainerView.hidden = false 83 | self.activityIndicatorView.hidden = true 84 | 85 | if weather.count > 0 { 86 | let currentWeather = weather[0] 87 | self.weatherLabel.text = currentWeather.tempString 88 | 89 | let request = NSURLRequest(URL: NSURL(string: "http://openweathermap.org/img/w/\(currentWeather.icon).png")!) 90 | NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: { [weak self] (response, data, error) -> Void in 91 | if error == nil { 92 | guard let data = data else { return } 93 | let image = UIImage(data: data) 94 | self?.weatherIconImageView.image = image 95 | } else { 96 | // Handle error 97 | } 98 | }) 99 | } 100 | self.city = city 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ViperWeather/DetailListViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListViewController.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import UIKit 12 | 13 | 14 | class DetailListViewController: UITableViewController { 15 | 16 | let kWeatherTableViewCellReuseIdentifier = "WeatherTableViewCellReuseIdentifier" 17 | 18 | 19 | // MARK: - VIPER Properties 20 | var presenter: DetailListPresenterProtocol! 21 | 22 | var city: City! 23 | var weatherList: [Weather] = [] 24 | 25 | 26 | override var nibName: String? { 27 | get { 28 | let classString = String(self.dynamicType) 29 | return classString 30 | } 31 | } 32 | override var nibBundle: NSBundle? { 33 | get { 34 | return NSBundle.mainBundle() 35 | } 36 | } 37 | 38 | 39 | override func viewDidLoad() { 40 | super.viewDidLoad() 41 | 42 | tableView.registerNib(UINib(nibName: "WeatherTableViewCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: kWeatherTableViewCellReuseIdentifier) 43 | tableView.estimatedRowHeight = UITableViewAutomaticDimension 44 | tableView.rowHeight = UITableViewAutomaticDimension 45 | // Do any additional setup after loading the view. 46 | } 47 | 48 | override func viewWillAppear(animated: Bool) { 49 | super.viewWillAppear(animated) 50 | 51 | if let city = city { 52 | if city.isLocationEnable() == false { 53 | self.presenter.getDetailCity(city) 54 | } else { 55 | self.presenter.getWeatherForCity(city) 56 | } 57 | } 58 | } 59 | 60 | override func didReceiveMemoryWarning() { 61 | super.didReceiveMemoryWarning() 62 | // Dispose of any resources that can be recreated. 63 | } 64 | } 65 | 66 | extension DetailListViewController { 67 | 68 | override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 69 | return UITableViewAutomaticDimension 70 | } 71 | 72 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 73 | return UITableViewAutomaticDimension 74 | } 75 | 76 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 77 | return weatherList.count 78 | } 79 | 80 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 81 | let cell = tableView.dequeueReusableCellWithIdentifier(kWeatherTableViewCellReuseIdentifier, forIndexPath: indexPath) as! WeatherTableViewCell 82 | cell.weather = weatherList[indexPath.row] 83 | return cell 84 | } 85 | 86 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 87 | /* 88 | 89 | // Uncomment this line to find out how Template does work 90 | let weather = weatherList[indexPath.row] 91 | self.presenter.presentDetailListDetailViewController(weather.tempString) 92 | 93 | */ 94 | } 95 | } 96 | 97 | extension DetailListViewController: DetailListInterfaceProtocol { 98 | 99 | func showEmpty() { 100 | self.city = nil 101 | self.weatherList.removeAll() 102 | } 103 | 104 | func showCity(city: City) { 105 | self.city = city 106 | 107 | self.presenter.getWeatherForCity(city) 108 | } 109 | 110 | func showWeatherForCity(weather: [Weather], city: City) { 111 | self.city = city 112 | self.weatherList = weather 113 | 114 | self.tableView.reloadData() 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /ViperWeather/DetailDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailDataManager.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitri Utmanov on 02/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RealmSwift 11 | import Alamofire 12 | import SwiftFetchedResultsController 13 | 14 | 15 | protocol DetailDataManagerInputProtocol: class { 16 | 17 | weak var interactor: DetailDataManagerOutputProtocol! { get set } 18 | 19 | func getDetailCity(city: City, callback: (City) -> ()) 20 | func getWeatherForCity(city: City, callback: ([Weather]) -> ()) 21 | func updateCityInPersistentStore(city: City) 22 | } 23 | 24 | protocol DetailDataManagerOutputProtocol: class { 25 | 26 | var dataManager: DetailDataManagerInputProtocol! { get set } 27 | } 28 | 29 | 30 | class DetailDataManager { 31 | 32 | weak var interactor: DetailDataManagerOutputProtocol! 33 | } 34 | 35 | extension DetailDataManager: DetailDataManagerInputProtocol { 36 | 37 | func getDetailCity(city: City, callback: (City) -> ()) { 38 | let method = Alamofire.Method.GET 39 | let url = "https://maps.googleapis.com/maps/api/place/details/json" 40 | let parameters = ["placeid": "\(city.placeID)", "key": googleMapKey] 41 | 42 | Alamofire.Manager.sharedInstance.request(method, url, parameters: parameters, encoding: ParameterEncoding.URL, headers: nil).responseJSON { (response) -> Void in 43 | switch response.result { 44 | case .Success(let JSON): 45 | let result = JSON["result"] as! [String: AnyObject] 46 | let geometry = result["geometry"] as! [String: AnyObject] 47 | let location = geometry["location"] as! [String: AnyObject] 48 | let lat = location["lat"] as! Double 49 | let lng = location["lng"] as! Double 50 | let city = City(title: city.title, ID: city.ID, placeID: city.placeID, lat: lat, lng: lng) 51 | callback(city) 52 | 53 | case .Failure(let error): 54 | print(error) 55 | callback(city) 56 | } 57 | } 58 | } 59 | 60 | func getWeatherForCity(city: City, callback: ([Weather]) -> ()) { 61 | let method = Alamofire.Method.GET 62 | let url = "http://api.openweathermap.org/data/2.5/forecast" 63 | let parameters: [String: AnyObject] = ["lat": city.lat, "lon": city.lng, "units": "metric", "cnt": 1, "APPID": openWeatherMapKey] 64 | 65 | Alamofire.Manager.sharedInstance.request(method, url, parameters: parameters, encoding: ParameterEncoding.URL, headers: nil).responseJSON { (response) -> Void in 66 | switch response.result { 67 | case .Success(let JSON): 68 | guard let list = JSON["list"] as? [[String: AnyObject]] else { 69 | callback([]) 70 | return 71 | } 72 | var weatherList: [Weather] = [] 73 | for weatherData in list { 74 | let weather = Weather(dt: weatherData["dt"] as! Double, temp: weatherData["main"]!["temp"] as! Double, pressure: weatherData["main"]!["pressure"] as! Double, icon: weatherData["weather"]![0]["icon"] as! String) 75 | weatherList.append(weather) 76 | } 77 | callback(weatherList) 78 | 79 | case .Failure(let error): 80 | print(error) 81 | callback([]) 82 | } 83 | } 84 | } 85 | 86 | func updateCityInPersistentStore(city: City) { 87 | let realm = try! Realm() 88 | 89 | realm.beginWrite() 90 | let predicate = NSPredicate(format: "ID = %@", argumentArray: [city.ID]) 91 | let cityEntities = realm.objects(CityEntity).filter(predicate) 92 | 93 | for cityEntity in cityEntities { 94 | cityEntity.lat = city.lat 95 | cityEntity.lng = city.lng 96 | } 97 | 98 | realm.addWithNotification(cityEntities, update: true) 99 | 100 | try! realm.commitWrite() 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ViperWeather/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RealmSwift 11 | 12 | 13 | @UIApplicationMain 14 | class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate { 15 | 16 | var window: UIWindow? 17 | let serviceLocatorAssembler = ServiceLocatorAssembler() 18 | 19 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 20 | // Override point for customization after application launch. 21 | self.configureAppearance() 22 | 23 | let config = Realm.Configuration(schemaVersion: 4, migrationBlock: { migration, oldSchemaVersion in 24 | // We haven’t migrated anything yet, so oldSchemaVersion == 0 25 | if (oldSchemaVersion < 1) { 26 | // Nothing to do! 27 | // Realm will automatically detect new properties and removed properties 28 | // And will update the schema on disk automatically 29 | } 30 | }) 31 | 32 | // Tell Realm to use this new configuration object for the default Realm 33 | Realm.Configuration.defaultConfiguration = config 34 | 35 | let rootAssembler = serviceLocatorAssembler.resolver.resolve(RootAssembler.self)! 36 | rootAssembler.presentRootViewController(fromViewController: window!.rootViewController!) 37 | 38 | return true 39 | } 40 | 41 | func applicationWillResignActive(application: UIApplication) { 42 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 43 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 44 | } 45 | 46 | func applicationDidEnterBackground(application: UIApplication) { 47 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 48 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 49 | } 50 | 51 | func applicationWillEnterForeground(application: UIApplication) { 52 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 53 | } 54 | 55 | func applicationDidBecomeActive(application: UIApplication) { 56 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 57 | } 58 | 59 | func applicationWillTerminate(application: UIApplication) { 60 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 61 | } 62 | 63 | 64 | func configureAppearance() { 65 | window?.backgroundColor = MaterialColor.blackColor() 66 | window?.tintColor = MaterialColor.lightBlueColor() 67 | 68 | UINavigationBar.appearance().barTintColor = MaterialColor.lightBlueColor() 69 | UINavigationBar.appearance().tintColor = MaterialColor.whiteColor() 70 | 71 | let titleTextAttributes: [String : AnyObject] = [NSForegroundColorAttributeName: MaterialColor.whiteColor()] 72 | UINavigationBar.appearance().titleTextAttributes = titleTextAttributes 73 | 74 | UISearchBar.appearance().barTintColor = MaterialColor.lightBlueColor() 75 | UISearchBar.appearance().tintColor = MaterialColor.whiteColor() 76 | if #available(iOS 9.0, *) { 77 | UITextField.appearanceWhenContainedInInstancesOfClasses([UISearchBar.self]).tintColor = MaterialColor.blackColor() 78 | } else { 79 | UITextField.appearanceWhenContainedWithin(UISearchBar.self).tintColor = MaterialColor.blackColor() 80 | } 81 | 82 | UITextField.appearance().keyboardAppearance = .Dark 83 | UITextView.appearance().keyboardAppearance = .Dark 84 | } 85 | 86 | } 87 | 88 | -------------------------------------------------------------------------------- /ViperWeather/DetailListDataManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailListDataManager.swift 3 | // ViperWeather 4 | // 5 | // Created Dmitri Utmanov on 03/03/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | // Generated via Swift-Viper templates by https://github.com/cooler333 9 | // 10 | 11 | import Foundation 12 | import RealmSwift 13 | import Alamofire 14 | import SwiftFetchedResultsController 15 | 16 | 17 | protocol DetailListDataManagerInputProtocol: class { 18 | 19 | weak var interactor: DetailListDataManagerOutputProtocol! { get set } 20 | 21 | func getDetailCity(city: City, callback: (City) -> ()) 22 | func getWeatherForCity(city: City, callback: ([Weather]) -> ()) 23 | func updateCityInPersistentStore(city: City) 24 | } 25 | 26 | protocol DetailListDataManagerOutputProtocol: class { 27 | 28 | var dataManager: DetailListDataManagerInputProtocol! { get set } 29 | } 30 | 31 | 32 | class DetailListDataManager { 33 | 34 | weak var interactor: DetailListDataManagerOutputProtocol! 35 | 36 | } 37 | 38 | extension DetailListDataManager: DetailListDataManagerInputProtocol { 39 | 40 | func getDetailCity(city: City, callback: (City) -> ()) { 41 | let method = Alamofire.Method.GET 42 | let url = "https://maps.googleapis.com/maps/api/place/details/json" 43 | let parameters = ["placeid": "\(city.placeID)", "key": googleMapKey] 44 | 45 | Alamofire.Manager.sharedInstance.request(method, url, parameters: parameters, encoding: ParameterEncoding.URL, headers: nil).responseJSON { (response) -> Void in 46 | switch response.result { 47 | case .Success(let JSON): 48 | let result = JSON["result"] as! [String: AnyObject] 49 | let geometry = result["geometry"] as! [String: AnyObject] 50 | let location = geometry["location"] as! [String: AnyObject] 51 | let lat = location["lat"] as! Double 52 | let lng = location["lng"] as! Double 53 | let city = City(title: city.title, ID: city.ID, placeID: city.placeID, lat: lat, lng: lng) 54 | callback(city) 55 | 56 | case .Failure(let error): 57 | print(error) 58 | callback(city) 59 | } 60 | } 61 | } 62 | 63 | func getWeatherForCity(city: City, callback: ([Weather]) -> ()) { 64 | let delay = 0.5 * Double(NSEC_PER_SEC) 65 | let time = dispatch_time(DISPATCH_TIME_NOW, Int64(delay)) 66 | dispatch_after(time, dispatch_get_main_queue()) { 67 | // Workaround error 429: Too many requests 68 | 69 | let method = Alamofire.Method.GET 70 | let url = "http://api.openweathermap.org/data/2.5/forecast" 71 | let parameters: [String: AnyObject] = ["lat": city.lat, "lon": city.lng, "units": "metric", "APPID": openWeatherMapKey] 72 | 73 | Alamofire.Manager.sharedInstance.request(method, url, parameters: parameters, encoding: ParameterEncoding.URL, headers: nil).responseJSON { (response) -> Void in 74 | switch response.result { 75 | case .Success(let JSON): 76 | guard let list = JSON["list"] as? [[String: AnyObject]] else { 77 | callback([]) 78 | return 79 | } 80 | var weatherList: [Weather] = [] 81 | for weatherData in list { 82 | let weather = Weather(dt: weatherData["dt"] as! Double, temp: weatherData["main"]!["temp"] as! Double, pressure: weatherData["main"]!["pressure"] as! Double, icon: weatherData["weather"]![0]["icon"] as! String) 83 | weatherList.append(weather) 84 | } 85 | callback(weatherList) 86 | 87 | case .Failure(let error): 88 | print(error) 89 | callback([]) 90 | } 91 | } 92 | } 93 | } 94 | 95 | func updateCityInPersistentStore(city: City) { 96 | let realm = try! Realm() 97 | 98 | realm.beginWrite() 99 | let predicate = NSPredicate(format: "ID = %@", argumentArray: [city.ID]) 100 | let cityEntities = realm.objects(CityEntity).filter(predicate) 101 | 102 | for cityEntity in cityEntities { 103 | cityEntity.lat = city.lat 104 | cityEntity.lng = city.lng 105 | } 106 | 107 | realm.addWithNotification(cityEntities, update: true) 108 | 109 | try! realm.commitWrite() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /ViperWeather/AddTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AddTableViewController.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | class AddTableViewController: UITableViewController { 13 | 14 | let kCityTableViewCellReuseIdentifier = "CityTableViewCellReuseIdentifier" 15 | 16 | 17 | // MARK: VIPER Properties 18 | var presenter: AddPresenterProtocol! 19 | 20 | let searchController = UISearchController(searchResultsController: nil) 21 | 22 | var cities: [City] = [] 23 | 24 | 25 | override var nibName: String? { 26 | get { 27 | let classString = String(self.dynamicType) 28 | return classString 29 | } 30 | } 31 | override var nibBundle: NSBundle? { 32 | get { 33 | return NSBundle.mainBundle() 34 | } 35 | } 36 | 37 | 38 | override func viewDidLoad() { 39 | super.viewDidLoad() 40 | 41 | tableView.registerNib(UINib(nibName: "CityTableViewCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: kCityTableViewCellReuseIdentifier) 42 | 43 | searchController.delegate = self 44 | searchController.searchResultsUpdater = self 45 | searchController.dimsBackgroundDuringPresentation = false 46 | searchController.searchBar.delegate = self 47 | 48 | tableView.tableHeaderView = searchController.searchBar 49 | 50 | definesPresentationContext = true 51 | 52 | searchController.searchBar.sizeToFit() 53 | 54 | searchController.active = true 55 | } 56 | 57 | // MARK: - Table view data source 58 | 59 | override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 60 | return UITableViewAutomaticDimension 61 | } 62 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 63 | return UITableViewAutomaticDimension 64 | } 65 | 66 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 67 | return 1 68 | } 69 | 70 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 71 | return cities.count 72 | } 73 | 74 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 75 | let cell = tableView.dequeueReusableCellWithIdentifier(kCityTableViewCellReuseIdentifier, forIndexPath: indexPath) as! CityTableViewCell 76 | cell.city = cities[indexPath.row] 77 | return cell 78 | } 79 | 80 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 81 | let city = cities[indexPath.row] 82 | 83 | let alertController = UIAlertController(title: city.title, message: "Save to storage and Add to list or Add only", preferredStyle: UIAlertControllerStyle.Alert) 84 | 85 | let saveAction = UIAlertAction(title: "Save to storage", style: UIAlertActionStyle.Default) { [weak self] (action) -> Void in 86 | self?.presenter.selectAndSaveCity(city) 87 | } 88 | alertController.addAction(saveAction) 89 | 90 | let addAction = UIAlertAction(title: "Add only", style: UIAlertActionStyle.Default) { [weak self] (action) -> Void in 91 | self?.presenter.selectCity(city) 92 | } 93 | alertController.addAction(addAction) 94 | 95 | let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil) 96 | alertController.addAction(cancelAction) 97 | 98 | self.presentViewController(alertController, animated: true, completion: nil) 99 | } 100 | 101 | @IBAction func cancel(sender: AnyObject) { 102 | self.presenter.cancel() 103 | } 104 | } 105 | 106 | extension AddTableViewController: AddInterfaceProtocol { 107 | 108 | func showEmpty() { 109 | self.cities.removeAll() 110 | self.tableView.reloadData() 111 | } 112 | 113 | func showCities(cities: [City]) { 114 | self.cities.removeAll() 115 | self.cities = cities 116 | self.tableView.reloadData() 117 | } 118 | } 119 | 120 | extension AddTableViewController: UISearchControllerDelegate { 121 | 122 | func didPresentSearchController(searchController: UISearchController) { 123 | searchController.searchBar.becomeFirstResponder() 124 | } 125 | } 126 | 127 | extension AddTableViewController: UISearchBarDelegate { 128 | 129 | func searchBarCancelButtonClicked(searchBar: UISearchBar) { 130 | self.presenter.cancel() 131 | } 132 | } 133 | 134 | extension AddTableViewController: UISearchResultsUpdating { 135 | 136 | func updateSearchResultsForSearchController(searchController: UISearchController) { 137 | self.presenter.getCitiesWithName(searchController.searchBar.text!) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /ViperWeather/WeatherTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 36 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /ViperWeather/DetailViewController.xib: -------------------------------------------------------------------------------- 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 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /ViperWeather/Detail.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /ViperWeather/CityTableViewCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 30 | 36 | 42 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ViperWeather/ListTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ListTableViewController.swift 3 | // ViperWeather 4 | // 5 | // Created by Dmitriy Utmanov on 29/02/16. 6 | // Copyright © 2016 Dmitriy Utmanov. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RealmSwift 11 | import SwiftFetchedResultsController 12 | 13 | 14 | class ListTableViewController: UITableViewController { 15 | 16 | let kCityTableViewCellReuseIdentifier = "CityTableViewCellReuseIdentifier" 17 | 18 | 19 | // MARK: - VIPER Properties 20 | var presenter: ListPresenterProtocol! 21 | 22 | 23 | var cityFetchedResultsController: FetchedResultsController! 24 | var cities: [City] = [] 25 | 26 | override var nibName: String? { 27 | get { 28 | let classString = String(self.dynamicType) 29 | return classString 30 | } 31 | } 32 | override var nibBundle: NSBundle? { 33 | get { 34 | return NSBundle.mainBundle() 35 | } 36 | } 37 | 38 | 39 | override func viewDidLoad() { 40 | super.viewDidLoad() 41 | 42 | tableView.registerNib(UINib(nibName: "CityTableViewCell", bundle: NSBundle.mainBundle()), forCellReuseIdentifier: kCityTableViewCellReuseIdentifier) 43 | tableView.estimatedRowHeight = UITableViewAutomaticDimension 44 | tableView.rowHeight = UITableViewAutomaticDimension 45 | 46 | self.navigationItem.rightBarButtonItems!.append(self.editButtonItem()) 47 | 48 | let realm = try! Realm() 49 | let predicate = NSPredicate(format: "placeID != %@", "0") 50 | let fetchRequest = FetchRequest(realm: realm, predicate: predicate) 51 | let sortDescriptor = SortDescriptor(property: "title", ascending: true) 52 | fetchRequest.sortDescriptors = [sortDescriptor] 53 | self.cityFetchedResultsController = FetchedResultsController(fetchRequest: fetchRequest, sectionNameKeyPath: nil, cacheName: nil) 54 | self.cityFetchedResultsController!.delegate = self 55 | self.cityFetchedResultsController!.performFetch() 56 | } 57 | 58 | override func didReceiveMemoryWarning() { 59 | super.didReceiveMemoryWarning() 60 | // Dispose of any resources that can be recreated. 61 | } 62 | 63 | // MARK: - Table view data source 64 | 65 | override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 66 | return UITableViewAutomaticDimension 67 | } 68 | 69 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 70 | return UITableViewAutomaticDimension 71 | } 72 | 73 | override func numberOfSectionsInTableView(tableView: UITableView) -> Int { 74 | return 2 75 | } 76 | 77 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 78 | switch section { 79 | case 0: 80 | return cityFetchedResultsController.numberOfRowsForSectionIndex(section) 81 | case 1: 82 | return cities.count 83 | default: 84 | fatalError("Wrong section") 85 | } 86 | } 87 | 88 | override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 89 | switch section { 90 | case 0: 91 | return self.tableView(tableView, numberOfRowsInSection: section) == 0 ? nil : "From persistent store" 92 | case 1: 93 | return self.tableView(tableView, numberOfRowsInSection: section) == 0 ? nil : "Local" 94 | default: 95 | fatalError("Wrong section") 96 | } 97 | } 98 | 99 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 100 | switch indexPath.section { 101 | case 0: 102 | let cell = tableView.dequeueReusableCellWithIdentifier(kCityTableViewCellReuseIdentifier, forIndexPath: indexPath) as! CityTableViewCell 103 | if let cityEntity = cityFetchedResultsController.objectAtIndexPath(indexPath) { 104 | let city = City(title: cityEntity.title, ID: cityEntity.ID, placeID: cityEntity.placeID, lat: cityEntity.lat, lng: cityEntity.lng) 105 | cell.city = city 106 | } 107 | return cell 108 | 109 | case 1: 110 | let cell = tableView.dequeueReusableCellWithIdentifier(kCityTableViewCellReuseIdentifier, forIndexPath: indexPath) as! CityTableViewCell 111 | cell.city = cities[indexPath.row] 112 | return cell 113 | 114 | default: 115 | fatalError("Wrong indexPath") 116 | } 117 | } 118 | 119 | override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { 120 | switch indexPath.section { 121 | case 0: 122 | if let cityEntity = cityFetchedResultsController.objectAtIndexPath(indexPath) { 123 | let city = City(title: cityEntity.title, ID: cityEntity.ID, placeID: cityEntity.placeID, lat: cityEntity.lat, lng: cityEntity.lng) 124 | self.presenter.showDetailCity(city) 125 | } 126 | 127 | case 1: 128 | let city = cities[indexPath.row] 129 | self.presenter.showDetailCity(city) 130 | 131 | default: 132 | fatalError("Wrong section") 133 | } 134 | } 135 | 136 | // Override to support conditional editing of the table view. 137 | override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool { 138 | // Return false if you do not want the specified item to be editable. 139 | return true 140 | } 141 | 142 | // Override to support editing the table view. 143 | override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) { 144 | if editingStyle == .Delete { 145 | // Delete the row from the data source 146 | switch indexPath.section { 147 | case 0: 148 | if let cityEntity = cityFetchedResultsController.objectAtIndexPath(indexPath) { 149 | let city = City(title: cityEntity.title, ID: cityEntity.ID, placeID: cityEntity.placeID, lat: cityEntity.lat, lng: cityEntity.lng) 150 | self.presenter.removeCity(city) 151 | } 152 | case 1: 153 | cities.removeAtIndex(indexPath.row) 154 | tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade) 155 | default: 156 | fatalError("Wrong section") 157 | } 158 | } else if editingStyle == .Insert { 159 | // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view 160 | } 161 | } 162 | 163 | // Override to support rearranging the table view. 164 | override func tableView(tableView: UITableView, moveRowAtIndexPath fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { 165 | 166 | } 167 | 168 | // Override to support conditional rearranging of the table view. 169 | override func tableView(tableView: UITableView, canMoveRowAtIndexPath indexPath: NSIndexPath) -> Bool { 170 | // Return false if you do not want the item to be re-orderable. 171 | return false 172 | } 173 | 174 | @IBAction func add(sender: AnyObject) { 175 | self.presenter.addNew() 176 | } 177 | 178 | @IBAction func exit(sender: AnyObject) { 179 | self.presenter.exit() 180 | } 181 | } 182 | 183 | extension ListTableViewController: ListInterfaceProtocol { 184 | 185 | } 186 | 187 | extension ListTableViewController: AddViewControllerDelegate { 188 | 189 | func addViewControllerDidSelectCity(city: City) { 190 | cities.append(city) 191 | self.tableView.reloadData() 192 | } 193 | } 194 | 195 | extension ListTableViewController: FetchedResultsControllerDelegate { 196 | 197 | func controllerWillChangeContent(controller: FetchedResultsController) { 198 | self.tableView.beginUpdates() 199 | } 200 | 201 | func controllerDidChangeObject(controller: FetchedResultsController, anObject: SafeObject, indexPath: NSIndexPath?, changeType: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { 202 | let tableView = self.tableView 203 | 204 | switch changeType { 205 | case .Insert: 206 | tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade) 207 | 208 | case .Delete: 209 | tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade) 210 | 211 | case .Update: 212 | tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade) 213 | 214 | case .Move: 215 | tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade) 216 | tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade) 217 | } 218 | } 219 | 220 | func controllerDidChangeSection(controller: FetchedResultsController, section: FetchResultsSectionInfo, sectionIndex: UInt, changeType: NSFetchedResultsChangeType) { 221 | let tableView = self.tableView 222 | 223 | if changeType == NSFetchedResultsChangeType.Insert { 224 | let indexSet = NSIndexSet(index: Int(sectionIndex)) 225 | tableView.reloadSections(indexSet, withRowAnimation: UITableViewRowAnimation.Fade) 226 | // tableView.insertSections(indexSet, withRowAnimation: UITableViewRowAnimation.Fade) 227 | } 228 | else if changeType == NSFetchedResultsChangeType.Delete { 229 | let indexSet = NSIndexSet(index: Int(sectionIndex)) 230 | tableView.reloadSections(indexSet, withRowAnimation: UITableViewRowAnimation.Fade) 231 | // tableView.deleteSections(indexSet, withRowAnimation: UITableViewRowAnimation.Fade) 232 | } 233 | } 234 | 235 | func controllerDidChangeContent(controller: FetchedResultsController) { 236 | self.tableView.endUpdates() 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------