├── .gitignore ├── images ├── core.png ├── header.png ├── service.png ├── coordinator.png ├── module-mvc.png ├── module-mvp.png ├── project-mvc.png ├── project-mvp.png ├── module-viper.png └── project-viper.png ├── Project Templates └── iOS │ └── iDevs.io │ ├── VIPER.xctemplate │ ├── Podfile │ ├── TemplateIcon.png │ ├── TemplateIcon@2x.png │ ├── Classes │ │ ├── Assemblys │ │ │ ├── Modules │ │ │ │ └── Main │ │ │ │ │ ├── Router │ │ │ │ │ └── MainRouter.swift │ │ │ │ │ ├── Interactor │ │ │ │ │ └── MainInteractor.swift │ │ │ │ │ ├── Contracts │ │ │ │ │ └── MainContracts.swift │ │ │ │ │ ├── Presenter │ │ │ │ │ └── MainPresenter.swift │ │ │ │ │ ├── View │ │ │ │ │ └── MainViewController.swift │ │ │ │ │ ├── Assembly │ │ │ │ │ └── MainAssembly.swift │ │ │ │ │ └── Storyboards │ │ │ │ │ └── Main.storyboard │ │ │ ├── Coordinators │ │ │ │ ├── AppCoordinator │ │ │ │ │ ├── AppCoordinatorType.swift │ │ │ │ │ ├── AppCoordinatorAssembly.swift │ │ │ │ │ └── AppCoordinator.swift │ │ │ │ └── MainCoordinator │ │ │ │ │ ├── MainCoordinatorType.swift │ │ │ │ │ ├── MainCoordinatorAssembly.swift │ │ │ │ │ └── MainCoordinator.swift │ │ │ └── Services │ │ │ │ ├── AppConfigService │ │ │ │ ├── AppConfigServiceAssembly.swift │ │ │ │ ├── AppConfigServiceType.swift │ │ │ │ └── AppConfigService.swift │ │ │ │ └── EnvironmentService │ │ │ │ ├── EnvironmentServiceAssembly.swift │ │ │ │ ├── EnvironmentServiceType.swift │ │ │ │ └── EnvironmentService.swift │ │ ├── Extensions │ │ │ ├── NSObject │ │ │ │ └── NSObject+Ext.swift │ │ │ ├── Codable │ │ │ │ └── Codable+Ext.swift │ │ │ ├── Numbers │ │ │ │ └── Int+Ext.swift │ │ │ ├── URL │ │ │ │ └── URL+Ext.swift │ │ │ ├── View │ │ │ │ ├── UINavigationController+Ext.swift │ │ │ │ ├── UITabBar+Ext.swift │ │ │ │ ├── UIScrollView+Ext.swift │ │ │ │ ├── UIImageView+Ext.swift │ │ │ │ ├── UIWindow+Ext.swift │ │ │ │ ├── UILabel+Ext.swift │ │ │ │ ├── UIImage+Ext.swift │ │ │ │ ├── UICollectionView+Ext.swift │ │ │ │ ├── UIViewController+Ext.swift │ │ │ │ ├── UIView+Ext.swift │ │ │ │ └── UITableView+Ext.swift │ │ │ ├── Date │ │ │ │ └── Date+Ext.swift │ │ │ ├── Color │ │ │ │ └── UIColor+Ext.swift │ │ │ ├── String │ │ │ │ └── String+Ext.swift │ │ │ ├── Collection │ │ │ │ └── Array+Ext.swift │ │ │ └── FileManager │ │ │ │ └── FileManager+Ext.swift │ │ ├── Constants │ │ │ └── Constants.swift │ │ ├── Common │ │ │ ├── Protocols │ │ │ │ ├── Presentable.swift │ │ │ │ ├── URLRoutable.swift │ │ │ │ ├── HUDRoutable.swift │ │ │ │ └── AlertRoutable.swift │ │ │ ├── Presenter │ │ │ │ └── BasePresenter.swift │ │ │ ├── Coordinator │ │ │ │ ├── CoordinatorType.swift │ │ │ │ ├── LaunchInstructor.swift │ │ │ │ └── BaseCoordinator.swift │ │ │ ├── Module │ │ │ │ └── Module.swift │ │ │ ├── Router │ │ │ │ ├── BaseModuleRouter.swift │ │ │ │ ├── RouterType.swift │ │ │ │ └── Router.swift │ │ │ ├── DeepLink │ │ │ │ └── DeepLinkOption.swift │ │ │ ├── View │ │ │ │ ├── ContainerView │ │ │ │ │ └── ContainerView.swift │ │ │ │ └── ViewBuilder │ │ │ │ │ └── ViewBuilder.swift │ │ │ └── AppDelegateManager │ │ │ │ └── AppDelegateManager.swift │ │ ├── Network │ │ │ ├── Request │ │ │ │ └── NetRequests.swift │ │ │ └── ServerApi.swift │ │ ├── Library │ │ │ └── Swilby │ │ │ │ ├── WeakContainer.swift │ │ │ │ ├── StrongBox.swift │ │ │ │ ├── WeakBox.swift │ │ │ │ ├── Assembly.swift │ │ │ │ ├── ObjectKey.swift │ │ │ │ ├── AssemblyFactory.swift │ │ │ │ └── DependencyContainer.swift │ │ ├── AppDelegate │ │ │ ├── AppDelegate.swift │ │ │ └── Services │ │ │ │ └── ApplicationService.swift │ │ ├── Utils │ │ │ └── Tweaks │ │ │ │ ├── ThreadTweak.swift │ │ │ │ ├── DeviceTweak.swift │ │ │ │ └── UtilsTweak.swift │ │ └── Themes │ │ │ └── Theme.swift │ └── TemplateInfo.plist │ └── Base.xctemplate │ ├── Images-iPad.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Images-iPhone.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── Images-Universal.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json │ ├── LaunchScreen.storyboard │ └── TemplateInfo.plist ├── File Templates └── iDevs.io │ ├── Module.xctemplate │ ├── TemplateIcon.png │ ├── TemplateIcon@2x.png │ ├── ___FILEBASENAME___ │ │ ├── Router │ │ │ └── ___FILEBASENAME___Router.swift │ │ ├── Interactor │ │ │ └── ___FILEBASENAME___Interactor.swift │ │ ├── Contracts │ │ │ └── ___FILEBASENAME___Contracts.swift │ │ ├── View │ │ │ └── ___FILEBASENAME___ViewController.swift │ │ ├── Presenter │ │ │ └── ___FILEBASENAME___Presenter.swift │ │ ├── Assembly │ │ │ └── ___FILEBASENAME___Assembly.swift │ │ └── Storyboards │ │ │ └── ___FILEBASENAME___.storyboard │ └── TemplateInfo.plist │ ├── Service.xctemplate │ ├── TemplateIcon.png │ ├── TemplateIcon@2x.png │ ├── ___FILEBASENAME___ │ │ ├── ___FILEBASENAME___Type.swift │ │ ├── ___FILEBASENAME___.swift │ │ └── ___FILEBASENAME___Assembly.swift │ └── TemplateInfo.plist │ └── Coordinator.xctemplate │ ├── TemplateIcon.png │ ├── TemplateIcon@2x.png │ ├── ___FILEBASENAME___ │ ├── ___FILEBASENAME___Type.swift │ ├── ___FILEBASENAME___.swift │ └── ___FILEBASENAME___Assembly.swift │ └── TemplateInfo.plist ├── LICENSE ├── install.swift └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /images/core.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/images/core.png -------------------------------------------------------------------------------- /images/header.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/images/header.png -------------------------------------------------------------------------------- /images/service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/images/service.png -------------------------------------------------------------------------------- /images/coordinator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/images/coordinator.png -------------------------------------------------------------------------------- /images/module-mvc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/images/module-mvc.png -------------------------------------------------------------------------------- /images/module-mvp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/images/module-mvp.png -------------------------------------------------------------------------------- /images/project-mvc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/images/project-mvc.png -------------------------------------------------------------------------------- /images/project-mvp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/images/project-mvp.png -------------------------------------------------------------------------------- /images/module-viper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/images/module-viper.png -------------------------------------------------------------------------------- /images/project-viper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/images/project-viper.png -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, "10.0" 2 | use_frameworks! 3 | target '___PROJECTNAME___' do 4 | 5 | 6 | 7 | end 8 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Module.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/File Templates/iDevs.io/Module.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /File Templates/iDevs.io/Service.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/File Templates/iDevs.io/Service.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /File Templates/iDevs.io/Module.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/File Templates/iDevs.io/Module.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /File Templates/iDevs.io/Service.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/File Templates/iDevs.io/Service.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /File Templates/iDevs.io/Coordinator.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/File Templates/iDevs.io/Coordinator.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/Project Templates/iOS/iDevs.io/VIPER.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /File Templates/iDevs.io/Coordinator.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/File Templates/iDevs.io/Coordinator.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bartleby/Core-iOS-Application-Architecture/HEAD/Project Templates/iOS/iDevs.io/VIPER.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /File Templates/iDevs.io/Service.xctemplate/___FILEBASENAME___/___FILEBASENAME___Type.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ___FILEBASENAME___ { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Coordinator.xctemplate/___FILEBASENAME___/___FILEBASENAME___Type.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | protocol ___FILEBASENAME___: CoordinatorType { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Service.xctemplate/___FILEBASENAME___/___FILEBASENAME___.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class ___FILEBASENAME___: ___VARIABLE_serviceName___Type { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Modules/Main/Router/MainRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class MainRouter: BaseModuleRouter, MainRouterProtocol { 12 | } 13 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinatorType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | protocol AppCoordinatorType: CoordinatorType { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinatorType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | protocol MainCoordinatorType: CoordinatorType { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Module.xctemplate/___FILEBASENAME___/Router/___FILEBASENAME___Router.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class ___VARIABLE_moduleName___Router: BaseModuleRouter, ___VARIABLE_moduleName___RouterProtocol { 12 | 13 | } 14 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Coordinator.xctemplate/___FILEBASENAME___/___FILEBASENAME___.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class ___FILEBASENAME___: BaseCoordinator, ___VARIABLE_coordinatorName___Type { 12 | override func start() { 13 | 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Modules/Main/Interactor/MainInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class MainInteractor: MainInteractorInput { 12 | 13 | } 14 | 15 | // MARK: - Private 16 | extension MainInteractor { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/NSObject/NSObject+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | extension NSObject { 12 | class var nameOfClass: String { 13 | return NSStringFromClass(self).components(separatedBy: ".").last! 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Constants/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | enum Storyboards: String { 12 | case main = "Main" 13 | } 14 | 15 | enum Constants { 16 | enum App { 17 | 18 | } 19 | 20 | enum Network { 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Service.xctemplate/___FILEBASENAME___/___FILEBASENAME___Assembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class ___FILEBASENAME___: Assembly { 12 | func build() -> ___VARIABLE_serviceName___Type { 13 | let service = ___VARIABLE_serviceName___() 14 | return service 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Module.xctemplate/___FILEBASENAME___/Interactor/___FILEBASENAME___Interactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class ___VARIABLE_moduleName___Interactor: ___VARIABLE_moduleName___InteractorInput { 12 | } 13 | 14 | // MARK: Private 15 | extension ___VARIABLE_moduleName___Interactor { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Protocols/Presentable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | protocol Presentable { 12 | func toPresent() -> UIViewController 13 | } 14 | 15 | extension UIViewController: Presentable { 16 | func toPresent() -> UIViewController { 17 | return self 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Presenter/BasePresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class BasePresenter { 12 | let interactor: I 13 | let router: R 14 | 15 | init(interactor: I, router: R) { 16 | self.interactor = interactor 17 | self.router = router 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Services/AppConfigService/AppConfigServiceAssembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppConfigServiceAssembly.swift 3 | // InstaViewer 4 | // 5 | // Created by Bart on 18.10.2019 6 | // Copyright © 2019 iDevs.io. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class AppConfigServiceAssembly: Assembly { 12 | func build() -> AppConfigServiceType { 13 | let service = AppConfigService() 14 | return service 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Services/EnvironmentService/EnvironmentServiceAssembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnvironmentServiceAssembly.swift 3 | // InstaViewer 4 | // 5 | // Created by Bart on 18.10.2019 6 | // Copyright © 2019 iDevs.io. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class EnvironmentServiceAssembly: Assembly { 12 | func build() -> EnvironmentServiceType { 13 | let service = EnvironmentService() 14 | return service 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Coordinator/CoordinatorType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | protocol CoordinatorType: AnyObject, Presentable { 12 | var container: Container {get} 13 | var router: RouterType {get} 14 | var completion: (() -> Void)? {get set} 15 | 16 | func start() 17 | func start(with option: DeepLinkOption?) 18 | } 19 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Module/Module.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ModuleOutput { 12 | 13 | } 14 | 15 | struct Module: Presentable { 16 | var view: UIViewController 17 | var input: Input 18 | var output: Output 19 | 20 | func toPresent() -> UIViewController { 21 | return view 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Network/Request/NetRequests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | protocol RequstModelSerealizable { 12 | func parameters() -> [String: Any] 13 | func body() -> Data 14 | } 15 | 16 | extension RequstModelSerealizable { 17 | func parameters() -> [String: Any] { return [:] } 18 | func body() -> Data { return Data() } 19 | } 20 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinatorAssembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class MainCoordinatorAssembly: Assembly { 12 | func build(router: RouterType) -> MainCoordinatorType { 13 | let coordinator = MainCoordinator(container: container, router: router) 14 | return coordinator 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/Codable/Codable+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | extension Encodable { 12 | var dictionary: [String: Any]? { 13 | guard let data = try? JSONEncoder().encode(self) else { return nil } 14 | return (try? JSONSerialization.jsonObject(with: data, options: .allowFragments)).flatMap { $0 as? [String: Any] } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Library/Swilby/WeakContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class WeakContainer { 12 | fileprivate weak var _value: AnyObject? 13 | var value: T? { 14 | set { self._value = newValue as AnyObject } 15 | get { return _value as? T } 16 | } 17 | 18 | init(value: T) { 19 | self._value = value as AnyObject 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/Numbers/Int+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | extension Int { 12 | func declination(worlds: [String]) -> String { 13 | let cases = [2, 0, 1, 1, 1, 2] 14 | return worlds[(self % 100 > 4 && self % 100 < 20) ? 2 : cases[(self % 10 < 5) ? self % 10 : 5]] 15 | } 16 | } 17 | 18 | extension Int { 19 | func toString() -> String { 20 | return "\(self)" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/URL/URL+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | extension URL { 12 | func fileSize() -> Int64 { 13 | if let resourceValuess = try? (self as NSURL).resourceValues(forKeys: [URLResourceKey.fileSizeKey]) { 14 | let size = resourceValuess[URLResourceKey.fileSizeKey] 15 | return size as? Int64 ?? 0 16 | } else { 17 | return 0 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | class MainCoordinator: BaseCoordinator, MainCoordinatorType { 12 | 13 | override func start() { 14 | let module = container.resolve(MainAssembly.self).build(coordinator: self) 15 | router.setRootModule(module) 16 | } 17 | 18 | override func toPresent() -> UIViewController { 19 | return router.rootViewController! 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/View/UINavigationController+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | public extension UINavigationController { 12 | var rootViewController: UIViewController { 13 | return self.viewControllers.first! 14 | } 15 | 16 | func setTransparentNavigationBar() { 17 | self.navigationBar.setBackgroundImage(UIImage(), for: .default) 18 | self.navigationBar.shadowImage = UIImage() 19 | self.navigationBar.isTranslucent = true 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinatorAssembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | class AppCoordinatorAssembly: Assembly { 12 | func build() -> AppCoordinatorType { 13 | let navigationController = UINavigationController(rootViewController: UIViewController()) 14 | let router = Router(navigationController: navigationController) 15 | let coordinator = AppCoordinator(container: container, router: router) 16 | return coordinator 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Coordinator.xctemplate/___FILEBASENAME___/___FILEBASENAME___Assembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | class ___FILEBASENAME___: Assembly { 12 | func build() -> ___VARIABLE_coordinatorName___Type { 13 | let navigationController = UINavigationController(rootViewController: UIViewController()) 14 | let router = Router(navigationController: navigationController) 15 | let coordinator = ___VARIABLE_coordinatorName___(container: container, router: router) 16 | return coordinator 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Protocols/URLRoutable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | protocol URLRoutable: Presentable, BaseModuleRoutable { 12 | 13 | } 14 | 15 | extension URLRoutable { 16 | func openURL(string: String) { 17 | guard let url = URL(string: string) else { return } 18 | if #available(iOS 10.0, *) { 19 | UIApplication.shared.open(url, options: [:], completionHandler: nil) 20 | } else { 21 | UIApplication.shared.openURL(url) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Coordinator/LaunchInstructor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | enum LaunchInstructor { 12 | case main 13 | case auth 14 | case onboarding 15 | 16 | static func configure(tutorialWasShown: Bool, isAutorized: Bool) -> LaunchInstructor { 17 | switch (tutorialWasShown, isAutorized) { 18 | case (true, false), (false, false): return .auth 19 | case (false, true): return .onboarding 20 | case (true, true): return .main 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/View/UITabBar+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | extension UITabBar { 12 | func setBackgroundImage(_ image: UIImage, yOffset: Int) { 13 | self.backgroundImage = UIImage() 14 | self.shadowImage = UIImage() 15 | let tabBarView = UIImageView(image: image) 16 | tabBarView.frame = CGRect(x: 0, y: yOffset, width: Int(tabBarView.bounds.width), height: Int(tabBarView.bounds.height)) 17 | self.addSubview(tabBarView) 18 | self.sendSubviewToBack(tabBarView) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Library/Swilby/StrongBox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | protocol StrongBox: class { 12 | var strongBoxHolder: [String : AnyObject] { set get } 13 | } 14 | 15 | extension StrongBox { 16 | func strongBox(_ configure: () -> T) -> T { 17 | let key = ObjectKey(T.self).key 18 | if let object = self.strongBoxHolder[key] { 19 | return object as! T 20 | } 21 | let object = configure() 22 | strongBoxHolder[key] = object as AnyObject 23 | return object 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/View/UIScrollView+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | extension UITableView { 12 | func scrollToBottom() { 13 | let lastSectionIndex = numberOfSections - 1 14 | if lastSectionIndex >= 0 { 15 | let lastRowIndex = numberOfRows(inSection: lastSectionIndex) - 1 16 | if lastRowIndex >= 0 { 17 | let pathToLastRow = IndexPath(row: lastRowIndex, section: lastSectionIndex) 18 | scrollToRow(at: pathToLastRow, at: .bottom, animated: false) 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Library/Swilby/WeakBox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | protocol WeakBox: class { 12 | var weakBoxHolder: [String : WeakContainer] { set get} 13 | } 14 | 15 | extension WeakBox { 16 | func weakBox(_ configure: () -> T) -> T { 17 | let key = ObjectKey(T.self).key 18 | if let object = self.weakBoxHolder[key]?.value as? T { 19 | return object 20 | } 21 | let object = configure() 22 | weakBoxHolder[key] = WeakContainer(value: object as AnyObject) 23 | return object 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Services/EnvironmentService/EnvironmentServiceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnvironmentServiceType.swift 3 | // InstaViewer 4 | // 5 | // Created by Bart on 18.10.2019 6 | // Copyright © 2019 iDevs.io. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum EnvironmentKey: String { 12 | case apiEndpoint = "api_endpoint" 13 | case apiVersion = "api_version" 14 | case appVersion = "CFBundleShortVersionString" 15 | case buildNumber = "CFBundleVersion" 16 | 17 | var value: String { 18 | return self.rawValue 19 | } 20 | } 21 | 22 | protocol EnvironmentServiceType { 23 | var apiURL: URL { get } 24 | func obtainEnvironment(for key: EnvironmentKey) -> T 25 | } 26 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Modules/Main/Contracts/MainContracts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | // Module Input 12 | protocol MainModuleInput { 13 | 14 | } 15 | 16 | // Module Output 17 | protocol MainModuleOutput: ModuleOutput { 18 | 19 | } 20 | 21 | // View Input 22 | protocol MainViewInput: AnyObject { 23 | func set(title: String) 24 | } 25 | 26 | // View Output 27 | protocol MainViewOutput: AnyObject { 28 | func viewDidLoad() 29 | } 30 | 31 | // Interactor 32 | protocol MainInteractorInput { 33 | 34 | } 35 | 36 | // Router 37 | protocol MainRouterProtocol: AlertRoutable { 38 | 39 | } 40 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Modules/Main/Presenter/MainPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class MainPresenter: BasePresenter, MainModuleOutput { 12 | // MARK: - Weak properties 13 | weak var view: MainViewInput? 14 | } 15 | 16 | // MARK: Private 17 | extension MainPresenter { 18 | 19 | } 20 | 21 | // MARK: Module Input 22 | extension MainPresenter: MainModuleInput { 23 | 24 | } 25 | 26 | // MARK: View Output 27 | extension MainPresenter: MainViewOutput { 28 | func viewDidLoad() { 29 | view?.set(title: "Main") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/View/UIImageView+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | func resizeImage(newWidth: CGFloat) -> UIImage { 13 | 14 | let scale = newWidth / self.size.width 15 | let newHeight = self.size.height * scale 16 | UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight)) 17 | self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight)) 18 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 19 | UIGraphicsEndImageContext() 20 | 21 | return newImage! 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Modules/Main/View/MainViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | class MainViewController: UIViewController, MainViewInput { 12 | var output: MainViewOutput! 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | output.viewDidLoad() 17 | } 18 | } 19 | 20 | // MARK: - Configure 21 | extension MainViewController { 22 | 23 | } 24 | 25 | // MARK: View Input 26 | extension MainViewController { 27 | func set(title: String) { 28 | self.title = title 29 | } 30 | } 31 | 32 | // MARK: Button Action 33 | extension MainViewController { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/Base.xctemplate/Images-iPad.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "ipad", 5 | "size" : "29x29", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "ipad", 10 | "size" : "29x29", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "ipad", 15 | "size" : "40x40", 16 | "scale" : "1x" 17 | }, 18 | { 19 | "idiom" : "ipad", 20 | "size" : "40x40", 21 | "scale" : "2x" 22 | }, 23 | { 24 | "idiom" : "ipad", 25 | "size" : "76x76", 26 | "scale" : "1x" 27 | }, 28 | { 29 | "idiom" : "ipad", 30 | "size" : "76x76", 31 | "scale" : "2x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/Base.xctemplate/Images-iPhone.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Router/BaseModuleRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | protocol BaseModuleRoutable { 12 | var coordinator: CoordinatorType! {set get} 13 | var container: Container {set get} 14 | } 15 | 16 | class BaseModuleRouter: BaseModuleRoutable { 17 | weak var coordinator: CoordinatorType! 18 | var container: Container 19 | 20 | init(coordinator: CoordinatorType) { 21 | self.coordinator = coordinator 22 | self.container = coordinator.container 23 | } 24 | } 25 | 26 | extension BaseModuleRouter: Presentable { 27 | func toPresent() -> UIViewController { 28 | return coordinator.toPresent() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Services/AppConfigService/AppConfigServiceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppConfigServiceType.swift 3 | // InstaViewer 4 | // 5 | // Created by Bart on 18.10.2019 6 | // Copyright © 2019 iDevs.io. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | enum ConfigKey: String { 12 | case authToken = "authToken" 13 | case onboardingWasShown = "onboardingWasShown" 14 | 15 | var value: String { 16 | return self.rawValue 17 | } 18 | 19 | static var defaultValues: [String: Any] = [ 20 | ConfigKey.authToken.value: "", 21 | ConfigKey.onboardingWasShown.value: false 22 | ] 23 | } 24 | 25 | protocol AppConfigServiceType { 26 | func obtain(for key: ConfigKey) -> T 27 | func set(value: T, for key: ConfigKey) 28 | 29 | func registerDefaults() 30 | } 31 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Module.xctemplate/___FILEBASENAME___/Contracts/___FILEBASENAME___Contracts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | // Module Input 12 | protocol ___VARIABLE_moduleName___ModuleInput { 13 | 14 | } 15 | 16 | // Module Output 17 | protocol ___VARIABLE_moduleName___ModuleOutput { 18 | 19 | } 20 | 21 | // View Input 22 | protocol ___VARIABLE_moduleName___ViewInput: class { 23 | func set(title: String) 24 | } 25 | 26 | // View Output 27 | protocol ___VARIABLE_moduleName___ViewOutput: class { 28 | func viewDidLoad() 29 | } 30 | 31 | // Interactor 32 | protocol ___VARIABLE_moduleName___InteractorInput { 33 | } 34 | 35 | // Router 36 | protocol ___VARIABLE_moduleName___RouterProtocol: AlertRoutable { 37 | 38 | } 39 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Module.xctemplate/___FILEBASENAME___/View/___FILEBASENAME___ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | class ___VARIABLE_moduleName___ViewController: UIViewController, ___VARIABLE_moduleName___ViewInput { 12 | var output: ___VARIABLE_moduleName___ViewOutput! 13 | 14 | override func viewDidLoad() { 15 | super.viewDidLoad() 16 | output.viewDidLoad() 17 | } 18 | } 19 | 20 | // MARK: - Configure 21 | extension ___VARIABLE_moduleName___ViewController { 22 | 23 | } 24 | 25 | // MARK: View Input 26 | extension ___VARIABLE_moduleName___ViewController { 27 | func set(title: String) { 28 | self.title = title 29 | } 30 | } 31 | 32 | // MARK: Button Action 33 | extension ___VARIABLE_moduleName___ViewController { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Library/Swilby/Assembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | protocol AssemblyType: class { 12 | associatedtype Container 13 | var container: Container {get set} 14 | init(container: Container) 15 | } 16 | 17 | class Assembly: AssemblyType { 18 | //typealias Container = Resolver 19 | 20 | var container: Container 21 | 22 | required init(container: Container) { 23 | self.container = container 24 | } 25 | } 26 | 27 | // Box 28 | extension Assembly { 29 | func weakBox(_ configure: () -> T) -> T { 30 | return self.container.weakBox(configure) 31 | } 32 | 33 | func strongBox(_ configure: () -> T) -> T { 34 | return self.container.strongBox(configure) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Module.xctemplate/___FILEBASENAME___/Presenter/___FILEBASENAME___Presenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class ___VARIABLE_moduleName___Presenter: BasePresenter<___VARIABLE_moduleName___InteractorInput, ___VARIABLE_moduleName___RouterProtocol>, ___VARIABLE_moduleName___ModuleOutput { 12 | 13 | // MARK: - Weak properties 14 | weak var view: ___VARIABLE_moduleName___ViewInput? 15 | } 16 | 17 | // MARK: Private 18 | extension ___VARIABLE_moduleName___Presenter { 19 | 20 | } 21 | 22 | // MARK: Module Input 23 | extension ___VARIABLE_moduleName___Presenter: ___VARIABLE_moduleName___ModuleInput { 24 | 25 | } 26 | 27 | // MARK: View Output 28 | extension ___VARIABLE_moduleName___Presenter: ___VARIABLE_moduleName___ViewOutput { 29 | func viewDidLoad() { 30 | view?.set(title: "___VARIABLE_moduleName___") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Library/Swilby/ObjectKey.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | internal struct ObjectKey { 12 | fileprivate let objectType: Any.Type 13 | fileprivate let name: String? 14 | 15 | fileprivate(set) var key: String { 16 | get { return String(self.hashValue) } 17 | set {} 18 | } 19 | 20 | internal init(_ objectType: Any.Type, name: String? = nil) { 21 | self.objectType = objectType 22 | self.name = name 23 | } 24 | } 25 | 26 | // MARK: Hashable 27 | extension ObjectKey: Hashable { 28 | func hash(into hasher: inout Hasher) { 29 | hasher.combine(String(describing: objectType).hashValue ^ (name?.hashValue ?? 0)) 30 | } 31 | } 32 | 33 | // MARK: Equatable 34 | func == (lhs: ObjectKey, rhs: ObjectKey) -> Bool { 35 | return lhs.objectType == rhs.objectType && lhs.name == rhs.name 36 | } 37 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Modules/Main/Assembly/MainAssembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | typealias MainModule = Module 12 | 13 | class MainAssembly: Assembly { 14 | func build(coordinator: CoordinatorType) -> MainModule { 15 | 16 | // View 17 | let view = MainViewController.controllerFromStoryboard(.main) 18 | 19 | // Interactor 20 | let interactor = MainInteractor() 21 | 22 | // Router 23 | let router = MainRouter(coordinator: coordinator) 24 | 25 | // Presenter 26 | let presenter = MainPresenter(interactor: interactor, router: router) 27 | 28 | // Dependency Setup 29 | presenter.view = view 30 | view.output = presenter 31 | 32 | return Module(view: view, input: presenter, output: presenter) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/Date/Date+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | extension Date { 12 | func toTimestamp() -> Int? { 13 | return Int(self.timeIntervalSince1970) 14 | } 15 | 16 | static func fromTimestamp(_ timestamp: Double) -> Date { 17 | let date = Date(timeIntervalSince1970: timestamp) 18 | return date 19 | } 20 | 21 | } 22 | 23 | extension Date { 24 | func convertToLocalTime(fromTimeZone timeZoneAbbreviation: String) -> Date? { 25 | if let timeZone = TimeZone(abbreviation: timeZoneAbbreviation) { 26 | let targetOffset = TimeInterval(timeZone.secondsFromGMT(for: self)) 27 | let localOffeset = TimeInterval(TimeZone.autoupdatingCurrent.secondsFromGMT(for: self)) 28 | 29 | return self.addingTimeInterval(targetOffset - localOffeset) 30 | } 31 | 32 | return nil 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/Color/UIColor+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | convenience init(hex: String) { 13 | var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() 14 | 15 | if (cString.hasPrefix("#")) { 16 | cString.remove(at: cString.startIndex) 17 | } 18 | 19 | if ((cString.count) != 6) { 20 | self.init(red: 50, green: 50, blue: 50, alpha: 1) 21 | return 22 | } 23 | 24 | var rgbValue:UInt64 = 0 25 | Scanner(string: cString).scanHexInt64(&rgbValue) 26 | 27 | self.init( 28 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, 29 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, 30 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0, 31 | alpha: CGFloat(1.0) 32 | ) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Alex 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 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Services/AppConfigService/AppConfigService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppConfigService.swift 3 | // InstaViewer 4 | // 5 | // Created by Bart on 18.10.2019 6 | // Copyright © 2019 iDevs.io. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class AppConfigService: AppConfigServiceType { 12 | fileprivate var userDefaults: UserDefaults { 13 | return UserDefaults.standard 14 | } 15 | } 16 | 17 | extension AppConfigService { 18 | func obtain(for key: ConfigKey) -> T { 19 | guard let value = userDefaults.object(forKey: key.value) else { fatalError("Config key not found") } 20 | guard let result = value as? T else { fatalError("Expecting another type") } 21 | return result 22 | } 23 | 24 | func set(value: T, for key: ConfigKey) { 25 | userDefaults.set(value, forKey: key.value) 26 | userDefaults.synchronize() 27 | } 28 | 29 | func registerDefaults() { 30 | userDefaults.register(defaults: ConfigKey.defaultValues) 31 | } 32 | } 33 | 34 | extension AppConfigService { 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Services/EnvironmentService/EnvironmentService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EnvironmentService.swift 3 | // InstaViewer 4 | // 5 | // Created by Bart on 18.10.2019 6 | // Copyright © 2019 iDevs.io. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | class EnvironmentService: EnvironmentServiceType { 12 | fileprivate var infoDictionary: [String: Any] { 13 | guard let dict = Bundle.main.infoDictionary else { fatalError("Plist file not found") } 14 | return dict 15 | } 16 | 17 | lazy var apiURL: URL = { 18 | let apiEndpoint: String = obtainEnvironment(for: .apiEndpoint) 19 | let apiVersion: String = obtainEnvironment(for: .apiVersion) 20 | return URL(string: "\(apiEndpoint)/\(apiVersion)")! 21 | }() 22 | } 23 | 24 | extension EnvironmentService { 25 | func obtainEnvironment(for key: EnvironmentKey) -> T { 26 | guard let value = infoDictionary[key.value] else { fatalError("Plist key not found") } 27 | guard let result = value as? T else { fatalError("Expecting another type") } 28 | return result 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Network/ServerApi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | /* 9 | import Foundation 10 | import Moya 11 | import Alamofire 12 | 13 | let apiProvider = MoyaProvider(plugins: [NetworkLoggerPlugin(verbose: true)]) 14 | 15 | enum ServerAPI { 16 | 17 | } 18 | 19 | 20 | extension ServerAPI: TargetType { 21 | var baseURL: URL { return URL(string: "http://")! } 22 | 23 | var path: String { 24 | switch self { 25 | 26 | } 27 | } 28 | 29 | var method: Moya.Method { 30 | switch self { 31 | default: 32 | return .get 33 | } 34 | } 35 | 36 | var sampleData: Data { 37 | return Data() 38 | } 39 | 40 | var task: Task { 41 | switch self { 42 | 43 | } 44 | } 45 | 46 | var headers: [String : String]? { 47 | switch self { 48 | default: 49 | return ["Content-Type": "application/json"] 50 | } 51 | } 52 | } 53 | */ 54 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/AppDelegate/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: AppDelegateManager { 13 | var window: UIWindow? 14 | 15 | lazy var container: DependencyContainer = { 16 | let factory = AssemblyFactory() 17 | let container = DependencyContainer(assemblyFactory: factory) 18 | 19 | // Setup Coordinators 20 | container.apply(AppCoordinatorAssembly.self) 21 | container.apply(MainCoordinatorAssembly.self) 22 | 23 | // Setup Modules 24 | container.apply(MainAssembly.self) 25 | 26 | // Setup Services 27 | container.apply(AppConfigServiceAssembly.self) 28 | container.apply(EnvironmentServiceAssembly.self) 29 | 30 | 31 | return container 32 | }() 33 | 34 | override var services: [AppDelegateService] { 35 | return [ 36 | ApplicationService(container: container, window: window) 37 | ] 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Library/Swilby/AssemblyFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | protocol AssemblyFactoryProtocol: class { 12 | func apply(_ assembly: T.Type, name: String?) 13 | func resolve(_ type: T.Type, name: String?) -> T.Type 14 | } 15 | 16 | class AssemblyFactory { 17 | typealias AssemblyCollection = [String : Any.Type] 18 | fileprivate var assemblyCollection = AssemblyCollection() 19 | } 20 | 21 | extension AssemblyFactory: AssemblyFactoryProtocol { 22 | func apply(_ assembly: T.Type, name: String? = nil) { 23 | let key = ObjectKey(assembly, name: name).key 24 | self.assemblyCollection[key] = assembly 25 | } 26 | 27 | func resolve(_ type: T.Type, name: String? = nil) -> T.Type { 28 | let key = ObjectKey(type, name: name).key 29 | guard let assembly = assemblyCollection[key] else { fatalError("Assemblay '\(String(describing: type))' has't been registered, use 'apply( _:)' method") } 30 | return assembly as! T.Type 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Module.xctemplate/___FILEBASENAME___/Assembly/___FILEBASENAME___Assembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | typealias ___VARIABLE_moduleName___Module = Module<___VARIABLE_moduleName___ModuleInput, ___VARIABLE_moduleName___ModuleOutput> 12 | 13 | class ___VARIABLE_moduleName___Assembly: Assembly { 14 | func build(coordinator: CoordinatorType) -> ___VARIABLE_moduleName___Module { 15 | // View 16 | let view = ___VARIABLE_moduleName___ViewController.controllerFromStoryboard("___VARIABLE_moduleName___") 17 | 18 | // Interactor 19 | let interactor = ___VARIABLE_moduleName___Interactor() 20 | 21 | // Router 22 | let router = ___VARIABLE_moduleName___Router(coordinator: coordinator) 23 | 24 | // Presenter 25 | let presenter = ___VARIABLE_moduleName___Presenter(interactor: interactor, router: router) 26 | 27 | // Dependency Setup 28 | presenter.view = view 29 | view.output = presenter 30 | 31 | return Module(view: view, input: presenter, output: presenter) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Module.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.IDEFoundation.TextSubstitutionFileTemplateKind 7 | Description 8 | Module 9 | SortOrder 10 | 1 11 | DefaultCompletionName 12 | Module 13 | Platforms 14 | 15 | com.apple.platform.iphoneos 16 | 17 | Options 18 | 19 | 20 | Description 21 | The name of the module to create 22 | Identifier 23 | moduleName 24 | Name 25 | New Module Name: 26 | NotPersisted 27 | 28 | Required 29 | 30 | Type 31 | text 32 | 33 | 34 | Default 35 | ___VARIABLE_moduleName___ 36 | Identifier 37 | productName 38 | Type 39 | static 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Service.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.IDEFoundation.TextSubstitutionFileTemplateKind 7 | Description 8 | Service 9 | SortOrder 10 | 4 11 | DefaultCompletionName 12 | Service 13 | Platforms 14 | 15 | com.apple.platform.iphoneos 16 | 17 | Options 18 | 19 | 20 | Description 21 | The name of the Service to create 22 | Identifier 23 | serviceName 24 | Name 25 | New Service Name: 26 | NotPersisted 27 | 28 | Required 29 | 30 | Type 31 | text 32 | 33 | 34 | Default 35 | ___VARIABLE_serviceName___ 36 | Identifier 37 | productName 38 | Type 39 | static 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/String/String+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | extension String { 12 | var first: String { 13 | return String(prefix(1)) 14 | } 15 | var uppercasedFirst: String { 16 | return first.uppercased() + String(dropFirst()) 17 | } 18 | } 19 | 20 | extension String { 21 | func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat { 22 | let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude) 23 | let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil) 24 | 25 | return ceil(boundingBox.height) 26 | } 27 | 28 | func width(withConstraintedHeight height: CGFloat, font: UIFont) -> CGFloat { 29 | let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height) 30 | let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [NSAttributedString.Key.font: font], context: nil) 31 | 32 | return ceil(boundingBox.width) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/DeepLink/DeepLinkOption.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | struct DeepLinkConstants { 12 | static let home = "home" 13 | static let item = "item" 14 | } 15 | 16 | enum DeepLinkOption { 17 | case home 18 | //case item(String?) 19 | 20 | static func build(with userActivity: NSUserActivity) -> DeepLinkOption? { 21 | guard userActivity.activityType == NSUserActivityTypeBrowsingWeb else { return nil } 22 | guard let url = userActivity.webpageURL else { return nil } 23 | guard let _ = URLComponents(url: url, resolvingAgainstBaseURL: true) else { return nil } 24 | 25 | //TODO: parse url 26 | return nil 27 | } 28 | 29 | static func build(with dict: [String : AnyObject]?) -> DeepLinkOption? { 30 | guard let screen = dict?["screen"] as? String else { return nil } 31 | //let itemID = dict?["item_id"] as? String 32 | 33 | switch screen { 34 | case DeepLinkConstants.home: return .home 35 | //case DeepLinkConstants.item: return .item(itemID) 36 | default: return nil 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Coordinator.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.IDEFoundation.TextSubstitutionFileTemplateKind 7 | Description 8 | Coordinator 9 | SortOrder 10 | 5 11 | DefaultCompletionName 12 | Coordinator 13 | Platforms 14 | 15 | com.apple.platform.iphoneos 16 | 17 | Options 18 | 19 | 20 | Description 21 | The name of the Coordinator to create 22 | Identifier 23 | coordinatorName 24 | Name 25 | New Coordinator Name: 26 | NotPersisted 27 | 28 | Required 29 | 30 | Type 31 | text 32 | 33 | 34 | Default 35 | ___VARIABLE_coordinatorName___ 36 | Identifier 37 | productName 38 | Type 39 | static 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/View/ContainerView/ContainerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | class ContainerView: UIView { 12 | var controller: UIViewController? 13 | 14 | func embedViewController(_ controller: UIViewController) { 15 | self.controller = controller 16 | controller.view.frame = self.bounds 17 | self.addSubview(controller.view) 18 | } 19 | 20 | func clear() { 21 | for item in self.subviews { 22 | item.removeFromSuperview() 23 | } 24 | controller?.removeFromParent() 25 | controller = nil 26 | } 27 | 28 | override func removeFromSuperview() { 29 | super.removeFromSuperview() 30 | } 31 | 32 | override func setNeedsUpdateConstraints() { 33 | super.setNeedsUpdateConstraints() 34 | controller?.view.setNeedsUpdateConstraints() 35 | } 36 | 37 | override func setNeedsLayout() { 38 | super.setNeedsLayout() 39 | controller?.view.setNeedsLayout() 40 | } 41 | 42 | override func layoutIfNeeded() { 43 | super.layoutIfNeeded() 44 | controller?.view.layoutIfNeeded() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Router/RouterType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | protocol RouterType: Presentable { 12 | var navigationController: UINavigationController { get } 13 | var rootViewController: UIViewController? { get } 14 | 15 | func present(_ module: Presentable) 16 | func present(_ module: Presentable, style: UIModalPresentationStyle) 17 | func present(_ module: Presentable, animated: Bool, style: UIModalPresentationStyle) 18 | 19 | func push(_ module: Presentable) 20 | func push(_ module: Presentable, hideBottomBar: Bool) 21 | func push(_ module: Presentable, animated: Bool) 22 | func push(_ module: Presentable, animated: Bool, completion: (() -> Void)?) 23 | func push(_ module: Presentable, animated: Bool, hideBottomBar: Bool, completion: (() -> Void)?) 24 | 25 | func popModule() 26 | func popModule(animated: Bool) 27 | 28 | func dismissModule() 29 | func dismissModule(animated: Bool, completion: (() -> Void)?) 30 | 31 | func setRootModule(_ module: Presentable) 32 | func setRootModule(_ module: Presentable, hideBar: Bool) 33 | 34 | func popToRootModule(animated: Bool) 35 | } 36 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Utils/Tweaks/ThreadTweak.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | public func delay(_ delay: Double, closure: @escaping () -> Void) { 12 | DispatchQueue.main.asyncAfter( 13 | deadline: DispatchTime.now() + Double(Int64(delay * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC), execute: closure) 14 | } 15 | 16 | enum Queue { 17 | case main // main queue 18 | case userInitiated // small and urgent actions 19 | case `default` // network tasks, rendering 20 | case utility // network tasks, rendering 21 | case background // long-term processes 22 | 23 | func async(_ closure: @escaping () -> Void) { 24 | switch self { 25 | case .main: 26 | DispatchQueue.main.async(execute: closure) 27 | case .userInitiated: 28 | DispatchQueue.global(qos: .userInitiated).async(execute: closure) 29 | case .default: 30 | DispatchQueue.global(qos: .default).async(execute: closure) 31 | case .utility: 32 | DispatchQueue.global(qos: .utility).async(execute: closure) 33 | case .background: 34 | DispatchQueue.global(qos: .background).async(execute: closure) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/Base.xctemplate/Images-Universal.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Utils/Tweaks/DeviceTweak.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | struct ScreenSize { 12 | static let width = UIScreen.main.bounds.size.width 13 | static let height = UIScreen.main.bounds.size.height 14 | static let maxLength = max(ScreenSize.width, ScreenSize.height) 15 | static let minLength = min(ScreenSize.width, ScreenSize.height) 16 | static let frame = CGRect(x: 0, y: 0, width: ScreenSize.width, height: ScreenSize.height) 17 | } 18 | 19 | struct DeviceType { 20 | static let iPhone4orLess = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxLength < 568.0 21 | static let iPhone5orSE = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxLength == 568.0 22 | static let iPhone678 = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxLength == 667.0 23 | static let iPhone678p = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxLength == 736.0 24 | static let iPhoneX = UIDevice.current.userInterfaceIdiom == .phone && ScreenSize.maxLength == 812.0 25 | 26 | static let IS_IPAD = UIDevice.current.userInterfaceIdiom == .pad && ScreenSize.maxLength == 1024.0 27 | static let IS_IPAD_PRO = UIDevice.current.userInterfaceIdiom == .pad && ScreenSize.maxLength == 1366.0 28 | } 29 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | class AppCoordinator: BaseCoordinator, AppCoordinatorType { 12 | 13 | private var instructor: LaunchInstructor { 14 | return LaunchInstructor.configure(tutorialWasShown: true, isAutorized: true) 15 | } 16 | 17 | override func start(with option: DeepLinkOption?) { 18 | if let option = option { 19 | switch option { 20 | case .home: 21 | startMainFlow() 22 | } 23 | } else { 24 | switch instructor { 25 | case .auth: startAuthFlow() 26 | case .onboarding: startOnboardingFlow() 27 | case .main: startMainFlow() 28 | } 29 | } 30 | } 31 | } 32 | 33 | // MARK: Main Flow 34 | extension AppCoordinator { 35 | func startMainFlow() { 36 | let coordinator = container.resolve(MainCoordinatorAssembly.self).build(router: router) 37 | addChild(coordinator) 38 | coordinator.start() 39 | 40 | self.router.setRootModule(coordinator) 41 | } 42 | } 43 | 44 | // MARK: Auth Flow 45 | extension AppCoordinator { 46 | func startAuthFlow() { 47 | 48 | } 49 | } 50 | 51 | // MARK: Onboarding Flow 52 | extension AppCoordinator { 53 | func startOnboardingFlow() { 54 | 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/View/UIWindow+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIWindow { 12 | func visibleViewController() -> UIViewController? { 13 | if let rootViewController: UIViewController = self.rootViewController { 14 | return UIWindow.getVisibleViewControllerFrom(rootViewController) 15 | } 16 | return nil 17 | } 18 | 19 | class func getVisibleViewControllerFrom(_ vc: UIViewController) -> UIViewController { 20 | if let navigationController = vc as? UINavigationController { 21 | return UIWindow.getVisibleViewControllerFrom(navigationController.visibleViewController!) 22 | 23 | } else if let tabBarController = vc as? UITabBarController { 24 | 25 | return UIWindow.getVisibleViewControllerFrom(tabBarController.selectedViewController!) 26 | 27 | } else if let 28 | pageViewController = vc as? UIPageViewController, 29 | let currentVC = pageViewController.viewControllers?.first { 30 | return UIWindow.getVisibleViewControllerFrom(currentVC) 31 | 32 | } else { 33 | 34 | if let presentedViewController = vc.presentedViewController { 35 | return UIWindow.getVisibleViewControllerFrom(presentedViewController) 36 | 37 | } else { 38 | return vc 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Coordinator/BaseCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | class BaseCoordinator: CoordinatorType { 12 | var completion: (() -> Void)? 13 | 14 | let container: Container 15 | let router: RouterType 16 | 17 | init(container: Container, router: RouterType) { 18 | self.container = container 19 | self.router = router 20 | } 21 | 22 | var childCoordinators: [CoordinatorType] = [] 23 | 24 | func start() { 25 | start(with: nil) 26 | } 27 | 28 | func start(with option: DeepLinkOption?) { } 29 | 30 | // add only unique object 31 | func addChild(_ coordinator: CoordinatorType) { 32 | guard !childCoordinators.contains(where: { $0 === coordinator }) else { return } 33 | childCoordinators.append(coordinator) 34 | } 35 | 36 | func removeChild(_ coordinator: CoordinatorType?) { 37 | guard childCoordinators.isEmpty == false, let coordinator = coordinator else { return } 38 | 39 | // Clear child-coordinators recursively 40 | if let coordinator = coordinator as? BaseCoordinator, !coordinator.childCoordinators.isEmpty { 41 | coordinator.childCoordinators.filter({ $0 !== coordinator }).forEach({ coordinator.removeChild($0) }) 42 | } 43 | 44 | for (index, element) in childCoordinators.enumerated() where element === coordinator { 45 | childCoordinators.remove(at: index) 46 | break 47 | } 48 | } 49 | 50 | func toPresent() -> UIViewController { 51 | return router.toPresent() 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/Base.xctemplate/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 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Library/Swilby/DependencyContainer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | typealias Container = Resolver 12 | typealias LightContainer = Applyer & Resolver 13 | 14 | // Container 15 | protocol Applyer { 16 | func apply(_ type: T.Type, name: String?) 17 | func apply(_ type: T.Type) 18 | } 19 | 20 | extension Applyer { 21 | func apply(_ type: T.Type) { 22 | self.apply(type, name: nil) 23 | } 24 | } 25 | 26 | // Resolver 27 | protocol Resolver: WeakBox, StrongBox { 28 | func resolve(_ type: T.Type, name: String?) -> T 29 | func resolve(_ type: T.Type) -> T 30 | } 31 | 32 | extension Resolver { 33 | func resolve(_ type: T.Type) -> T where T : Assembly { 34 | return self.resolve(type, name: nil) 35 | } 36 | } 37 | 38 | // DI Container 39 | class DependencyContainer { 40 | internal var weakBoxHolder = [String : WeakContainer]() 41 | internal var strongBoxHolder = [String : AnyObject]() 42 | 43 | let assemblyFactory: AssemblyFactoryProtocol 44 | 45 | init(assemblyFactory: AssemblyFactoryProtocol) { 46 | self.assemblyFactory = assemblyFactory 47 | } 48 | } 49 | 50 | extension DependencyContainer: Resolver { 51 | func resolve(_ type: T.Type, name: String?) -> T where T : Assembly { 52 | let module = self.assemblyFactory.resolve(type, name: name) 53 | return module.init(container: self) 54 | } 55 | } 56 | 57 | extension DependencyContainer: Applyer { 58 | func apply(_ type: T.Type, name: String?) where T : AssemblyType { 59 | self.assemblyFactory.apply(type, name: name) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/Collection/Array+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | extension Array { 12 | var isNotEmpty: Bool { 13 | return !isEmpty 14 | } 15 | 16 | mutating func removeObject(_ object: U) { 17 | var index: Int? 18 | 19 | for (idx, objectToCompare) in self.enumerated() { 20 | if let to = objectToCompare as? U { 21 | if object == to { 22 | index = idx 23 | } 24 | } 25 | } 26 | 27 | if let index = index { 28 | self.remove(at: index) 29 | } 30 | } 31 | 32 | mutating func removeElement(_ element: U) { 33 | var index: Int? 34 | 35 | for (idx, objectToCompare) in self.enumerated() { 36 | if let to = objectToCompare as? U { 37 | if element === to { 38 | index = idx 39 | } 40 | } 41 | } 42 | 43 | if let index = index { 44 | self.remove(at: index) 45 | } 46 | } 47 | } 48 | 49 | extension Array { 50 | subscript (safe index: Index) -> Element? { 51 | return 0 <= index && index < count ? self[index] : nil 52 | } 53 | } 54 | 55 | extension Array { 56 | func crossJoin(array: [B], joiner: @escaping (A, B) -> C?) -> [C] { 57 | var results = [C]() 58 | for (i, a) in self.enumerated() { 59 | if let b = array[safe: i] { 60 | if let result = joiner(a as! A, b) { 61 | results.append(result) 62 | } 63 | } 64 | } 65 | return results 66 | } 67 | } 68 | 69 | -------------------------------------------------------------------------------- /install.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // Core-iOS-Application-Architecture 4 | // 5 | // Created by Alexey Artemev on 29/03/2019 6 | // Copyright © 2019 iDevs. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | func printInConsole(_ message:Any){ 12 | print("==> \(message)") 13 | } 14 | 15 | let fileManager = FileManager.default 16 | let homeDirectoryForCurrentUser = fileManager.homeDirectoryForCurrentUser.path 17 | let currentPath = fileManager.currentDirectoryPath 18 | let templatePath = "\(homeDirectoryForCurrentUser)/Library/Developer/Xcode/Templates/" 19 | 20 | let projectDir = "Project Templates/" 21 | let moduleDir = "File Templates/" 22 | 23 | let sourceProjectPath = "\(currentPath)/\(projectDir)" 24 | let sourceModulePath = "\(currentPath)/\(moduleDir)" 25 | 26 | let projectTemplatePath = "\(templatePath)/\(projectDir)" 27 | let moduleTemplatePath = "\(templatePath)/\(moduleDir)" 28 | 29 | func makeDir(path: String) { 30 | try? fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil) 31 | } 32 | 33 | func moveTemplate(fromPath: String, toPath: String) throws { 34 | let toURL = URL(fileURLWithPath:toPath) 35 | try _ = fileManager.removeItem(at: toURL) 36 | try _ = fileManager.copyItem(atPath: fromPath, toPath: toPath) 37 | } 38 | 39 | 40 | do { 41 | printInConsole("Install Project templates at \(projectTemplatePath)") 42 | makeDir(path: projectTemplatePath) 43 | try moveTemplate(fromPath: sourceProjectPath, toPath: projectTemplatePath) 44 | 45 | printInConsole("Install Module templates at \(moduleTemplatePath)") 46 | makeDir(path: moduleTemplatePath) 47 | try moveTemplate(fromPath: sourceModulePath, toPath: moduleTemplatePath) 48 | 49 | printInConsole("All templates have been successfully installed.") 50 | } catch let error as NSError { 51 | printInConsole("Could not install the templates. Reason: \(error.localizedFailureReason ?? "")") 52 | } 53 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Utils/Tweaks/UtilsTweak.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | func measureClosure(title: String, operation:()->()) { 13 | let startTime = CFAbsoluteTimeGetCurrent() 14 | operation() 15 | let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime 16 | print("Time elapsed for \(title): \(timeElapsed) s") 17 | } 18 | 19 | public func cycle(_ times: Int, closure: () -> ()) { 20 | for _ in 0..(_ minimum: T, maximum: T, value: T) -> T { 26 | return min( max(minimum, value), maximum) 27 | } 28 | 29 | public func degreesToRadians(_ degrees: Double) -> Double { 30 | return (degrees * Double.pi) / 180.0 31 | } 32 | 33 | func randomRange(lower: UInt32 , upper: UInt32) -> UInt32 { 34 | return lower + arc4random_uniform(upper - lower + 1) 35 | } 36 | 37 | func randomString(_ length: Int) -> String { 38 | 39 | let letters: NSString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" 40 | let len = UInt32(letters.length) 41 | 42 | var randomString = "" 43 | 44 | for _ in 0 ..< length { 45 | let rand = arc4random_uniform(len) 46 | var nextChar = letters.character(at: Int(rand)) 47 | randomString += NSString(characters: &nextChar, length: 1) as String 48 | } 49 | 50 | return randomString 51 | } 52 | 53 | public func printAllAvailableFonts() { 54 | let fontFamilyNames = UIFont.familyNames 55 | 56 | for familyName in fontFamilyNames { 57 | print("------------------------------") 58 | print("Font Family Name = [\(familyName)]") 59 | let names = UIFont.fontNames(forFamilyName: familyName ) 60 | print("Font Names = [\(names)]") 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/View/ViewBuilder/ViewBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | protocol ViewConfiguratorType: class { 12 | var key: String {get set} 13 | var isCachable: Bool {get set} 14 | func configureView(_ data: Any) -> UIViewController 15 | } 16 | 17 | class ViewConfigurator: ViewConfiguratorType { 18 | typealias ConfigureBlock = (T) -> UIViewController 19 | 20 | var key: String 21 | var isCachable: Bool 22 | let configure: ConfigureBlock 23 | 24 | init(key: String, cacheble: Bool = true, configure: @escaping ConfigureBlock) { 25 | self.key = key 26 | self.isCachable = cacheble 27 | self.configure = configure 28 | } 29 | 30 | func configureView(_ data: Any) -> UIViewController { 31 | if let data = data as? T { 32 | return configure(data) 33 | } else { 34 | fatalError("invalid data type") 35 | } 36 | } 37 | } 38 | 39 | class ViewBuilder { 40 | var items = [String : ViewConfiguratorType]() 41 | var cache = [String : ContainerView]() 42 | 43 | func register(_ configurator: ViewConfigurator) { 44 | items[configurator.key] = configurator 45 | } 46 | 47 | func build(forKey key: String, data: T) -> ContainerView? { 48 | guard let configure = items[key] else { return nil } 49 | var result: ContainerView? 50 | 51 | if let view = cache[key] { 52 | result = view 53 | } else { 54 | let controller = configure.configureView(data) 55 | let container = ContainerView() 56 | container.embedViewController(controller) 57 | if configure.isCachable { 58 | cache[key] = container 59 | } 60 | result = container 61 | } 62 | 63 | return result 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/View/UILabel+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable extension UILabel { 12 | func setCharacterSpacing(_ characterSpacing: CGFloat = 0.0) { 13 | guard let labelText = text else { return } 14 | 15 | let attributedString: NSMutableAttributedString 16 | if let labelAttributedText = attributedText { 17 | attributedString = NSMutableAttributedString(attributedString: labelAttributedText) 18 | } else { 19 | attributedString = NSMutableAttributedString(string: labelText) 20 | } 21 | 22 | // Character spacing attribute 23 | attributedString.addAttribute(NSAttributedString.Key.kern, value: characterSpacing, range: NSMakeRange(0, attributedString.length)) 24 | 25 | attributedText = attributedString 26 | } 27 | 28 | func setLineSpacing(_ lineSpacing: CGFloat = 0.0) { 29 | guard let labelText = text else { return } 30 | 31 | let paragraphStyle = NSMutableParagraphStyle() 32 | paragraphStyle.lineSpacing = lineSpacing 33 | 34 | let attributedString: NSMutableAttributedString 35 | if let labelAttributedText = attributedText { 36 | attributedString = NSMutableAttributedString(attributedString: labelAttributedText) 37 | } else { 38 | attributedString = NSMutableAttributedString(string: labelText) 39 | } 40 | 41 | // Character spacing attribute 42 | attributedString.addAttribute(NSAttributedString.Key.paragraphStyle, value: paragraphStyle, range: NSMakeRange(0, attributedString.length)) 43 | 44 | attributedText = attributedString 45 | } 46 | 47 | @IBInspectable public var kern_: CGFloat { 48 | set { 49 | self.setCharacterSpacing(newValue) 50 | } 51 | 52 | get { 53 | return 0 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Protocols/HUDRoutable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | import JGProgressHUD 11 | 12 | class HUDHolder { 13 | static let shared: HUDHolder = HUDHolder() 14 | 15 | private var hudStack: [UIViewController: JGProgressHUD] = [:] 16 | 17 | func add(hud: JGProgressHUD, controller: UIViewController) { 18 | hudStack[controller] = hud 19 | } 20 | 21 | func dismissCurrent(_ viewController: UIViewController) { 22 | let hud = hudStack[viewController] 23 | hud?.dismiss() 24 | hudStack.removeValue(forKey: viewController) 25 | } 26 | 27 | func dismissAll() { 28 | hudStack.forEach({$0.value.dismiss()}) 29 | } 30 | } 31 | 32 | 33 | protocol HUDRoutable: Presentable, BaseModuleRoutable { 34 | 35 | } 36 | 37 | extension HUDRoutable { 38 | func showProgress() { 39 | let view = self.coordinator.router.navigationController 40 | let progressHUD = JGProgressHUD(style: .dark) 41 | progressHUD.backgroundColor = UIColor.black.withAlphaComponent(0.3) 42 | progressHUD.animation = JGProgressHUDFadeZoomAnimation() 43 | progressHUD.show(in: view.view) 44 | HUDHolder.shared.add(hud: progressHUD, controller: view) 45 | } 46 | 47 | func showNotification(_ message: String) { 48 | let view = self.coordinator.router.navigationController 49 | let progressHUD = JGProgressHUD(style: .dark) 50 | progressHUD.backgroundColor = UIColor.black.withAlphaComponent(0.3) 51 | progressHUD.animation = JGProgressHUDFadeZoomAnimation() 52 | progressHUD.textLabel.text = message 53 | progressHUD.show(in: view.view) 54 | 55 | HUDHolder.shared.add(hud: progressHUD, controller: view) 56 | } 57 | 58 | func dismissHUD() { 59 | let view = self.coordinator.router.navigationController 60 | HUDHolder.shared.dismissCurrent(view) 61 | } 62 | 63 | func dismissAll() { 64 | HUDHolder.shared.dismissAll() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Themes/Theme.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | struct Theme { 13 | private static let lightThemeColors: [Color: String] = [ 14 | .red: "#ff0000", 15 | .orange: "#ff0000", 16 | .gray: "#ff0000", 17 | .black: "#000000", 18 | ] 19 | 20 | private static let darkThemeColors: [Color: String] = [ 21 | .red: "#ff0000", 22 | .orange: "#ff0000", 23 | .gray: "#ff0000", 24 | .black: "#ffffff", 25 | ] 26 | 27 | enum Color: String { 28 | case black 29 | case darkGray 30 | case lightGray 31 | case white 32 | case gray 33 | case red 34 | case green 35 | case blue 36 | case cyan 37 | case yellow 38 | case magenta 39 | case orange 40 | case purple 41 | case brown 42 | } 43 | 44 | static func color(_ type: Color) -> UIColor { 45 | var isDarkMode = false 46 | if #available(iOS 13.0, *) { isDarkMode = UITraitCollection.current.userInterfaceStyle == .dark } 47 | let colorCollection = isDarkMode ? darkThemeColors : lightThemeColors 48 | guard let hexColor = colorCollection[type] else { 49 | fatalError("not found color with type: \(type.rawValue)") 50 | } 51 | return UIColor(hex: hexColor) 52 | } 53 | 54 | enum Font: String { 55 | case light = "SFProDisplay-Light" 56 | case regular = "SFProDisplay-Regular" 57 | case medium = "SFProDisplay-Medium" 58 | case bold = "SFProDisplay-Bold" 59 | 60 | enum Size: CGFloat { 61 | case xxlarge = 30 62 | case xlarge = 25 63 | case large = 20 64 | case medium = 18 65 | case normal = 16 66 | case small = 12 67 | } 68 | 69 | func font(size: Size) -> UIFont? { 70 | let fontSize = size.rawValue 71 | let fontName = self.rawValue 72 | return UIFont(name: fontName, size: fontSize) 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/View/UIImage+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | 13 | /** 14 | Returns image with size 1x1px of certain color. 15 | */ 16 | class func imageWithColor(_ color: UIColor) -> UIImage { 17 | let rect = CGRect(x: 0.0, y: 0.0, width: 1.0, height: 1.0) 18 | UIGraphicsBeginImageContext(rect.size) 19 | let context = UIGraphicsGetCurrentContext() 20 | 21 | context!.setFillColor(color.cgColor) 22 | context!.fill(rect) 23 | 24 | let image = UIGraphicsGetImageFromCurrentImageContext() 25 | UIGraphicsEndImageContext() 26 | 27 | return image! 28 | } 29 | 30 | /** 31 | Returns current image colored to certain color. 32 | */ 33 | //@available(*, deprecated, message: "Use similar build-in XCAssetCatalog functionality.") 34 | func imageWithColor(_ color: UIColor) -> UIImage { 35 | UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale) 36 | 37 | let context = UIGraphicsGetCurrentContext() 38 | context!.translateBy(x: 0, y: self.size.height) 39 | context!.scaleBy(x: 1.0, y: -1.0) 40 | 41 | context!.setBlendMode(.normal) 42 | 43 | let rect = CGRect(x: 0, y: 0, width: self.size.width, height: self.size.height) 44 | context!.clip(to: rect, mask: self.cgImage!) 45 | color.setFill() 46 | context!.fill(rect) 47 | 48 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 49 | UIGraphicsEndImageContext() 50 | 51 | return newImage! 52 | } 53 | 54 | func resize(newWidth: CGFloat) -> UIImage? { 55 | 56 | let scale = newWidth / self.size.width 57 | let newHeight = self.size.height * scale 58 | UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight)) 59 | self.draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight)) 60 | 61 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 62 | UIGraphicsEndImageContext() 63 | 64 | return newImage 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/AppDelegate/Services/ApplicationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | class ApplicationService: NSObject, AppDelegateService { 12 | var window: UIWindow? 13 | let container: DependencyContainer 14 | 15 | lazy var appCoordinator: AppCoordinatorType = { 16 | return self.container.resolve(AppCoordinatorAssembly.self).build() 17 | }() 18 | 19 | init(container: DependencyContainer, window: UIWindow?) { 20 | self.container = container 21 | self.window = window 22 | } 23 | 24 | func application(_ application: UIApplication, didFinishLaunchingWithOptions 25 | launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 26 | 27 | // App Config 28 | let appConfig = container.resolve(AppConfigServiceAssembly.self).build() 29 | 30 | // Set defaults 31 | appConfig.registerDefaults() 32 | 33 | // Setup window 34 | window = UIWindow() 35 | window?.rootViewController = appCoordinator.toPresent() 36 | window?.backgroundColor = .white 37 | window?.makeKeyAndVisible() 38 | 39 | // Configure deep link 40 | let notification = launchOptions?[.remoteNotification] as? [String: AnyObject] 41 | let deepLink = DeepLinkOption.build(with: notification) 42 | appCoordinator.start(with: deepLink) 43 | 44 | //printAllAvailableFonts() 45 | 46 | return true 47 | } 48 | 49 | func application(_ application: UIApplication, didReceiveRemoteNotification 50 | userInfo: [AnyHashable : Any]) { 51 | 52 | // Configure deep link 53 | let dict = userInfo as? [String: AnyObject] 54 | let deepLink = DeepLinkOption.build(with: dict) 55 | appCoordinator.start(with: deepLink) 56 | } 57 | 58 | func application(_ application: UIApplication, continue 59 | userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { 60 | 61 | // Configure deep link 62 | let deepLink = DeepLinkOption.build(with: userActivity) 63 | appCoordinator.start(with: deepLink) 64 | 65 | return true 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/View/UICollectionView+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | extension UICollectionReusableView { 12 | static var autoReuseIdentifier: String { 13 | return NSStringFromClass(self) + "AutogenerateIdentifier" 14 | } 15 | } 16 | 17 | public extension UICollectionView { 18 | var currentPageNumber: Int { 19 | return Int(ceil(self.contentOffset.x / self.frame.size.width)) 20 | } 21 | 22 | func dequeue(cell: T.Type, indexPath: IndexPath) -> T? { 23 | return dequeueReusableCell(withReuseIdentifier: T.autoReuseIdentifier, for: indexPath) as? T 24 | } 25 | 26 | func dequeue(header: T.Type, indexPath: IndexPath) -> T? { 27 | return dequeueReusableSupplementaryView( 28 | ofKind: UICollectionView.elementKindSectionHeader, 29 | withReuseIdentifier: T.autoReuseIdentifier, 30 | for: indexPath) as? T 31 | } 32 | 33 | func dequeue(footer: T.Type, indexPath: IndexPath) -> T? { 34 | return dequeueReusableSupplementaryView( 35 | ofKind: UICollectionView.elementKindSectionFooter, 36 | withReuseIdentifier: T.autoReuseIdentifier, 37 | for: indexPath) as? T 38 | } 39 | 40 | func registerCell(_ cell: T.Type) { 41 | register(nibFromClass(cell), forCellWithReuseIdentifier: cell.autoReuseIdentifier) 42 | } 43 | 44 | func registerSectionHeader(_ header: T.Type) { 45 | register(nibFromClass(header), forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, 46 | withReuseIdentifier: header.autoReuseIdentifier) 47 | } 48 | 49 | func registerSectionFooter(_ footer: T.Type) { 50 | register(nibFromClass(footer), forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, 51 | withReuseIdentifier: footer.autoReuseIdentifier) 52 | } 53 | 54 | // Private 55 | 56 | fileprivate func nibFromClass(_ type: UICollectionReusableView.Type) -> UINib? { 57 | guard let nibName = NSStringFromClass(type).components(separatedBy: ".").last 58 | else { 59 | return nil 60 | } 61 | 62 | return UINib(nibName: nibName, bundle: nil) 63 | } 64 | } 65 | 66 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/View/UIViewController+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIViewController { 12 | func add(childViewController: UIViewController) { 13 | self.addChild(childViewController) 14 | self.view.addSubview(childViewController.view) 15 | childViewController.didMove(toParent: self) 16 | } 17 | 18 | func insert(childViewController: UIViewController, belowSubview subview: UIView) { 19 | self.addChild(childViewController) 20 | self.view.insertSubview(childViewController.view, belowSubview: subview) 21 | childViewController.didMove(toParent: self) 22 | } 23 | 24 | func insert(childViewController: UIViewController, aboveSubview subview: UIView) { 25 | self.addChild(childViewController) 26 | self.view.insertSubview(childViewController.view, aboveSubview: subview) 27 | childViewController.didMove(toParent: self) 28 | } 29 | 30 | func insert(childViewController: UIViewController, at index: Int) { 31 | self.addChild(childViewController) 32 | self.view.insertSubview(childViewController.view, at: index) 33 | childViewController.didMove(toParent: self) 34 | } 35 | 36 | func removeFromeParent() { 37 | self.willMove(toParent: nil) 38 | self.view.removeFromSuperview() 39 | self.removeFromParent() 40 | } 41 | } 42 | 43 | extension UIViewController { 44 | 45 | private class func instantiateControllerInStoryboard(_ storyboard: UIStoryboard, identifier: String) -> T { 46 | return storyboard.instantiateViewController(withIdentifier: identifier) as! T 47 | } 48 | 49 | class func controllerInStoryboard(_ storyboard: UIStoryboard, identifier: String) -> Self { 50 | return instantiateControllerInStoryboard(storyboard, identifier: identifier) 51 | } 52 | 53 | class func controllerInStoryboard(_ storyboard: UIStoryboard) -> Self { 54 | return controllerInStoryboard(storyboard, identifier: nameOfClass) 55 | } 56 | 57 | class func controllerFromStoryboard(_ storyboard: Storyboards) -> Self { 58 | return controllerInStoryboard(UIStoryboard(name: storyboard.rawValue, bundle: nil), identifier: nameOfClass) 59 | } 60 | 61 | class func controllerFromStoryboard(_ storyboardName: String) -> Self { 62 | return controllerInStoryboard(UIStoryboard(name: storyboardName, bundle: nil), identifier: nameOfClass) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Assemblys/Modules/Main/Storyboards/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /File Templates/iDevs.io/Module.xctemplate/___FILEBASENAME___/Storyboards/___FILEBASENAME___.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Protocols/AlertRoutable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | protocol AlertRoutable: Presentable { 12 | typealias AlertTextFieldHandler = (UITextField) -> Void 13 | 14 | func showAlert(title: String, message: String) 15 | func showAllert(config: AlertConfiguration, style: UIAlertController.Style) 16 | func showAlertWithTextField(configuration: TextFieldAlertConfiguration) 17 | } 18 | 19 | struct TextFieldAlertConfiguration { 20 | let title: String? 21 | var message: String? 22 | var placeholder: String? 23 | var handler: AlertRoutable.AlertTextFieldHandler 24 | } 25 | 26 | struct AlertConfiguration { 27 | typealias AlertCompletion = () -> Void 28 | typealias AlertActionHandler = (UIAlertAction) -> Void 29 | 30 | var title: String? 31 | var message: String? 32 | var actions: [UIAlertAction] 33 | var completion: AlertCompletion? 34 | 35 | init(title: String?, message: String?, completion: AlertCompletion? = nil) { 36 | self.init(title: title, message: message, actions: [], completion: completion) 37 | } 38 | 39 | init(title: String?, message: String?, actions: [UIAlertAction], completion: AlertCompletion? = nil) { 40 | self.title = title 41 | self.message = message 42 | self.actions = actions 43 | self.completion = completion 44 | } 45 | 46 | mutating func addAction(title: String?, style: UIAlertAction.Style, actionHandler: AlertActionHandler? = nil) { 47 | self.actions.append(UIAlertAction(title: title, style: style, handler: actionHandler)) 48 | } 49 | } 50 | 51 | extension AlertRoutable { 52 | func showAlert(title: String, message: String) { 53 | let source = toPresent() 54 | let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) 55 | let completeAction = UIAlertAction(title: "OK", style: .cancel, handler: nil) 56 | alertController.addAction(completeAction) 57 | source.present(alertController, animated: true, completion: nil) 58 | 59 | } 60 | 61 | func showAlertWithTextField(configuration: TextFieldAlertConfiguration) { 62 | 63 | let source = toPresent() 64 | let alertController = UIAlertController(title: configuration.title, message: configuration.message, preferredStyle: .alert) 65 | 66 | alertController.addTextField { (textField) in 67 | textField.placeholder = configuration.placeholder 68 | } 69 | 70 | let completeAction = UIAlertAction(title: "OK", style: .default, handler: { [weak alertController] (action) -> Void in 71 | if let textField = alertController?.textFields![0] { 72 | configuration.handler(textField) 73 | } 74 | }) 75 | let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil) 76 | 77 | alertController.addAction(completeAction) 78 | alertController.addAction(cancelAction) 79 | 80 | source.present(alertController, animated: true, completion: nil) 81 | 82 | } 83 | 84 | func showAllert(config: AlertConfiguration, style: UIAlertController.Style) { 85 | let alertController = UIAlertController(title: config.title, message: config.message, preferredStyle: style) 86 | config.actions.forEach({ (item) in 87 | alertController.addAction(item) 88 | }) 89 | let source = toPresent() 90 | source.present(alertController, animated: true, completion: config.completion) 91 | } 92 | } 93 | 94 | 95 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/View/UIView+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | @IBDesignable extension UIView { 12 | 13 | @IBInspectable public var cornerRadius: CGFloat { 14 | set { 15 | layer.cornerRadius = newValue 16 | } 17 | get { 18 | return layer.cornerRadius 19 | } 20 | } 21 | 22 | /* The color of the shadow. Defaults to opaque black. Colors created 23 | * from patterns are currently NOT supported. Animatable. */ 24 | @IBInspectable var shadowColor: UIColor? { 25 | set { 26 | layer.shadowColor = newValue!.cgColor 27 | } 28 | get { 29 | if let color = layer.shadowColor { 30 | return UIColor(cgColor: color) 31 | } else { 32 | return nil 33 | } 34 | } 35 | } 36 | 37 | /* The opacity of the shadow. Defaults to 0. Specifying a value outside the 38 | * [0,1] range will give undefined results. Animatable. */ 39 | @IBInspectable var shadowOpacity: Float { 40 | set { 41 | layer.shadowOpacity = newValue 42 | } 43 | get { 44 | return layer.shadowOpacity 45 | } 46 | } 47 | 48 | /* The shadow offset. Defaults to (0, -3). Animatable. */ 49 | @IBInspectable var shadowOffset: CGPoint { 50 | set { 51 | layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y) 52 | } 53 | get { 54 | return CGPoint(x: layer.shadowOffset.width, y: layer.shadowOffset.height) 55 | } 56 | } 57 | 58 | /* The blur radius used to create the shadow. Defaults to 3. Animatable. */ 59 | @IBInspectable var shadowRadius: CGFloat { 60 | set { 61 | layer.shadowRadius = newValue 62 | } 63 | get { 64 | return layer.shadowRadius 65 | } 66 | } 67 | 68 | @IBInspectable public var borderColor: UIColor? { 69 | set { 70 | layer.borderColor = newValue!.cgColor 71 | } 72 | get { 73 | if let color = layer.borderColor { 74 | return UIColor(cgColor: color) 75 | } else { 76 | return nil 77 | } 78 | } 79 | } 80 | @IBInspectable public var borderWidth: CGFloat { 81 | set { 82 | layer.borderWidth = newValue 83 | } 84 | get { 85 | return layer.borderWidth 86 | } 87 | } 88 | } 89 | 90 | extension UIView { 91 | public func shake(duration: CFTimeInterval = 0.5) { 92 | let animation = CAKeyframeAnimation(keyPath: "transform.translation.x") 93 | animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear) 94 | animation.values = [ -10, 10, -5 ] 95 | animation.duration = duration / Double(animation.values!.count) 96 | self.layer.add(animation, forKey: "shake") 97 | } 98 | } 99 | 100 | extension UIView { 101 | func setLinearGradient(startColor: UIColor, endColor: UIColor) -> CALayer { 102 | let gradientLayer = CAGradientLayer() 103 | gradientLayer.colors = [startColor.cgColor, endColor.cgColor] 104 | gradientLayer.locations = [0.0, 1.0] 105 | gradientLayer.frame = self.bounds 106 | 107 | 108 | self.layer.insertSublayer(gradientLayer, at: 0) 109 | return gradientLayer 110 | } 111 | } 112 | 113 | 114 | /// DEBUG! 115 | //extension UIView { 116 | // public func dump() { 117 | // Swift.print(perform(Selector(("recursiveDescription")))) 118 | // } 119 | //} 120 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/Router/Router.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | 12 | final class Router: NSObject, RouterType { 13 | typealias RouterCompletion = () -> Void 14 | private var completions: [UIViewController : RouterCompletion] 15 | 16 | public let navigationController: UINavigationController 17 | public var rootViewController: UIViewController? { 18 | return navigationController.viewControllers.first 19 | } 20 | 21 | public var hasRootController: Bool { 22 | return rootViewController != nil 23 | } 24 | 25 | public init(navigationController: UINavigationController = UINavigationController()) { 26 | self.navigationController = navigationController 27 | self.completions = [:] 28 | super.init() 29 | self.navigationController.delegate = self 30 | } 31 | 32 | func present(_ module: Presentable) { 33 | present(module, style: .overFullScreen) 34 | } 35 | 36 | func present(_ module: Presentable, style: UIModalPresentationStyle) { 37 | present(module, animated: true, style: style) 38 | } 39 | 40 | func present(_ module: Presentable, animated: Bool, style: UIModalPresentationStyle) { 41 | let controller = module.toPresent() 42 | controller.modalPresentationStyle = style 43 | navigationController.present(controller, animated: animated, completion: nil) 44 | } 45 | 46 | func dismissModule() { 47 | dismissModule(animated: true, completion: nil) 48 | } 49 | 50 | func dismissModule(animated: Bool, completion: RouterCompletion?) { 51 | navigationController.dismiss(animated: animated, completion: completion) 52 | } 53 | 54 | func push(_ module: Presentable) { 55 | push(module, animated: true) 56 | } 57 | 58 | func push(_ module: Presentable, hideBottomBar: Bool) { 59 | push(module, animated: true, hideBottomBar: hideBottomBar, completion: nil) 60 | } 61 | 62 | func push(_ module: Presentable, animated: Bool) { 63 | push(module, animated: animated, completion: nil) 64 | } 65 | 66 | func push(_ module: Presentable, animated: Bool, completion: RouterCompletion?) { 67 | push(module, animated: animated, hideBottomBar: false, completion: completion) 68 | } 69 | 70 | func push(_ module: Presentable, animated: Bool, hideBottomBar: Bool, completion: RouterCompletion?) { 71 | let controller = module.toPresent() 72 | guard controller is UINavigationController == false else { 73 | assertionFailure("Deprecated push UINavigationController.") 74 | return 75 | } 76 | 77 | if let completion = completion { 78 | completions[controller] = completion 79 | } 80 | 81 | controller.hidesBottomBarWhenPushed = hideBottomBar 82 | navigationController.pushViewController(controller, animated: animated) 83 | } 84 | 85 | func popModule() { 86 | popModule(animated: true) 87 | } 88 | 89 | func popModule(animated: Bool) { 90 | if let controller = navigationController.popViewController(animated: animated) { 91 | runCompletion(for: controller) 92 | } 93 | } 94 | 95 | func setRootModule(_ module: Presentable) { 96 | setRootModule(module, hideBar: false) 97 | } 98 | 99 | func setRootModule(_ module: Presentable, hideBar: Bool) { 100 | completions.forEach { $0.value() } 101 | navigationController.setViewControllers([module.toPresent()], animated: false) 102 | navigationController.isNavigationBarHidden = hideBar 103 | } 104 | 105 | func popToRootModule(animated: Bool) { 106 | if let controllers = navigationController.popToRootViewController(animated: animated) { 107 | controllers.forEach { runCompletion(for: $0) } 108 | } 109 | } 110 | 111 | private func runCompletion(for controller: UIViewController) { 112 | guard let completion = completions[controller] else { return } 113 | completion() 114 | completions.removeValue(forKey: controller) 115 | } 116 | 117 | // MARK: Presentable 118 | public func toPresent() -> UIViewController { 119 | return navigationController 120 | } 121 | } 122 | 123 | // MARK: UINavigationController Delegate 124 | extension Router: UINavigationControllerDelegate { 125 | public func navigationController(_ navigationController: UINavigationController, 126 | didShow viewController: UIViewController, animated: Bool) { 127 | 128 | guard let poppedViewController = navigationController.transitionCoordinator?.viewController(forKey: .from), 129 | !navigationController.viewControllers.contains(poppedViewController) else { 130 | return 131 | } 132 | 133 | runCompletion(for: poppedViewController) 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/View/UITableView+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import UIKit 10 | 11 | class TableViewPlaceholderLabel: UILabel { 12 | var inset: UIEdgeInsets = .zero 13 | 14 | override var intrinsicContentSize: CGSize { 15 | var size = super.intrinsicContentSize 16 | size.width += inset.left + inset.right 17 | size.height += inset.top + inset.bottom 18 | return size 19 | } 20 | 21 | override func drawText(in rect: CGRect) { 22 | super.drawText(in: rect.inset(by: self.inset)) 23 | } 24 | } 25 | 26 | extension UITableViewController { 27 | func placeholder(message:String, fontSize: CGFloat = 20) { 28 | let messageLabel = TableViewPlaceholderLabel() 29 | messageLabel.inset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) 30 | messageLabel.backgroundColor = .clear 31 | messageLabel.numberOfLines = 0 32 | messageLabel.font = .systemFont(ofSize: 20) 33 | messageLabel.textColor = UIColor.systemGray 34 | messageLabel.textAlignment = .center; 35 | messageLabel.text = message 36 | 37 | self.tableView.backgroundView = messageLabel 38 | } 39 | } 40 | 41 | extension UITableViewCell { 42 | static var autoReuseIdentifier: String { 43 | 44 | return NSStringFromClass(self) + "AutogeneratedIdentifier" 45 | } 46 | } 47 | 48 | public extension UITableView { 49 | 50 | func dequeue(cell: T.Type, indexPath: IndexPath) -> T? { 51 | return dequeueReusableCell(withIdentifier: T.autoReuseIdentifier, for: indexPath) as? T 52 | } 53 | 54 | func registerCell(_ cell: T.Type) { 55 | register(nibFromClass(cell), forCellReuseIdentifier: cell.autoReuseIdentifier) 56 | } 57 | 58 | // Private 59 | fileprivate func nibFromClass(_ type: UITableViewCell.Type) -> UINib? { 60 | guard let nibName = NSStringFromClass(type).components(separatedBy: ".").last 61 | else { 62 | return nil 63 | } 64 | 65 | return UINib(nibName: nibName, bundle: nil) 66 | } 67 | } 68 | 69 | extension UITableView { 70 | var visibleSectionHeaders: [UITableViewHeaderFooterView] { 71 | get { 72 | var headerViews = [UITableViewHeaderFooterView]() 73 | if let visibleRows = self.indexPathsForVisibleRows { 74 | let visibleSections = visibleRows.map({$0.section}) 75 | visibleSections.forEach { (index) in 76 | if let view = self.headerView(forSection: index) { 77 | headerViews.append(view) 78 | } 79 | } 80 | 81 | } 82 | return headerViews 83 | } 84 | } 85 | 86 | private func indexesOfVisibleSections() -> Array { 87 | var visibleSectionIndexes = [Int]() 88 | 89 | for index in 0...numberOfSections { 90 | var headerRect: CGRect? 91 | 92 | if (self.style == .plain) { 93 | headerRect = self.rect(forSection: index) 94 | } else { 95 | headerRect = self.rectForHeader(inSection: index) 96 | } 97 | 98 | if headerRect != nil { 99 | let visiblePartOfTableView: CGRect = CGRect( 100 | x: self.contentOffset.x, 101 | y: self.contentOffset.y, 102 | width: self.bounds.size.width, 103 | height: self.bounds.size.height 104 | ) 105 | 106 | if (visiblePartOfTableView.intersects(headerRect!)) { 107 | visibleSectionIndexes.append(index) 108 | } 109 | } 110 | } 111 | 112 | return visibleSectionIndexes 113 | } 114 | } 115 | 116 | extension UITableView { 117 | func layoutTableHeaderView() { 118 | 119 | guard let headerView = self.tableHeaderView else { return } 120 | headerView.translatesAutoresizingMaskIntoConstraints = false 121 | 122 | let headerWidth = headerView.bounds.size.width; 123 | let temporaryWidthConstraints = NSLayoutConstraint.constraints(withVisualFormat: "[headerView(width)]", options: NSLayoutConstraint.FormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["headerView": headerView]) 124 | 125 | headerView.addConstraints(temporaryWidthConstraints) 126 | 127 | headerView.setNeedsLayout() 128 | headerView.layoutIfNeeded() 129 | 130 | let headerSize = headerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) 131 | let height = headerSize.height 132 | var frame = headerView.frame 133 | 134 | frame.size.height = height 135 | headerView.frame = frame 136 | 137 | self.tableHeaderView = headerView 138 | 139 | headerView.removeConstraints(temporaryWidthConstraints) 140 | headerView.translatesAutoresizingMaskIntoConstraints = true 141 | } 142 | 143 | func layoutTableFooterView() { 144 | 145 | guard let footerView = self.tableFooterView else { return } 146 | footerView.translatesAutoresizingMaskIntoConstraints = false 147 | 148 | let headerWidth = footerView.bounds.size.width; 149 | let temporaryWidthConstraints = NSLayoutConstraint.constraints(withVisualFormat: "[footerView(width)]", options: NSLayoutConstraint.FormatOptions(rawValue: UInt(0)), metrics: ["width": headerWidth], views: ["footerView": footerView]) 150 | 151 | footerView.addConstraints(temporaryWidthConstraints) 152 | 153 | footerView.setNeedsLayout() 154 | footerView.layoutIfNeeded() 155 | 156 | let footerSize = footerView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) 157 | let height = footerSize.height 158 | var frame = footerView.frame 159 | 160 | frame.size.height = height 161 | footerView.frame = frame 162 | 163 | self.tableFooterView = footerView 164 | 165 | footerView.removeConstraints(temporaryWidthConstraints) 166 | footerView.translatesAutoresizingMaskIntoConstraints = true 167 | } 168 | } 169 | 170 | 171 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/Base.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.Xcode3.ProjectTemplateUnitKind 7 | Identifier 8 | io.idevs.dt.unit.cocoaTouchApplicationBase 9 | Ancestors 10 | 11 | com.apple.dt.unit.applicationBase 12 | com.apple.dt.unit.iosBase 13 | 14 | 15 | Targets 16 | 17 | 18 | TargetIdentifier 19 | com.apple.dt.cocoaTouchApplicationTarget 20 | SharedSettings 21 | 22 | ASSETCATALOG_COMPILER_APPICON_NAME 23 | AppIcon 24 | LD_RUNPATH_SEARCH_PATHS 25 | $(inherited) @executable_path/Frameworks 26 | 27 | 28 | 29 | Options 30 | 31 | 32 | Identifier 33 | universalDeviceFamily 34 | Name 35 | Devices: 36 | Description 37 | Which device family to create a project for 38 | SortOrder 39 | 1 40 | Type 41 | popup 42 | Default 43 | Universal 44 | Values 45 | 46 | Universal 47 | iPhone 48 | iPad 49 | 50 | Units 51 | 52 | iPhone 53 | 54 | Nodes 55 | 56 | Info.plist:UISupportedInterfaceOrientations~iPhone 57 | Resources/Assets.xcassets 58 | 59 | Definitions 60 | 61 | Resources/Assets.xcassets 62 | 63 | Group 64 | 65 | Resources 66 | 67 | Path 68 | Images-iPhone.xcassets 69 | SortOrder 70 | 100 71 | 72 | 73 | 74 | iPad 75 | 76 | Project 77 | 78 | SharedSettings 79 | 80 | TARGETED_DEVICE_FAMILY 81 | 2 82 | 83 | 84 | Nodes 85 | 86 | Info.plist:UISupportedInterfaceOrientations~iPad 87 | Resources/Assets.xcassets 88 | 89 | Definitions 90 | 91 | Resources/Assets.xcassets 92 | 93 | Group 94 | 95 | Resources 96 | 97 | Path 98 | Images-iPad.xcassets 99 | SortOrder 100 | 100 101 | 102 | 103 | 104 | Universal 105 | 106 | Project 107 | 108 | SharedSettings 109 | 110 | TARGETED_DEVICE_FAMILY 111 | 1,2 112 | 113 | 114 | Nodes 115 | 116 | Info.plist:UISupportedInterfaceOrientations~iPhone 117 | Info.plist:UISupportedInterfaceOrientations~iPad 118 | Resources/Assets.xcassets 119 | 120 | Definitions 121 | 122 | Resources/Assets.xcassets 123 | 124 | Group 125 | 126 | Resources 127 | 128 | Path 129 | Images-Universal.xcassets 130 | SortOrder 131 | 100 132 | 133 | 134 | 135 | 136 | 137 | 138 | Identifier 139 | hasUnitTests 140 | Name 141 | Include Unit Tests 142 | NotPersisted 143 | 144 | SortOrder 145 | 100 146 | Type 147 | checkbox 148 | Default 149 | true 150 | Units 151 | 152 | true 153 | 154 | Components 155 | 156 | 157 | Identifier 158 | com.apple.dt.unit.cocoaTouchApplicationUnitTestBundle 159 | Name 160 | ___PACKAGENAME___Tests 161 | 162 | 163 | 164 | 165 | 166 | 167 | Identifier 168 | hasUITests 169 | Name 170 | Include UI Tests 171 | NotPersisted 172 | 173 | SortOrder 174 | 101 175 | Type 176 | checkbox 177 | Default 178 | true 179 | Units 180 | 181 | true 182 | 183 | Components 184 | 185 | 186 | Identifier 187 | com.apple.dt.unit.cocoaTouchApplicationUITestBundle 188 | Name 189 | ___PACKAGENAME___UITests 190 | 191 | 192 | 193 | 194 | 195 | 196 | Identifier 197 | languageChoice 198 | Units 199 | 200 | Swift 201 | 202 | Nodes 203 | 204 | Definitions 205 | 206 | 207 | 208 | 209 | 210 | Nodes 211 | 212 | Info.plist:iPhone 213 | Info.plist:UIRequiredDeviceCapabilities:base 214 | Info.plist:LaunchScreen 215 | Base.lproj/LaunchScreen.storyboard 216 | 217 | Definitions 218 | 219 | Info.plist:iPhone 220 | <key>LSRequiresIPhoneOS</key> 221 | <true/> 222 | Info.plist:UIRequiredDeviceCapabilities 223 | 224 | Beginning 225 | <key>UIRequiredDeviceCapabilities</key> 226 | <array> 227 | End 228 | </array> 229 | Indent 230 | 1 231 | 232 | Info.plist:UIRequiredDeviceCapabilities:base 233 | <string>armv7</string> 234 | Info.plist:statusBarTintForNavBar 235 | <key>UIStatusBarTintParameters</key> 236 | <dict> 237 | <key>UINavigationBar</key> 238 | <dict> 239 | <key>Style</key> 240 | <string>UIBarStyleDefault</string> 241 | <key>Translucent</key> 242 | <false/> 243 | </dict> 244 | </dict> 245 | 246 | Info.plist:LaunchScreen 247 | <key>UILaunchStoryboardName</key> 248 | <string>LaunchScreen</string> 249 | 250 | Base.lproj/LaunchScreen.storyboard 251 | 252 | Group 253 | 254 | Supporting Files 255 | 256 | Path 257 | LaunchScreen.storyboard 258 | SortOrder 259 | 101 260 | 261 | 262 | 263 | 264 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Common/AppDelegateManager/AppDelegateManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegateManager.swift 3 | // InstaViewer 4 | // 5 | // Created by Bart on 18.10.2019 6 | // Copyright © 2019 iDevs.io. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol AppDelegateService: UIApplicationDelegate {} 12 | 13 | class AppDelegateManager: UIResponder, UIApplicationDelegate { 14 | var services: [AppDelegateService] { return [] } 15 | lazy private var _services: [AppDelegateService] = { 16 | return services 17 | }() 18 | } 19 | 20 | extension AppDelegateManager { 21 | @available(iOS 2.0, *) 22 | open func applicationDidFinishLaunching(_ application: UIApplication) { 23 | _services.forEach { $0.applicationDidFinishLaunching?(application) } 24 | } 25 | 26 | @available(iOS 6.0, *) 27 | open func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 28 | var result = false 29 | for service in _services { 30 | if service.application?(application, willFinishLaunchingWithOptions: launchOptions) ?? false { 31 | result = true 32 | } 33 | } 34 | return result 35 | } 36 | 37 | @available(iOS 3.0, *) 38 | open func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { 39 | var result = false 40 | for service in _services { 41 | if service.application?(application, didFinishLaunchingWithOptions: launchOptions) ?? false { 42 | result = true 43 | } 44 | } 45 | return result 46 | } 47 | 48 | 49 | @available(iOS 2.0, *) 50 | open func applicationDidBecomeActive(_ application: UIApplication) { 51 | for service in _services { 52 | service.applicationDidBecomeActive?(application) 53 | } 54 | } 55 | 56 | @available(iOS 2.0, *) 57 | open func applicationWillResignActive(_ application: UIApplication) { 58 | for service in _services { 59 | service.applicationWillResignActive?(application) 60 | } 61 | } 62 | 63 | @available(iOS 9.0, *) 64 | open func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { 65 | var result = false 66 | for service in _services { 67 | if service.application?(app, open: url, options: options) ?? false { 68 | result = true 69 | } 70 | } 71 | return result 72 | } 73 | 74 | @available(iOS 2.0, *) 75 | open func applicationDidReceiveMemoryWarning(_ application: UIApplication) { 76 | for service in _services { 77 | service.applicationDidReceiveMemoryWarning?(application) 78 | } 79 | } 80 | 81 | @available(iOS 2.0, *) 82 | open func applicationWillTerminate(_ application: UIApplication) { 83 | for service in _services { 84 | service.applicationWillTerminate?(application) 85 | } 86 | } 87 | 88 | @available(iOS 2.0, *) 89 | open func applicationSignificantTimeChange(_ application: UIApplication) { 90 | for service in _services { 91 | service.applicationSignificantTimeChange?(application) 92 | } 93 | } 94 | 95 | @available(iOS, introduced: 2.0, deprecated: 13.0) 96 | open func application(_ application: UIApplication, willChangeStatusBarOrientation newStatusBarOrientation: UIInterfaceOrientation, duration: TimeInterval) { 97 | for service in _services { 98 | service.application?(application, willChangeStatusBarOrientation: newStatusBarOrientation, duration: duration) 99 | } 100 | } 101 | 102 | @available(iOS, introduced: 2.0, deprecated: 13.0) 103 | open func application(_ application: UIApplication, didChangeStatusBarOrientation oldStatusBarOrientation: UIInterfaceOrientation) { 104 | for service in _services { 105 | service.application?(application, didChangeStatusBarOrientation: oldStatusBarOrientation) 106 | } 107 | } 108 | 109 | 110 | @available(iOS 3.0, *) 111 | open func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { 112 | for service in _services { 113 | service.application?(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) 114 | } 115 | } 116 | 117 | 118 | @available(iOS 3.0, *) 119 | open func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { 120 | for service in _services { 121 | service.application?(application, didFailToRegisterForRemoteNotificationsWithError: error) 122 | } 123 | } 124 | 125 | 126 | @available(iOS 9.0, *) 127 | open func applicationShouldRequestHealthAuthorization(_ application: UIApplication) { 128 | for service in _services { 129 | service.applicationShouldRequestHealthAuthorization?(application) 130 | } 131 | } 132 | 133 | 134 | @available(iOS 4.0, *) 135 | open func applicationDidEnterBackground(_ application: UIApplication) { 136 | for service in _services { 137 | service.applicationDidEnterBackground?(application) 138 | } 139 | } 140 | 141 | @available(iOS 4.0, *) 142 | open func applicationWillEnterForeground(_ application: UIApplication) { 143 | for service in _services { 144 | service.applicationWillEnterForeground?(application) 145 | } 146 | } 147 | 148 | 149 | @available(iOS 4.0, *) 150 | open func applicationProtectedDataWillBecomeUnavailable(_ application: UIApplication) { 151 | for service in _services { 152 | service.applicationProtectedDataWillBecomeUnavailable?(application) 153 | } 154 | } 155 | 156 | @available(iOS 4.0, *) 157 | open func applicationProtectedDataDidBecomeAvailable(_ application: UIApplication) { 158 | for service in _services { 159 | service.applicationProtectedDataDidBecomeAvailable?(application) 160 | } 161 | } 162 | 163 | 164 | // Applications may reject specific types of extensions based on the extension point identifier. 165 | // Constants representing common extension point identifiers are provided further down. 166 | // If unimplemented, the default behavior is to allow the extension point identifier. 167 | @available(iOS 8.0, *) 168 | open func application(_ application: UIApplication, shouldAllowExtensionPointIdentifier extensionPointIdentifier: UIApplication.ExtensionPointIdentifier) -> Bool { 169 | var result = false 170 | for service in _services { 171 | if service.application?(application, shouldAllowExtensionPointIdentifier: extensionPointIdentifier) ?? true { 172 | result = true 173 | } 174 | } 175 | return result 176 | } 177 | 178 | 179 | // Called on the main thread as soon as the user indicates they want to continue an activity in your application. The NSUserActivity object may not be available instantly, 180 | // so use this as an opportunity to show the user that an activity will be continued shortly. 181 | // For each application:willContinueUserActivityWithType: invocation, you are guaranteed to get exactly one invocation of application:continueUserActivity: on success, 182 | // or application:didFailToContinueUserActivityWithType:error: if an error was encountered. 183 | @available(iOS 8.0, *) 184 | open func application(_ application: UIApplication, willContinueUserActivityWithType userActivityType: String) -> Bool { 185 | var result = false 186 | for service in _services { 187 | if service.application?(application, willContinueUserActivityWithType: userActivityType) ?? false { 188 | result = true 189 | } 190 | } 191 | return result 192 | } 193 | 194 | 195 | // Called on the main thread after the NSUserActivity object is available. Use the data you stored in the NSUserActivity object to re-create what the user was doing. 196 | // You can create/fetch any restorable objects associated with the user activity, and pass them to the restorationHandler. They will then have the UIResponder restoreUserActivityState: method 197 | // invoked with the user activity. Invoking the restorationHandler is optional. It may be copied and invoked later, and it will bounce to the main thread to complete its work and call 198 | // restoreUserActivityState on all objects. 199 | @available(iOS 8.0, *) 200 | open func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Swift.Void) -> Bool { 201 | let returns = apply({ (service, restorationHandler) -> Bool? in 202 | service.application?(application, continue: userActivity, restorationHandler: restorationHandler) 203 | }, completionHandler: { results in 204 | let result = results.reduce([], { $0 + ($1 ?? []) }) 205 | restorationHandler(result) 206 | }) 207 | 208 | return returns.reduce(false, { $0 || $1 }) 209 | } 210 | 211 | 212 | // If the user activity cannot be fetched after willContinueUserActivityWithType is called, this will be called on the main thread when implemented. 213 | @available(iOS 8.0, *) 214 | open func application(_ application: UIApplication, didFailToContinueUserActivityWithType userActivityType: String, error: Error) { 215 | for service in _services { 216 | service.application?(application, didFailToContinueUserActivityWithType: userActivityType, error: error) 217 | } 218 | } 219 | 220 | 221 | // This is called on the main thread when a user activity managed by UIKit has been updated. You can use this as a last chance to add additional data to the userActivity. 222 | @available(iOS 8.0, *) 223 | open func application(_ application: UIApplication, didUpdate userActivity: NSUserActivity) { 224 | for service in _services { 225 | service.application?(application, didUpdate: userActivity) 226 | } 227 | } 228 | } 229 | 230 | extension AppDelegateManager { 231 | @discardableResult 232 | private func apply(_ work: (AppDelegateService, @escaping (T) -> Void) -> S?, 233 | completionHandler: @escaping ([T]) -> Void) -> [S] { 234 | let dispatchGroup = DispatchGroup() 235 | var results: [T] = [] 236 | var returns: [S] = [] 237 | 238 | for service in _services { 239 | dispatchGroup.enter() 240 | let returned = work(service, { result in 241 | results.append(result) 242 | dispatchGroup.leave() 243 | }) 244 | if let returned = returned { 245 | returns.append(returned) 246 | } else { // delegate doesn't impliment method 247 | dispatchGroup.leave() 248 | } 249 | } 250 | 251 | dispatchGroup.notify(queue: .main) { 252 | completionHandler(results) 253 | } 254 | 255 | return returns 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Core-iOS-Application-Architecture](/images/header.png) 2 | 3 | ## Overview 4 | 5 | MVP and MVC modules template has been deprecated! 6 | 7 | Core iOS Application Architecture - The development paradigm of clean, testable code and modular iOS applications. 8 | 9 | This repository contains Xcode templates for quickly creating a project, modules, services and coordinator. 10 | 11 | 12 | ![Core-Architecture](/images/core.png) 13 | 14 | ## Contents 15 | 16 | * [Requirements](#requirements) 17 | + [Example project](#example-project) 18 | * [Usage](#usage) 19 | + [Project structure](#project-structure) 20 | + [Project Template](#project-template) 21 | + [Module Template](#module-template) 22 | + [Service Template](#service-template-soa) 23 | + [Coordinator Template](#coordinator-template) 24 | * [Code Style](#code-style) 25 | + [Naming](#naming) 26 | * [Installation](#installation) 27 | * [Author](#author) 28 | * [License](#license) 29 | 30 | 31 | ## Requirements 32 | 33 | * Xcode 10+ 34 | * Swift 4.2+ 35 | 36 | ### Example project 37 | 38 | [Download](https://github.com/bartleby/CAA-Exemple-Project) example project built on the basis of this paradigm. 39 | 40 | 41 | ## Usage 42 | 43 | #### Create new Project 44 | 45 | * Open Xcode 46 | * File > New > Project or press shortcuts ⇧⌘N 47 | * Choice MVC, MVP or VIPER Architecture 48 | * After you have created a project, you need to remove reference on the folder "Classes" 49 | - Highlight the Classes folder in the Xcode Project Navigator 50 | - Press Backspace Key 51 | - Press "Remove Reference" in alert window 52 | * Now you need to return the "Classes" folder to the project. 53 | - Drag the "Classes" folder from the Finder to the Xcode project 54 | * Profit! 🎉 55 | 56 | #### Create new Module, Service or Coordinator 57 | 58 | * Open Xcode Project 59 | * Select Modules, Services or Coordinators Group in Xcode Project Navigator 60 | * Create new file 61 | - File > New > File... or press shortcuts ⌘N 62 | - Choice Module (MVC, MVP or VIPER) Service or Coordinator 63 | - Enter Name (if you wont to create "Service" you must specify at the end of the name "Service" for example - NetworkService or SettingsService) 64 | * After you have created a Service, Module or Coordinator, you need to remove reference on the folder 65 | - Highlight the Folder in the Xcode Project Navigator 66 | - Press Backspace Key 67 | - Press "Remove Reference" in alert window 68 | * Now you need to return your Folder to the project. 69 | - Drag the Folder from the Finder to the Xcode project 70 | * Profit! 🎉 71 | 72 | Important! you need to add your Service, Module or Coordinator to the DI Container in AppDelegate.swift 73 | 74 | ```Swift 75 | // Setup Coordinators 76 | container.apply(AppCoordinatorAssembly.self) 77 | container.apply(MainCoordinatorAssembly.self) 78 | // add your coordinator here 79 | 80 | // Setup Modules 81 | container.apply(MainAssembly.self) 82 | // add your module here 83 | 84 | // Setup Services 85 | container.apply(AppConfigServiceAssembly.self) 86 | container.apply(EnvironmentServiceAssembly.self) 87 | // add your service here 88 | ``` 89 | 90 | ### Project Template 91 | 92 | 93 | 94 | 95 | 96 |
97 |
98 |
99 |
100 | 101 | There are 3 project templates, MVC, MVP and VIPER, the templates differ only in the main module architecture. 102 | This does not affect the continued use of modules, you can create any modules based on the complexity of your screen. 103 | For example, you plan a small project, then your choice is a project based on the MVP pattern. 104 | 105 | #### Each project template includes: 106 | - DeepLink out of the box 107 | - Launch Instructor 108 | - Plugable AppDelegate 109 | - [Swilby](https://github.com/bartleby/Swilby) Library for DI 110 | - Toolkit, Tweaks 111 | - Extensions 112 | - Router 113 | - etc. 114 | 115 | ### Project structure 116 | 117 | * AppDelegate 118 | - Services 119 | * Assemblys 120 | - Coordinators 121 | - Modules 122 | - Services 123 | * Constants 124 | * Library 125 | - Swilby 126 | * Utils 127 | - Tweaks 128 | * Extensions 129 | - Collection 130 | - Numbers 131 | - Codable 132 | - Color 133 | - Date 134 | - URL 135 | - FileManager 136 | - View 137 | - String 138 | - NSObject 139 | * Common 140 | - AppDelegateManager 141 | - Coordinator 142 | - Module 143 | - ActionCloud 144 | - View 145 | - Protocols 146 | - DeepLink 147 | - Router 148 | 149 | 150 | 151 | ### Module Template 152 | 153 | 154 | 155 | 156 | 157 |
158 |
159 |
160 |
161 | 162 | You can use different modules in one project based on the complexity of your screen. 163 | One screen - one module. 164 | For example, you need to create a simple screen on which there will be a WebView with some information - your choice of MVC module, 165 | If the screen assumes complex business logic, interaction with the server, etc., your choice is the VIPER module. 166 | 167 | All your modules should be in the "Modules" folder along the path "Classes/Assemblys/Modules" 168 | 169 | #### VIPER Module structure 170 | 171 | * Module 172 | - Storyboards 173 | - Assembly 174 | - Presenter 175 | - Interactor 176 | - View 177 | - Protocols 178 | 179 | #### MVP Module structure 180 | 181 | * Module 182 | - Storyboards 183 | - Assembly 184 | - Presenter 185 | - View 186 | - Protocols 187 | 188 | #### MVC Module structure 189 | 190 | * Module 191 | - Storyboards 192 | - Assembly 193 | - View 194 | - Protocols 195 | 196 | ### Service Template (SoA) 197 | 198 | 199 | 200 |
201 |
202 |
203 |
204 | 205 | Each service is engaged in its own business: the authorization service works with authorization, the user service with user data and so on. A good rule (a specific service works with one type of entity) is separation from the server side into different path: /auth, /user, /settings, but this is not necessary. 206 | 207 | All your services should be in the "Services" folder along the path "Classes/Assemblys/Services" 208 | 209 | You can learn more about the principle of developing SoA from [wikipedia](https://en.wikipedia.org/wiki/Service-oriented_architecture) 210 | 211 | 212 | ### Coordinator Template 213 | 214 | 215 | 216 |
217 |
218 |
219 |
220 | 221 | Coordinator is an object that handles navigation flow and shares flow’s handling for the next coordinator after switching on the next chain. It means that coordinators should only keep navigation logic between screens. Coordinators can keep references to a storage, which holds data that is used by factories for creating modules, but they can never handle a module’s business logic and never drive a cell’s behavior. 222 | 223 | All your coordinators should be in the "Coordinators" folder along the path "Classes/Assemblys/Coordinators" 224 | 225 | You can learn more about the coordinators from the [article](https://medium.com/blacklane-engineering/coordinators-essential-tutorial-part-i-376c836e9ba7) by Andrey Panov. 226 | 227 | 228 | ## Code Style 229 | 230 | ### Naming 231 | 232 | Good clean code also depends on the correct naming. 233 | 234 | ###### Wrong! 235 | ```Swift 236 | @IBOutlet weak var descriptionText: UITextView! 237 | @IBOutlet weak var image: UIImageView! 238 | @IBOutlet weak var avatarIcon: UIImageView! 239 | @IBOutlet weak var labelTitle: UILabel! 240 | @IBOutlet weak var contentView: UIScrollView! 241 | 242 | ``` 243 | 244 | ###### Right! 245 | ```Swift 246 | 247 | @IBOutlet weak var descriptionTextView: UITextView! 248 | @IBOutlet weak var avatarImageView: UIImageView! 249 | @IBOutlet weak var titleLabel: UILabel! 250 | @IBOutlet weak var contentScrollView: UIScrollView! 251 | 252 | ``` 253 | 254 | ###### Wrong! 255 | ```Swift 256 | 257 | let vc = NewsViewController() 258 | let msg = Message() 259 | let shape1 = Shape() 260 | 261 | ``` 262 | 263 | ###### Right! 264 | ```Swift 265 | 266 | let newsViewController = NewsViewController() 267 | let message = Message() 268 | let square = Shape() 269 | 270 | ``` 271 | 272 | ###### Wrong! 273 | ```Swift 274 | 275 | view.hidden 276 | element.expanded 277 | array.empty 278 | 279 | ``` 280 | 281 | ###### Right! 282 | ```Swift 283 | 284 | view.isHidden 285 | element.isExpanded 286 | array.isEmpty 287 | 288 | ``` 289 | 290 | #### View Output 291 | 292 | ###### Wrong! 293 | ```Swift 294 | func viewLoaded() 295 | func authButtonTap() 296 | func textChanged(text: String) 297 | 298 | ``` 299 | 300 | ###### Right! 301 | ```Swift 302 | 303 | func viewDidLoad() 304 | func authButtonDidTap() 305 | func usernameTextFieldDidChange(value: String) 306 | 307 | ``` 308 | 309 | #### Module Output 310 | 311 | ###### Wrong! 312 | ```Swift 313 | 314 | var authCancel: (() -> Void)? { get set } 315 | var authComplete: ((String) -> Void)? { get set } 316 | var userData: ((UserData) -> Void)? { get set } 317 | 318 | ``` 319 | 320 | ###### Right! 321 | ```Swift 322 | 323 | var onAuthCanceled: (() -> Void)? { get set } 324 | var onAuthCompleted: ((String) -> Void)? { get set } 325 | var onUserDataCompleted: ((UserData) -> Void)? { get set } 326 | 327 | ``` 328 | 329 | #### Module Input 330 | 331 | ###### Wrong! 332 | ```Swift 333 | 334 | func token(_ token: String) 335 | func pageIndex(_ i: Int) 336 | 337 | ``` 338 | 339 | ###### Right! 340 | ```Swift 341 | 342 | func set(token: String) 343 | func setPage(index: Int) 344 | 345 | ``` 346 | 347 | #### Interactor Input 348 | 349 | ###### Wrong! 350 | ```Swift 351 | 352 | func getUser(_ completion: (User) -> Void) 353 | func obtainUser(_ completion: (User) -> ()) 354 | func config() -> Config 355 | 356 | ``` 357 | 358 | ###### Right! 359 | 360 | ```Swift 361 | 362 | func obtainUser(_ completion: (User) -> Void) 363 | func obtainConfig(_ completion: (Config) -> Void) 364 | 365 | ``` 366 | 367 | 368 | ## Installation 369 | 370 | Only need execute this command in terminal: 371 | 372 | ```swift 373 | swift install.swift 374 | ``` 375 | 376 | ## Author 377 | 378 | 🦆 Alex Artemev [www.iDevs.io](https://iDevs.io) 379 | 380 | 381 | ## License 382 | 383 | MIT License 384 | 385 | Copyright (c) 2019 Alex Artemev (iDevs.io) 386 | 387 | Permission is hereby granted, free of charge, to any person obtaining a copy 388 | of this software and associated documentation files (the "Software"), to deal 389 | in the Software without restriction, including without limitation the rights 390 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 391 | copies of the Software, and to permit persons to whom the Software is 392 | furnished to do so, subject to the following conditions: 393 | 394 | The above copyright notice and this permission notice shall be included in all 395 | copies or substantial portions of the Software. 396 | 397 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 398 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 399 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 400 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 401 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 402 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 403 | SOFTWARE. 404 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/Classes/Extensions/FileManager/FileManager+Ext.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | // MARK: - FileManager extension 12 | 13 | /// This extension adds some useful functions to FileManager. 14 | extension FileManager { 15 | // MARK: - Variables 16 | 17 | /// Path type enum. 18 | /// 19 | /// - mainBundle: Main bundle path. 20 | /// - library: Library path. 21 | /// - documents: Documents path. 22 | /// - cache: Cache path. 23 | enum PathType: Equatable { 24 | case mainBundle 25 | case library 26 | case documents 27 | case cache 28 | case applicationSupport 29 | case source(String) 30 | 31 | static func ==(lhs: PathType, rhs: PathType) -> Bool { 32 | switch (lhs, rhs) { 33 | case let (.source(a), .source(b)): 34 | return a == b 35 | case (.mainBundle, .mainBundle): 36 | return true 37 | case (.library, .library): 38 | return true 39 | case (.documents, .documents): 40 | return true 41 | case (.cache, .cache): 42 | return true 43 | case (.applicationSupport, .applicationSupport): 44 | return true 45 | default: 46 | return false 47 | } 48 | } 49 | } 50 | 51 | enum FileManagerError: Error { 52 | case pathNotExist 53 | case pathNotAllowed 54 | } 55 | 56 | // MARK: - Functions 57 | 58 | /// Get the path for a PathType. 59 | /// 60 | /// - Parameter path: Path type. 61 | /// - Returns: Returns the path type String. 62 | func pathFor(_ path: PathType) -> String? { 63 | var pathString: String? 64 | 65 | switch path { 66 | case .mainBundle: 67 | pathString = self.mainBundlePath() 68 | case .library: 69 | pathString = self.libraryPath() 70 | case .documents: 71 | pathString = self.documentsPath() 72 | case .cache: 73 | pathString = self.cachePath() 74 | case .applicationSupport: 75 | pathString = self.applicationSupportPath() 76 | case .source(let src): 77 | pathString = src 78 | } 79 | 80 | 81 | return pathString 82 | } 83 | 84 | /// Save a file with given content. 85 | /// 86 | /// - Parameters: 87 | /// - file: File to be saved. 88 | /// - path: File path. 89 | /// - content: Content to be saved. 90 | /// - Throws: write(toFile:, atomically:, encoding:) errors. 91 | func save(file: String, in path: PathType, content: String) throws { 92 | guard let path = FileManager.default.pathFor(path) else { 93 | return 94 | } 95 | try content.write(toFile: (path as NSString).appendingPathComponent(file), atomically: true, encoding: .utf8) 96 | } 97 | 98 | /// Read a file an returns the content as String. 99 | /// 100 | /// - Parameters: 101 | /// - file: File to be read. 102 | /// - path: File path. 103 | /// - Returns: Returns the content of the file a String. 104 | /// - Throws: Throws String(contentsOfFile:, encoding:) errors. 105 | func read(file: String, from path: PathType) throws -> String? { 106 | guard let path = FileManager.default.pathFor(path) else { 107 | return nil 108 | } 109 | return try String(contentsOfFile: (path as NSString).appendingPathComponent(file), encoding: .utf8) 110 | } 111 | 112 | /// Save an object into a PLIST with given filename. 113 | /// 114 | /// - Parameters: 115 | /// - object: Object to save into PLIST. 116 | /// - path: Path of PLIST. 117 | /// - filename: PLIST filename. 118 | /// - Returns: Returns true if the operation was successful, otherwise false. 119 | // @discardableResult 120 | // public func savePlist(object: Any, in path: PathType, filename: String) -> Bool { 121 | // let path = checkPlist(path: path, filename: filename) 122 | // 123 | // if path.exist { 124 | // return NSKeyedArchiver.archiveRootObject(object, toFile: path.path) 125 | // } 126 | // return false 127 | // } 128 | 129 | /// Load an object from a PLIST with given filename. 130 | /// 131 | /// - Parameters: 132 | /// - path: Path of PLIST. 133 | /// - filename: PLIST filename. 134 | /// - Returns: Returns the loaded object. 135 | // public func readPlist(from path: PathType, filename: String) -> Any? { 136 | // let path = checkPlist(path: path, filename: filename) 137 | // 138 | // if path.exist { 139 | // return NSKeyedUnarchiver.unarchiveObject(withFile: path.path) 140 | // } 141 | // return nil 142 | // } 143 | 144 | /// Check if plist exist. 145 | /// 146 | /// - Parameters: 147 | /// - path: Path of plist. 148 | /// - filename: Plist filename. 149 | /// - Returns: Returns if plist exists and path. 150 | private func checkPlist(path: PathType, filename: String) -> (exist: Bool, path: String) { 151 | guard let path = FileManager.default.pathFor(path), let finalPath = ((path as NSString).appendingPathComponent(filename) as NSString).appendingPathExtension("plist") else { 152 | return (false, "") 153 | } 154 | return (true, finalPath) 155 | } 156 | 157 | /// Get Main Bundle path for a filename. 158 | /// 159 | /// - Parameter file: Filename 160 | /// - Returns: Returns the path as a String. 161 | func mainBundlePath(file: String = "") -> String? { 162 | return Bundle.main.path(forResource: (file as NSString).deletingPathExtension, ofType: (file as NSString).pathExtension) 163 | } 164 | 165 | /// Get Documents path for a filename. 166 | /// 167 | /// - Parameter file: Filename 168 | /// - Returns: Returns the path as a String. 169 | func documentsPath(file: String = "") -> String? { 170 | guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { 171 | return nil 172 | } 173 | return (documentsURL.path as NSString).appendingPathComponent(file) 174 | } 175 | 176 | /// Get Library path for a filename. 177 | /// 178 | /// - Parameter file: Filename 179 | /// - Returns: Returns the path as a String. 180 | func libraryPath(file: String = "") -> String? { 181 | guard let libraryURL = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first else { 182 | return nil 183 | } 184 | return (libraryURL.path as NSString).appendingPathComponent(file) 185 | } 186 | 187 | /// Get Cache path for a filename. 188 | /// 189 | /// - Parameter file: Filename 190 | /// - Returns: Returns the path as a String. 191 | func cachePath(file: String = "") -> String? { 192 | guard let cacheURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first else { 193 | return nil 194 | } 195 | return (cacheURL.path as NSString).appendingPathComponent(file) 196 | } 197 | 198 | /// Get Application Support path for a filename. 199 | /// 200 | /// - Parameter file: Filename 201 | /// - Returns: Returns the path as a String. 202 | func applicationSupportPath(file: String = "") -> String? { 203 | guard let applicationSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first else { 204 | return nil 205 | } 206 | return (applicationSupportURL.path as NSString).appendingPathComponent(file) 207 | } 208 | 209 | /// Returns the file size. 210 | /// 211 | /// - Parameters: 212 | /// - file: Filename. 213 | /// - path: Path of the file. 214 | /// - Returns: Returns the file size. 215 | /// - Throws: Throws FileManager.default.attributesOfItem(atPath: ) errors. 216 | func size(file: String, from path: PathType) throws -> Float? { 217 | if !file.isEmpty { 218 | guard let path = FileManager.default.pathFor(path) else { 219 | return nil 220 | } 221 | 222 | let finalPath = (path as NSString).appendingPathComponent(file) 223 | 224 | if FileManager.default.fileExists(atPath: finalPath) { 225 | let fileAttributes = try FileManager.default.attributesOfItem(atPath: finalPath) 226 | return fileAttributes[FileAttributeKey.size] as? Float 227 | } 228 | } 229 | 230 | return nil 231 | } 232 | 233 | /// Delete a file with the given filename. 234 | /// 235 | /// - Parameters: 236 | /// - file: File to delete. 237 | /// - path: Path of the file. 238 | /// - Returns: Returns true if the operation was successful, otherwise false. 239 | /// - Throws: Throws FileManager.default.removeItem(atPath: ) errors. 240 | 241 | func delete(file: String, from path: PathType) throws { 242 | if !file.isEmpty { 243 | guard let path = FileManager.default.pathFor(path) else { 244 | throw FileManagerError.pathNotExist 245 | } 246 | 247 | if FileManager.default.fileExists(atPath: (path as NSString).appendingPathComponent(file)) { 248 | try FileManager.default.removeItem(atPath: (path as NSString).appendingPathComponent(file)) 249 | } 250 | } else { 251 | throw FileManagerError.pathNotExist 252 | } 253 | } 254 | 255 | /// Move a file from a path to another. 256 | /// 257 | /// - Parameters: 258 | /// - file: Filename to move. 259 | /// - origin: Origin path of the file. 260 | /// - destination: Destination path of the file. 261 | /// - Returns: Returns true if the operation was successful, otherwise false. 262 | /// - Throws: Throws FileManager.default.moveItem(atPath:, toPath:) and BFKitError errors. 263 | func move(file: String, from origin: PathType, to destination: PathType) throws { 264 | let paths = try check(file: file, origin: origin, destination: destination) 265 | 266 | if paths.fileExist { 267 | try FileManager.default.moveItem(atPath: paths.origin, toPath: paths.destination) 268 | } 269 | } 270 | 271 | /// Copy a file into another path. 272 | /// 273 | /// - Parameters: 274 | /// - file: Filename to copy. 275 | /// - origin: Origin path 276 | /// - destination: Destination path 277 | /// - Returns: Returns true if the operation was successful, otherwise false. 278 | /// - Throws: Throws FileManager.default.copyItem(atPath:, toPath:) and BFKitError errors. 279 | func copy(file: String, from origin: PathType, to destination: PathType) throws { 280 | let paths = try check(file: file, origin: origin, destination: destination) 281 | 282 | if paths.fileExist { 283 | try FileManager.default.copyItem(atPath: paths.origin, toPath: paths.destination) 284 | } 285 | } 286 | 287 | /// Check is orign path, destination path and file exists. 288 | /// 289 | /// - Parameters: 290 | /// - file: File. 291 | /// - origin: Origin path. 292 | /// - destination: Destination path. 293 | /// - Returns: Returns a tuple with origin, destination and if file exist. 294 | /// - Throws: Throws BFKitError errors. 295 | private func check(file: String, origin: PathType, destination: PathType) throws -> (origin: String, destination: String, fileExist: Bool) { 296 | guard let originPath = FileManager.default.pathFor(origin), let destinationPath = FileManager.default.pathFor(destination) else { 297 | throw FileManagerError.pathNotExist 298 | } 299 | guard destination != .mainBundle else { 300 | throw FileManagerError.pathNotAllowed 301 | } 302 | 303 | let finalOriginPath = (originPath as NSString).appendingPathComponent(file) 304 | let finalDestinationPath = (destinationPath as NSString).appendingPathComponent(file) 305 | 306 | if FileManager.default.fileExists(atPath: finalOriginPath) { 307 | return (finalOriginPath, finalDestinationPath, true) 308 | } 309 | return (finalOriginPath, finalDestinationPath, false) 310 | } 311 | 312 | /// Rename a file with another filename. 313 | /// 314 | /// - Parameters: 315 | /// - file: Filename to rename. 316 | /// - origin: Origin path. 317 | /// - newName: New filename. 318 | /// - Returns: Returns true if the operation was successful, otherwise false. 319 | /// - Throws: Throws FileManager.default.copyItem(atPath:, toPath:), FileManager.default.removeItem(atPath:, toPath:) and BFKitError errors. 320 | func rename(file: String, in origin: PathType, to newName: String) throws { 321 | guard let originPath = FileManager.default.pathFor(origin) else { 322 | throw FileManagerError.pathNotExist 323 | } 324 | 325 | let finalOriginPath = (originPath as NSString).appendingPathComponent(file) 326 | 327 | if FileManager.default.fileExists(atPath: finalOriginPath) { 328 | let destinationPath: String = finalOriginPath.replacingOccurrences(of: file, with: newName) 329 | try FileManager.default.copyItem(atPath: finalOriginPath, toPath: destinationPath) 330 | try FileManager.default.removeItem(atPath: finalOriginPath) 331 | } 332 | } 333 | 334 | /// Set settings for object and key. The file will be saved in the Library path if not exist. 335 | /// 336 | /// - Parameters: 337 | /// - filename: Settings filename. "-Settings" will be automatically added. 338 | /// - object: Object to set. 339 | /// - objKey: Object key. 340 | /// - Returns: Returns true if the operation was successful, otherwise false. 341 | /// - Throws: Throws BFKitError errors. 342 | @discardableResult 343 | func setSettings(filename: String, object: Any, forKey objKey: String) -> Bool { 344 | guard var path = FileManager.default.pathFor(.applicationSupport) else { 345 | return false 346 | } 347 | path = (path as NSString).appendingPathComponent("\(filename)-Settings.plist") 348 | 349 | var loadedPlist: NSMutableDictionary 350 | if FileManager.default.fileExists(atPath: path) { 351 | loadedPlist = NSMutableDictionary(contentsOfFile: path)! 352 | } else { 353 | loadedPlist = NSMutableDictionary() 354 | } 355 | 356 | loadedPlist[objKey] = object 357 | 358 | return loadedPlist.write(toFile: path, atomically: true) 359 | } 360 | 361 | /// Get settings for key. 362 | /// 363 | /// - Parameters: 364 | /// - filename: Settings filename. "-Settings" will be automatically added. 365 | /// - forKey: Object key. 366 | /// - Returns: Returns the object for the given key. 367 | func getSettings(filename: String, forKey: String) -> Any? { 368 | guard var path = FileManager.default.pathFor(.applicationSupport) else { 369 | return nil 370 | } 371 | path = (path as NSString).appendingPathComponent("\(filename)-Settings.plist") 372 | 373 | var loadedPlist: NSMutableDictionary 374 | if FileManager.default.fileExists(atPath: path) { 375 | loadedPlist = NSMutableDictionary(contentsOfFile: path)! 376 | } else { 377 | return nil 378 | } 379 | 380 | return loadedPlist.object(forKey: forKey) 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /Project Templates/iOS/iDevs.io/VIPER.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.Xcode3.ProjectTemplateUnitKind 7 | Identifier 8 | io.idevs.dt.unit.VIPER 9 | Ancestors 10 | 11 | io.idevs.dt.unit.cocoaTouchApplicationBase 12 | com.apple.dt.unit.languageChoice 13 | 14 | Concrete 15 | 16 | Description 17 | This template provides a starting point for an application that uses a "VIPER" Architecture. 18 | SortOrder 19 | 1 20 | Options 21 | 22 | 23 | Identifier 24 | languageChoice 25 | Units 26 | 27 | Swift 28 | 29 | Nodes 30 | 31 | Classes/Assemblys/Modules/Main/Assembly/MainAssembly.swift 32 | Classes/Assemblys/Modules/Main/Interactor/MainInteractor.swift 33 | Classes/Assemblys/Modules/Main/Presenter/MainPresenter.swift 34 | Classes/Assemblys/Modules/Main/Contracts/MainContracts.swift 35 | Classes/Assemblys/Modules/Main/View/MainViewController.swift 36 | Classes/Assemblys/Modules/Main/Storyboards/Main.storyboard 37 | Classes/Assemblys/Modules/Main/Router/MainRouter.swift 38 | Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinator.swift 39 | Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinatorAssembly.swift 40 | Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinatorType.swift 41 | Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinator.swift 42 | Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinatorAssembly.swift 43 | Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinatorType.swift 44 | Classes/Assemblys/Services/AppConfigService/AppConfigService.swift 45 | Classes/Assemblys/Services/AppConfigService/AppConfigServiceAssembly.swift 46 | Classes/Assemblys/Services/AppConfigService/AppConfigServiceType.swift 47 | Classes/Assemblys/Services/EnvironmentService/EnvironmentService.swift 48 | Classes/Assemblys/Services/EnvironmentService/EnvironmentServiceAssembly.swift 49 | Classes/Assemblys/Services/EnvironmentService/EnvironmentServiceType.swift 50 | Classes/AppDelegate/AppDelegate.swift 51 | Classes/AppDelegate/Services/ApplicationService.swift 52 | Classes/Common/Module/Module.swift 53 | Classes/Common/AppDelegateManager/AppDelegateManager.swift 54 | Classes/Common/Coordinator/LaunchInstructor.swift 55 | Classes/Common/Coordinator/BaseCoordinator.swift 56 | Classes/Common/Coordinator/CoordinatorType.swift 57 | Classes/Common/DeepLink/DeepLinkOption.swift 58 | Classes/Common/Protocols/AlertRoutable.swift 59 | Classes/Common/Protocols/Presentable.swift 60 | Classes/Common/Router/Router.swift 61 | Classes/Common/Router/RouterType.swift 62 | Classes/Common/Router/BaseModuleRouter.swift 63 | Classes/Common/View/ContainerView/ContainerView.swift 64 | Classes/Common/View/ViewBuilder/ViewBuilder.swift 65 | Classes/Common/Presenter/BasePresenter.swift 66 | Classes/Constants/Constants.swift 67 | Classes/Extensions/Codable/Codable+Ext.swift 68 | Classes/Extensions/Collection/Array+Ext.swift 69 | Classes/Extensions/Color/UIColor+Ext.swift 70 | Classes/Extensions/Date/Data+Ext.swift 71 | Classes/Extensions/FileManager/FileManager+Ext.swift 72 | Classes/Extensions/NSObject/NSObject+Ext.swift 73 | Classes/Extensions/Numbers/Int+Ext.swift 74 | Classes/Extensions/String/String+Ext.swift 75 | Classes/Extensions/URL/URL+Ext.swift 76 | Classes/Extensions/View/UICollectionView+Ext.swift 77 | Classes/Extensions/View/UIImage+Ext.swift 78 | Classes/Extensions/View/UIImageView+Ext.swift 79 | Classes/Extensions/View/UILabel+Ext.swift 80 | Classes/Extensions/View/UINavigationController+Ext.swift 81 | Classes/Extensions/View/UIScrollView+Ext.swift 82 | Classes/Extensions/View/UITabBar+Ext.swift 83 | Classes/Extensions/View/UITableView+Ext.swift 84 | Classes/Extensions/View/UIView+Ext.swift 85 | Classes/Extensions/View/UIViewController+Ext.swift 86 | Classes/Extensions/View/UIWindow+Ext.swift 87 | Classes/Library/Swilby/Assembly.swift 88 | Classes/Library/Swilby/AssemblyFactory.swift 89 | Classes/Library/Swilby/DependencyContainer.swift 90 | Classes/Library/Swilby/ObjectKey.swift 91 | Classes/Library/Swilby/StrongBox.swift 92 | Classes/Library/Swilby/WeakBox.swift 93 | Classes/Library/Swilby/WeakContainer.swift 94 | Classes/Utils/Tweaks/DeviceTweak.swift 95 | Classes/Utils/Tweaks/ThreadTweak.swift 96 | Classes/Utils/Tweaks/UtilsTweak.swift 97 | 98 | Classes/Network/ServerApi.swift 99 | Classes/Network/Response/ExempleResponse.swift 100 | Classes/Network/Request/NetRequests.swift 101 | 102 | Classes/Themes/Theme.swift 103 | 104 | 105 | ../Podfile 106 | 107 | 108 | 109 | 110 | 111 | Definitions 112 | 113 | Classes/Assemblys/Modules/Main/Assembly/MainAssembly.swift 114 | 115 | Group 116 | 117 | Classes 118 | Assemblys 119 | Modules 120 | Main 121 | Assembly 122 | 123 | Path 124 | Classes/Assemblys/Modules/Main/Assembly/MainAssembly.swift 125 | 126 | Classes/Assemblys/Modules/Main/Interactor/MainInteractor.swift 127 | 128 | Group 129 | 130 | Classes 131 | Assemblys 132 | Modules 133 | Main 134 | Interactor 135 | 136 | Path 137 | Classes/Assemblys/Modules/Main/Interactor/MainInteractor.swift 138 | 139 | Classes/Assemblys/Modules/Main/Presenter/MainPresenter.swift 140 | 141 | Group 142 | 143 | Classes 144 | Assemblys 145 | Modules 146 | Main 147 | Presenter 148 | 149 | Path 150 | Classes/Assemblys/Modules/Main/Presenter/MainPresenter.swift 151 | 152 | Classes/Assemblys/Modules/Main/Contracts/MainContracts.swift 153 | 154 | Group 155 | 156 | Classes 157 | Assemblys 158 | Modules 159 | Main 160 | Contracts 161 | 162 | Path 163 | Classes/Assemblys/Modules/Main/Contracts/MainContracts.swift 164 | 165 | Classes/Assemblys/Modules/Main/View/MainViewController.swift 166 | 167 | Group 168 | 169 | Classes 170 | Assemblys 171 | Modules 172 | Main 173 | View 174 | 175 | Path 176 | Classes/Assemblys/Modules/Main/View/MainViewController.swift 177 | 178 | Classes/Assemblys/Modules/Main/Storyboards/Main.storyboard 179 | 180 | Group 181 | 182 | Classes 183 | Assemblys 184 | Modules 185 | Main 186 | Storyboards 187 | 188 | Path 189 | Classes/Assemblys/Modules/Main/Storyboards/Main.storyboard 190 | 191 | Classes/Assemblys/Modules/Main/Router/MainRouter.swift 192 | 193 | Group 194 | 195 | Classes 196 | Assemblys 197 | Modules 198 | Main 199 | Router 200 | 201 | Path 202 | Classes/Assemblys/Modules/Main/Router/MainRouter.swift 203 | 204 | Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinator.swift 205 | 206 | Group 207 | 208 | Classes 209 | Assemblys 210 | Coordinators 211 | AppCoordinator 212 | 213 | Path 214 | Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinator.swift 215 | 216 | Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinatorAssembly.swift 217 | 218 | Group 219 | 220 | Classes 221 | Assemblys 222 | Coordinators 223 | AppCoordinator 224 | 225 | Path 226 | Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinatorAssembly.swift 227 | 228 | Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinatorType.swift 229 | 230 | Group 231 | 232 | Classes 233 | Assemblys 234 | Coordinators 235 | AppCoordinator 236 | 237 | Path 238 | Classes/Assemblys/Coordinators/AppCoordinator/AppCoordinatorType.swift 239 | 240 | Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinator.swift 241 | 242 | Group 243 | 244 | Classes 245 | Assemblys 246 | Coordinators 247 | MainCoordinator 248 | 249 | Path 250 | Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinator.swift 251 | 252 | Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinatorAssembly.swift 253 | 254 | Group 255 | 256 | Classes 257 | Assemblys 258 | Coordinators 259 | MainCoordinator 260 | 261 | Path 262 | Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinatorAssembly.swift 263 | 264 | Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinatorType.swift 265 | 266 | Group 267 | 268 | Classes 269 | Assemblys 270 | Coordinators 271 | MainCoordinator 272 | 273 | Path 274 | Classes/Assemblys/Coordinators/MainCoordinator/MainCoordinatorType.swift 275 | 276 | Classes/Assemblys/Services/AppConfigService/AppConfigService.swift 277 | 278 | Group 279 | 280 | Classes 281 | Assemblys 282 | Services 283 | AppConfigService 284 | 285 | Path 286 | Classes/Assemblys/Services/AppConfigService/AppConfigService.swift 287 | 288 | Classes/Assemblys/Services/AppConfigService/AppConfigServiceAssembly.swift 289 | 290 | Group 291 | 292 | Classes 293 | Assemblys 294 | Services 295 | AppConfigService 296 | 297 | Path 298 | Classes/Assemblys/Services/AppConfigService/AppConfigServiceAssembly.swift 299 | 300 | Classes/Assemblys/Services/AppConfigService/AppConfigServiceType.swift 301 | 302 | Group 303 | 304 | Classes 305 | Assemblys 306 | Services 307 | AppConfigService 308 | 309 | Path 310 | Classes/Assemblys/Services/AppConfigService/AppConfigServiceType.swift 311 | 312 | Classes/Assemblys/Services/EnvironmentService/EnvironmentService.swift 313 | 314 | Group 315 | 316 | Classes 317 | Assemblys 318 | Services 319 | EnvironmentService 320 | 321 | Path 322 | Classes/Assemblys/Services/EnvironmentService/EnvironmentService.swift 323 | 324 | Classes/Assemblys/Services/EnvironmentService/EnvironmentServiceAssembly.swift 325 | 326 | Group 327 | 328 | Classes 329 | Assemblys 330 | Services 331 | EnvironmentService 332 | 333 | Path 334 | Classes/Assemblys/Services/EnvironmentService/EnvironmentServiceAssembly.swift 335 | 336 | Classes/Assemblys/Services/EnvironmentService/EnvironmentServiceType.swift 337 | 338 | Group 339 | 340 | Classes 341 | Assemblys 342 | Services 343 | EnvironmentService 344 | 345 | Path 346 | Classes/Assemblys/Services/EnvironmentService/EnvironmentServiceType.swift 347 | 348 | Classes/AppDelegate/AppDelegate.swift 349 | 350 | Group 351 | 352 | Classes 353 | AppDelegate 354 | 355 | Path 356 | Classes/AppDelegate/AppDelegate.swift 357 | 358 | Classes/AppDelegate/Services/ApplicationService.swift 359 | 360 | Group 361 | 362 | Classes 363 | AppDelegate 364 | Services 365 | 366 | Path 367 | Classes/AppDelegate/Services/ApplicationService.swift 368 | 369 | Classes/Common/Module/Module.swift 370 | 371 | Group 372 | 373 | Classes 374 | Common 375 | Module 376 | 377 | Path 378 | Classes/Common/Module/Module.swift 379 | 380 | 381 | Classes/Common/AppDelegateManager/AppDelegateManager.swift 382 | 383 | Group 384 | 385 | Classes 386 | Common 387 | AppDelegateManager 388 | 389 | Path 390 | Classes/Common/AppDelegateManager/AppDelegateManager.swift 391 | 392 | Classes/Common/Coordinator/LaunchInstructor.swift 393 | 394 | Group 395 | 396 | Classes 397 | Common 398 | Coordinator 399 | 400 | Path 401 | Classes/Common/Coordinator/LaunchInstructor.swift 402 | 403 | Classes/Common/Coordinator/BaseCoordinator.swift 404 | 405 | Group 406 | 407 | Classes 408 | Common 409 | Coordinator 410 | 411 | Path 412 | Classes/Common/Coordinator/BaseCoordinator.swift 413 | 414 | Classes/Common/Coordinator/CoordinatorType.swift 415 | 416 | Group 417 | 418 | Classes 419 | Common 420 | Coordinator 421 | 422 | Path 423 | Classes/Common/Coordinator/CoordinatorType.swift 424 | 425 | Classes/Common/DeepLink/DeepLinkOption.swift 426 | 427 | Group 428 | 429 | Classes 430 | Common 431 | DeepLink 432 | 433 | Path 434 | Classes/Common/DeepLink/DeepLinkOption.swift 435 | 436 | Classes/Common/Protocols/AlertRoutable.swift 437 | 438 | Group 439 | 440 | Classes 441 | Common 442 | Protocols 443 | 444 | Path 445 | Classes/Common/Protocols/AlertRoutable.swift 446 | 447 | Classes/Common/Protocols/Presentable.swift 448 | 449 | Group 450 | 451 | Classes 452 | Common 453 | Protocols 454 | 455 | Path 456 | Classes/Common/Protocols/Presentable.swift 457 | 458 | Classes/Common/Router/Router.swift 459 | 460 | Group 461 | 462 | Classes 463 | Common 464 | Router 465 | 466 | Path 467 | Classes/Common/Router/Router.swift 468 | 469 | Classes/Common/Router/RouterType.swift 470 | 471 | Group 472 | 473 | Classes 474 | Common 475 | Router 476 | 477 | Path 478 | Classes/Common/Router/RouterType.swift 479 | 480 | Classes/Common/Router/BaseModuleRouter.swift 481 | 482 | Group 483 | 484 | Classes 485 | Common 486 | Router 487 | 488 | Path 489 | Classes/Common/Router/BaseModuleRouter.swift 490 | 491 | Classes/Common/View/ContainerView/ContainerView.swift 492 | 493 | Group 494 | 495 | Classes 496 | Common 497 | View 498 | ContainerView 499 | 500 | Path 501 | Classes/Common/View/ContainerView/ContainerView.swift 502 | 503 | Classes/Common/View/ViewBuilder/ViewBuilder.swift 504 | 505 | Group 506 | 507 | Classes 508 | Common 509 | View 510 | ViewBuilder 511 | 512 | Path 513 | Classes/Common/View/ViewBuilder/ViewBuilder.swift 514 | 515 | Classes/Common/Presenter/BasePresenter.swift 516 | 517 | Group 518 | 519 | Classes 520 | Common 521 | Presenter 522 | 523 | Path 524 | Classes/Common/Presenter/BasePresenter.swift 525 | 526 | Classes/Constants/Constants.swift 527 | 528 | Group 529 | 530 | Classes 531 | Constants 532 | 533 | Path 534 | Classes/Constants/Constants.swift 535 | 536 | Classes/Extensions/Codable/Codable+Ext.swift 537 | 538 | Group 539 | 540 | Classes 541 | Extensions 542 | Codable 543 | 544 | Path 545 | Classes/Extensions/Codable/Codable+Ext.swift 546 | 547 | Classes/Extensions/Collection/Array+Ext.swift 548 | 549 | Group 550 | 551 | Classes 552 | Extensions 553 | Collection 554 | 555 | Path 556 | Classes/Extensions/Collection/Array+Ext.swift 557 | 558 | Classes/Extensions/Color/UIColor+Ext.swift 559 | 560 | Group 561 | 562 | Classes 563 | Extensions 564 | Color 565 | 566 | Path 567 | Classes/Extensions/Color/UIColor+Ext.swift 568 | 569 | Classes/Extensions/Date/Data+Ext.swift 570 | 571 | Group 572 | 573 | Classes 574 | Extensions 575 | Date 576 | 577 | Path 578 | Classes/Extensions/Date/Data+Ext.swift 579 | 580 | Classes/Extensions/FileManager/FileManager+Ext.swift 581 | 582 | Group 583 | 584 | Classes 585 | Extensions 586 | FileManager 587 | 588 | Path 589 | Classes/Extensions/FileManager/FileManager+Ext.swift 590 | 591 | Classes/Extensions/NSObject/NSObject+Ext.swift 592 | 593 | Group 594 | 595 | Classes 596 | Extensions 597 | NSObject 598 | 599 | Path 600 | Classes/Extensions/NSObject/NSObject+Ext.swift 601 | 602 | Classes/Extensions/Numbers/Int+Ext.swift 603 | 604 | Group 605 | 606 | Classes 607 | Extensions 608 | Numbers 609 | 610 | Path 611 | Classes/Extensions/Numbers/Int+Ext.swift 612 | 613 | Classes/Extensions/String/String+Ext.swift 614 | 615 | Group 616 | 617 | Classes 618 | Extensions 619 | String 620 | 621 | Path 622 | Classes/Extensions/String/String+Ext.swift 623 | 624 | Classes/Extensions/URL/URL+Ext.swift 625 | 626 | Group 627 | 628 | Classes 629 | Extensions 630 | URL 631 | 632 | Path 633 | Classes/Extensions/URL/URL+Ext.swift 634 | 635 | Classes/Extensions/View/UICollectionView+Ext.swift 636 | 637 | Group 638 | 639 | Classes 640 | Extensions 641 | View 642 | 643 | Path 644 | Classes/Extensions/View/UICollectionView+Ext.swift 645 | 646 | Classes/Extensions/View/UIImage+Ext.swift 647 | 648 | Group 649 | 650 | Classes 651 | Extensions 652 | View 653 | 654 | Path 655 | Classes/Extensions/View/UIImage+Ext.swift 656 | 657 | Classes/Extensions/View/UIImageView+Ext.swift 658 | 659 | Group 660 | 661 | Classes 662 | Extensions 663 | View 664 | 665 | Path 666 | Classes/Extensions/View/UIImageView+Ext.swift 667 | 668 | Classes/Extensions/View/UILabel+Ext.swift 669 | 670 | Group 671 | 672 | Classes 673 | Extensions 674 | View 675 | 676 | Path 677 | Classes/Extensions/View/UILabel+Ext.swift 678 | 679 | Classes/Extensions/View/UINavigationController+Ext.swift 680 | 681 | Group 682 | 683 | Classes 684 | Extensions 685 | View 686 | 687 | Path 688 | Classes/Extensions/View/UINavigationController+Ext.swift 689 | 690 | Classes/Extensions/View/UIScrollView+Ext.swift 691 | 692 | Group 693 | 694 | Classes 695 | Extensions 696 | View 697 | 698 | Path 699 | Classes/Extensions/View/UIScrollView+Ext.swift 700 | 701 | Classes/Extensions/View/UITabBar+Ext.swift 702 | 703 | Group 704 | 705 | Classes 706 | Extensions 707 | View 708 | 709 | Path 710 | Classes/Extensions/View/UITabBar+Ext.swift 711 | 712 | Classes/Extensions/View/UITableView+Ext.swift 713 | 714 | Group 715 | 716 | Classes 717 | Extensions 718 | View 719 | 720 | Path 721 | Classes/Extensions/View/UITableView+Ext.swift 722 | 723 | Classes/Extensions/View/UIView+Ext.swift 724 | 725 | Group 726 | 727 | Classes 728 | Extensions 729 | View 730 | 731 | Path 732 | Classes/Extensions/View/UIView+Ext.swift 733 | 734 | Classes/Extensions/View/UIViewController+Ext.swift 735 | 736 | Group 737 | 738 | Classes 739 | Extensions 740 | View 741 | 742 | Path 743 | Classes/Extensions/View/UIViewController+Ext.swift 744 | 745 | Classes/Extensions/View/UIWindow+Ext.swift 746 | 747 | Group 748 | 749 | Classes 750 | Extensions 751 | View 752 | 753 | Path 754 | Classes/Extensions/View/UIWindow+Ext.swift 755 | 756 | Classes/Library/Swilby/Assembly.swift 757 | 758 | Group 759 | 760 | Classes 761 | Library 762 | Swilby 763 | 764 | Path 765 | Classes/Library/Swilby/Assembly.swift 766 | 767 | Classes/Library/Swilby/AssemblyFactory.swift 768 | 769 | Group 770 | 771 | Classes 772 | Library 773 | Swilby 774 | 775 | Path 776 | Classes/Library/Swilby/AssemblyFactory.swift 777 | 778 | Classes/Library/Swilby/DependencyContainer.swift 779 | 780 | Group 781 | 782 | Classes 783 | Library 784 | Swilby 785 | 786 | Path 787 | Classes/Library/Swilby/DependencyContainer.swift 788 | 789 | Classes/Library/Swilby/ObjectKey.swift 790 | 791 | Group 792 | 793 | Classes 794 | Library 795 | Swilby 796 | 797 | Path 798 | Classes/Library/Swilby/ObjectKey.swift 799 | 800 | Classes/Library/Swilby/StrongBox.swift 801 | 802 | Group 803 | 804 | Classes 805 | Library 806 | Swilby 807 | 808 | Path 809 | Classes/Library/Swilby/StrongBox.swift 810 | 811 | Classes/Library/Swilby/WeakBox.swift 812 | 813 | Group 814 | 815 | Classes 816 | Library 817 | Swilby 818 | 819 | Path 820 | Classes/Library/Swilby/WeakBox.swift 821 | 822 | Classes/Library/Swilby/WeakContainer.swift 823 | 824 | Group 825 | 826 | Classes 827 | Library 828 | Swilby 829 | 830 | Path 831 | Classes/Library/Swilby/WeakContainer.swift 832 | 833 | Classes/Utils/Tweaks/DeviceTweak.swift 834 | 835 | Group 836 | 837 | Classes 838 | Utils 839 | Tweaks 840 | 841 | Path 842 | Classes/Utils/Tweaks/DeviceTweak.swift 843 | 844 | Classes/Utils/Tweaks/ThreadTweak.swift 845 | 846 | Group 847 | 848 | Classes 849 | Utils 850 | Tweaks 851 | 852 | Path 853 | Classes/Utils/Tweaks/ThreadTweak.swift 854 | 855 | Classes/Utils/Tweaks/UtilsTweak.swift 856 | 857 | Group 858 | 859 | Classes 860 | Utils 861 | Tweaks 862 | 863 | Path 864 | Classes/Utils/Tweaks/UtilsTweak.swift 865 | 866 | Classes/Network/ServerApi.swift 867 | 868 | Group 869 | 870 | Classes 871 | Network 872 | 873 | Path 874 | Classes/Network/ServerApi.swift 875 | 876 | 877 | Classes/Network/Response/ExempleResponse.swift 878 | 879 | Group 880 | 881 | Classes 882 | Network 883 | Response 884 | 885 | Path 886 | Classes/Network/Response/ExempleResponse.swift 887 | 888 | 889 | Classes/Network/Request/NetRequests.swift 890 | 891 | Group 892 | 893 | Classes 894 | Network 895 | Request 896 | 897 | Path 898 | Classes/Network/Request/NetRequests.swift 899 | 900 | Classes/Themes/Theme.swift 901 | 902 | Group 903 | 904 | Classes 905 | Themes 906 | 907 | Path 908 | Classes/Themes/Theme.swift 909 | 910 | ../Podfile 911 | 912 | Group 913 | 914 | Supporting Files 915 | 916 | Path 917 | Podfile 918 | 919 | 920 | 921 | 922 | --------------------------------------------------------------------------------