├── .gitignore ├── LICENSE ├── README.md └── rx_mvvm_controller ├── RxModelStateAndProtocols └── RxViewModelPorotocol.swift ├── RxViewModel └── RxModel Module.xctemplate │ ├── TemplateIcon.png │ ├── TemplateIcon@2x.png │ ├── TemplateInfo.plist │ ├── ___FILEBASENAME___+Structures.swift │ ├── ___FILEBASENAME___Configurator.swift │ ├── ___FILEBASENAME___View.swift │ ├── ___FILEBASENAME___ViewController.swift │ └── ___FILEBASENAME___ViewModel.swift └── readme /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | # CocoaPods 31 | # 32 | # We recommend against adding the Pods directory to your .gitignore. However 33 | # you should judge for yourself, the pros and cons are mentioned at: 34 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 35 | # 36 | # Pods/ 37 | 38 | # Carthage 39 | # 40 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 41 | # Carthage/Checkouts 42 | 43 | Carthage/Build 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 51 | 52 | fastlane/report.xml 53 | fastlane/screenshots 54 | 55 | #Code Injection 56 | # 57 | # After new code Injection tools there's a generated folder /iOSInjectionProject 58 | # https://github.com/johnno1962/injectionforxcode 59 | 60 | iOSInjectionProject/ 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Lёha Lobanov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Swift templates of MVVM+Rx architecture 2 | ### Setup via xCode Templates 3 | 4 | This section about native templates used by xCode 5 | 6 | ### Auto syncing 7 | 8 | 1) You need to clean up you Templates folder in xCode location. For this you need open in finder folder below and move all files to trash: 9 | ``` 10 | ~/Library/Developer/Xcode/Templates/ 11 | ``` 12 | 13 | 2) Clone this repo in special place on you disc :) 14 | 15 | 3) Next, we create simlink to EACH template, that you needed in templates section: 16 | ``` 17 | ln -sfv /Users/IvanTheTerrible/Documents/git-repos/alobanov/vps-template/rx_mvvm_controller/RxViewModel ~/Library/Developer/Xcode/Templates/ 18 | ``` 19 | 20 | !ATTENTION! To correct create simlink you MUST use only ABSOLUTE path of git repo! 21 | Check in folder for success creation folders 22 | 23 | 4) That is all! After this add some helpful bash command in you .bash_profile files 24 | ``` 25 | # xCode templates 26 | alias xCode_update_templates="cd ~/Documents/git-repos/alobanov/vps-template; git pull" 27 | ``` 28 | 29 | Do not forget make `source ~/.bash_profile` in you terminal for start using update command. 30 | 31 | Now you can type in terminal: `xCode_update_templates` 32 | -------------------------------------------------------------------------------- /rx_mvvm_controller/RxModelStateAndProtocols/RxViewModelPorotocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RxViewModel.swift 3 | // 4 | // Created by Lobanov Aleksey on 08.09.16. 5 | // Copyright © 2016 All rights reserved. 6 | // 7 | 8 | import Foundation 9 | import RxSwift 10 | import RxCocoa 11 | 12 | protocol RxViewModelType { 13 | associatedtype InputDependencies 14 | associatedtype Input 15 | associatedtype Output 16 | 17 | func configure(input: Input) -> Output 18 | } 19 | 20 | protocol RxViewModelModuleType { 21 | associatedtype ModuleInput 22 | associatedtype ModuleOutput 23 | 24 | func configureModule(input: ModuleInput) -> ModuleOutput 25 | } 26 | 27 | // MARK: - Enums 28 | 29 | public enum ViewAppearState { 30 | case didLoad, didAppear, willAppear, didDisappear, willDisappear, didDeinit 31 | } 32 | 33 | public enum ModelState: Equatable { 34 | case normal 35 | // Content is available and not loading any content 36 | 37 | case empty 38 | // No Content is available 39 | 40 | case error(NSError) 41 | // Got an error 42 | 43 | case networkActivity 44 | // Network activity 45 | 46 | case unknown 47 | // State is not defiend yet 48 | 49 | // for implementation of Equatable 50 | var hash: Int { 51 | switch self { 52 | case .normal: return 0 53 | case .empty: return 1 54 | case .error: return 2 55 | case .networkActivity: return 3 56 | case .unknown: return 4 57 | } 58 | } 59 | } 60 | 61 | // MARK: - ModelState Equatable 62 | 63 | public func == (lhs: ModelState, rhs: ModelState) -> Bool { 64 | return lhs.hash == rhs.hash 65 | } 66 | 67 | // MARK: - RxViewModelStateProtocol 68 | 69 | protocol RxViewModelStateProtocol { 70 | var state: Observable { get } 71 | func isRequestInProcess() -> Bool 72 | func change(state: ModelState) 73 | func show(error: NSError) 74 | } 75 | 76 | // MARK: - RxViewModelState 77 | 78 | class RxViewModelState: RxViewModelStateProtocol { 79 | var state: Observable { 80 | return _state.asObservable() 81 | } 82 | 83 | private var _state = BehaviorRelay(value: .unknown) 84 | 85 | func isRequestInProcess() -> Bool { 86 | return _state.value == .networkActivity 87 | } 88 | 89 | func change(state: ModelState) { 90 | _state.accept(state) 91 | } 92 | 93 | func show(error: NSError) { 94 | _state.accept(.error(error)) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /rx_mvvm_controller/RxViewModel/RxModel Module.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alobanov/MVVM-Templates/54ebfbb8515ccef539b3b9815a82f19492f62ba9/rx_mvvm_controller/RxViewModel/RxModel Module.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /rx_mvvm_controller/RxViewModel/RxModel Module.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alobanov/MVVM-Templates/54ebfbb8515ccef539b3b9815a82f19492f62ba9/rx_mvvm_controller/RxViewModel/RxModel Module.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /rx_mvvm_controller/RxViewModel/RxModel Module.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DefaultCompletionName 6 | My RxModel module 7 | Description 8 | This generates a new module using RxModel architecture. It consists of the view controller, viewModel, configurator, and router. You can then create individual parts of module. 9 | Kind 10 | Xcode.IDEKit.TextSubstitutionFileTemplateKind 11 | Options 12 | 13 | 14 | Description 15 | The name of the module to create 16 | Identifier 17 | sceneName 18 | Name 19 | New Module Name: 20 | NotPersisted 21 | 22 | Required 23 | 24 | Type 25 | text 26 | 27 | 28 | Default 29 | ___VARIABLE_sceneName___ 30 | Identifier 31 | productName 32 | Type 33 | static 34 | 35 | 36 | Default 37 | ___VARIABLE_sceneName___ViewController 38 | Description 39 | The view controller name 40 | Identifier 41 | viewControllerName 42 | Name 43 | View Controller: 44 | Required 45 | 46 | Type 47 | static 48 | 49 | 50 | Default 51 | ___VARIABLE_sceneName___+Structures 52 | Description 53 | The structures name 54 | Identifier 55 | structuresName 56 | Name 57 | Structures: 58 | Required 59 | 60 | Type 61 | static 62 | 63 | 64 | Default 65 | ___VARIABLE_sceneName___Configurator 66 | Description 67 | The configurator name 68 | Identifier 69 | configuratorName 70 | Name 71 | Configurator: 72 | Required 73 | 74 | Type 75 | static 76 | 77 | 78 | Default 79 | ___VARIABLE_sceneName___View 80 | Description 81 | The view name 82 | Identifier 83 | viewName 84 | Name 85 | View: 86 | Required 87 | 88 | Type 89 | static 90 | 91 | 92 | Default 93 | ___VARIABLE_sceneName___ViewModel 94 | Description 95 | The model file name 96 | Identifier 97 | modelFileName 98 | Name 99 | View Model: 100 | Required 101 | 102 | Type 103 | static 104 | 105 | 106 | Platforms 107 | 108 | com.apple.platform.iphoneos 109 | 110 | SortOrder 111 | 9 112 | Summary 113 | This generates a new module using RxModel architecture. 114 | 115 | 116 | -------------------------------------------------------------------------------- /rx_mvvm_controller/RxViewModel/RxModel Module.xctemplate/___FILEBASENAME___+Structures.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright (c) ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | 8 | import Foundation 9 | import RxCocoa 10 | 11 | extension ___VARIABLE_sceneName___ViewModel { 12 | 13 | enum OutputModuleActionType { 14 | 15 | } 16 | 17 | // MARK: - initial module data 18 | struct ModuleInputData { 19 | 20 | } 21 | 22 | // MARK: - module input structure 23 | struct ModuleInput { 24 | 25 | } 26 | 27 | // MARK: - module output structure 28 | struct ModuleOutput { 29 | let moduleAction: Signal 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /rx_mvvm_controller/RxViewModel/RxModel Module.xctemplate/___FILEBASENAME___Configurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright (c) ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ___VARIABLE_sceneName___Configurator { 12 | typealias Result = ( 13 | viewController: UIViewController, 14 | moduleOutput: ___VARIABLE_sceneName___ViewModel.ModuleOutput) 15 | 16 | class func configure(inputData:___VARIABLE_sceneName___ViewModel.ModuleInputData = .init(), 17 | moduleInput: ___VARIABLE_sceneName___ViewModel.ModuleInput = .init()) throws -> Result { 18 | // View controller 19 | let viewController = createViewController() 20 | 21 | // Dependencies 22 | let dependencies = try createDependencies() 23 | 24 | // View model 25 | let viewModel = ___VARIABLE_sceneName___ViewModel(dependencies: dependencies, moduleInputData: inputData) 26 | let moduleOutput = viewModel.configureModule(input: moduleInput) 27 | 28 | viewController.viewModel = viewModel 29 | 30 | return (viewController, moduleOutput) 31 | } 32 | 33 | private class func createViewController() -> ___VARIABLE_sceneName___ViewController { 34 | return ___VARIABLE_sceneName___ViewController() 35 | } 36 | 37 | private class func createDependencies() throws -> ___VARIABLE_sceneName___ViewModel.InputDependencies { 38 | return ___VARIABLE_sceneName___ViewModel.InputDependencies() 39 | } 40 | 41 | static func module( 42 | inputData: ___VARIABLE_sceneName___ViewModel.ModuleInputData = .init(), 43 | moduleInput: ___VARIABLE_sceneName___ViewModel.ModuleInput = .init()) 44 | -> (Presentable, ___VARIABLE_sceneName___ViewModel.ModuleOutput)? { 45 | do { 46 | let output = try ___VARIABLE_sceneName___Configurator.configure(inputData: inputData, moduleInput: moduleInput) 47 | return (output.viewController, output.moduleOutput) 48 | } catch let err { 49 | print(err) 50 | return nil 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /rx_mvvm_controller/RxViewModel/RxModel Module.xctemplate/___FILEBASENAME___View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright (c) ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ___VARIABLE_sceneName___View: UIView { 12 | 13 | override init(frame: CGRect = CGRect.zero) { 14 | super.init(frame: frame) 15 | configureView() 16 | addSubviews() 17 | } 18 | 19 | required init?(coder aDecoder: NSCoder) { 20 | super.init(coder: aDecoder) 21 | } 22 | 23 | private func configureView() { 24 | 25 | } 26 | 27 | private func addSubviews() { 28 | 29 | } 30 | 31 | public func makeConstraints(viewController: UIViewController) { 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /rx_mvvm_controller/RxViewModel/RxModel Module.xctemplate/___FILEBASENAME___ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright (c) ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import RxSwift 11 | import RxCocoa 12 | 13 | class ___VARIABLE_sceneName___ViewController: UIViewController { 14 | 15 | // MARK: - Properties 16 | 17 | // Dependencies 18 | var viewModel: ___VARIABLE_sceneName___ViewOutput? 19 | 20 | // Public 21 | var bag = DisposeBag() 22 | 23 | // Private 24 | private let viewAppearState = PublishRelay() 25 | 26 | // IBOutlet & UI 27 | lazy var customView: ___VARIABLE_sceneName___View = { 28 | let customView = ___VARIABLE_sceneName___View(frame: CGRect(x: 0, y: 0, width: 320, height: 480)) 29 | return customView 30 | }() 31 | 32 | // MARK: - View lifecycle 33 | override func loadView() { 34 | self.view = customView 35 | } 36 | 37 | override func viewDidLoad() { 38 | super.viewDidLoad() 39 | configureUI() 40 | configureRx() 41 | viewAppearState.accept(.didLoad) 42 | } 43 | 44 | override func viewWillAppear(_ animated: Bool) { 45 | super.viewWillAppear(animated) 46 | viewAppearState.accept(.willAppear) 47 | } 48 | 49 | override func viewDidAppear(_ animated: Bool) { 50 | super.viewDidAppear(animated) 51 | viewAppearState.accept(.didAppear) 52 | } 53 | 54 | override func viewWillDisappear(_ animated: Bool) { 55 | super.viewWillDisappear(animated) 56 | viewAppearState.accept(.willDisappear) 57 | } 58 | 59 | override func viewDidDisappear(_ animated: Bool) { 60 | super.viewDidDisappear(animated) 61 | viewAppearState.accept(.didDisappear) 62 | } 63 | 64 | deinit { 65 | print("___VARIABLE_sceneName___ViewController deinit") 66 | } 67 | 68 | // MARK: - Configuration 69 | private func configureRx() { 70 | guard let model = viewModel else { 71 | assertionFailure("Please, set ViewModel as dependency for ___VARIABLE_sceneName___") 72 | return 73 | } 74 | 75 | let input = ___VARIABLE_sceneName___ViewModel.Input( 76 | appearState: viewAppearState.asSignal() 77 | ) 78 | let output = model.configure(input: input) 79 | 80 | output.title.drive(onNext: { [weak self] str in 81 | self?.navigationItem.title = str 82 | }).disposed(by: bag) 83 | 84 | output.state.drive(onNext: { [weak self] state in 85 | // state handler 86 | print(state) 87 | }).disposed(by: bag) 88 | } 89 | 90 | private func configureUI() { 91 | customView.makeConstraints(viewController: self) 92 | } 93 | 94 | // MARK: - Additional 95 | 96 | } 97 | -------------------------------------------------------------------------------- /rx_mvvm_controller/RxViewModel/RxModel Module.xctemplate/___FILEBASENAME___ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___. 6 | // Copyright (c) ___YEAR___ ___ORGANIZATIONNAME___. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import RxSwift 11 | import RxCocoa 12 | 13 | protocol ___VARIABLE_sceneName___ViewOutput { 14 | func configure(input: ___VARIABLE_sceneName___ViewModel.Input) -> ___VARIABLE_sceneName___ViewModel.Output 15 | } 16 | 17 | class ___VARIABLE_sceneName___ViewModel: RxViewModelType, RxViewModelModuleType, ___VARIABLE_sceneName___ViewOutput { 18 | 19 | // MARK: In/Out struct 20 | struct InputDependencies { 21 | 22 | } 23 | 24 | struct Input { 25 | let appearState: Signal 26 | } 27 | 28 | struct Output { 29 | let title: Driver 30 | let state: Driver 31 | } 32 | 33 | // MARK: Dependencies 34 | private let dp: InputDependencies 35 | private let moduleInputData: ModuleInputData 36 | 37 | // MARK: Properties 38 | private let bag = DisposeBag() 39 | private let modelState = BehaviorRelay(value: .unknown) 40 | 41 | // MARK: Observables 42 | private let title = Observable.just("___VARIABLE_sceneName___") 43 | private let outputModuleAction = PublishRelay() 44 | 45 | // MARK: - initializer 46 | 47 | init(dependencies: InputDependencies, moduleInputData: ModuleInputData) { 48 | self.dp = dependencies 49 | self.moduleInputData = moduleInputData 50 | } 51 | 52 | // MARK: - ___VARIABLE_sceneName___ViewOutput 53 | 54 | func configure(input: Input) -> Output { 55 | // Configure input 56 | input.appearState.emit(onNext: { [weak self] state in 57 | switch state { 58 | case .didLoad: 59 | self?.start() 60 | default: 61 | break 62 | } 63 | }).disposed(by: bag) 64 | 65 | // Configure output 66 | return Output( 67 | title: title.asDriver(onErrorJustReturn: ""), 68 | state: modelState.asDriver(onErrorJustReturn: .unknown) 69 | ) 70 | } 71 | 72 | // MARK: - Module configuration 73 | 74 | func configureModule(input: ModuleInput) -> ModuleOutput { 75 | // Configure input signals 76 | 77 | // Configure module output 78 | return ModuleOutput( 79 | moduleAction: outputModuleAction.asSignal() 80 | ) 81 | } 82 | 83 | // MARK: - Additional 84 | 85 | func start() { 86 | 87 | } 88 | 89 | deinit { 90 | print("-- ___VARIABLE_sceneName___ViewModel dead") 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /rx_mvvm_controller/readme: -------------------------------------------------------------------------------- 1 | ~/Library/Developer/Xcode/Templates --------------------------------------------------------------------------------