├── File Templates └── Artem Tishchenko │ ├── Module.xctemplate │ ├── TemplateIcon.png │ ├── TemplateIcon@2x.png │ ├── TemplateInfo.plist │ └── ___FILEBASENAME___ │ │ ├── Assembly │ │ └── ___FILEBASENAME___Assembly.swift │ │ ├── Contracts │ │ └── ___FILEBASENAME___Contracts.swift │ │ ├── Interactor │ │ └── ___FILEBASENAME___Interactor.swift │ │ ├── Presenter │ │ └── ___FILEBASENAME___Presenter.swift │ │ ├── Router │ │ └── ___FILEBASENAME___Router.swift │ │ ├── View │ │ └── ___FILEBASENAME___View.swift │ │ └── ViewState │ │ └── ___FILEBASENAME___ViewState.swift │ └── Service.xctemplate │ ├── TemplateIcon.png │ ├── TemplateIcon@2x.png │ ├── TemplateInfo.plist │ └── ___FILEBASENAME___ │ ├── ___FILEBASENAME___.swift │ ├── ___FILEBASENAME___Assembly.swift │ └── ___FILEBASENAME___Type.swift ├── Project Templates └── iOS │ └── Artem Tishchenko │ ├── Base.xctemplate │ ├── LaunchScreen.storyboard │ └── TemplateInfo.plist │ └── VIPER.xctemplate │ ├── ApplicationViewBuilder.swift │ ├── Classes │ ├── Architecture │ │ ├── InteractorProtocol.swift │ │ ├── PresenterProtocol.swift │ │ ├── RouterProtocol.swift │ │ └── ViewStateProtocol.swift │ ├── Library │ │ └── Swilby │ │ │ ├── Assembly.swift │ │ │ ├── AssemblyFactory.swift │ │ │ ├── DependencyContainer.swift │ │ │ ├── ObjectKey.swift │ │ │ ├── StrongBox.swift │ │ │ ├── WeakBox.swift │ │ │ └── WeakContainer.swift │ ├── Modules │ │ └── Main │ │ │ ├── Assembly │ │ │ └── MainAssembly.swift │ │ │ ├── Contracts │ │ │ └── MainContracts.swift │ │ │ ├── Interactor │ │ │ └── MainInteractor.swift │ │ │ ├── Presenter │ │ │ └── MainPresenter.swift │ │ │ ├── Router │ │ │ └── MainRouter.swift │ │ │ ├── View │ │ │ └── MainView.swift │ │ │ └── ViewState │ │ │ └── MainViewState.swift │ └── Services │ │ └── NavigationService │ │ ├── NavigationAssembly.swift │ │ ├── NavigationService.swift │ │ └── NavigationServiceType.swift │ ├── Resources │ └── Assets.xcassets │ │ ├── AccentColor.colorset │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ └── Contents.json │ │ └── Contents.json │ ├── RootApp.swift │ ├── RootView.swift │ ├── TemplateIcon.png │ ├── TemplateIcon@2x.png │ └── TemplateInfo.plist ├── README-RU.md ├── README.md └── install.swift /File Templates/Artem Tishchenko/Module.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maukur/SwiftUI-Viper-Architecture/a937ceb96e8af93b6838197a4ae11c6d520f26de/File Templates/Artem Tishchenko/Module.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/Module.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maukur/SwiftUI-Viper-Architecture/a937ceb96e8af93b6838197a4ae11c6d520f26de/File Templates/Artem Tishchenko/Module.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/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/Artem Tishchenko/Module.xctemplate/___FILEBASENAME___/Assembly/___FILEBASENAME___Assembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | 10 | import SwiftUI 11 | 12 | final class ___VARIABLE_moduleName___Assembly: Assembly { 13 | 14 | func build() -> some View { 15 | 16 | let navigation = container.resolve(NavigationAssembly.self).build() 17 | 18 | // Router 19 | let router = ___VARIABLE_moduleName___Router(navigation: navigation) 20 | 21 | // Interactor 22 | let interactor = ___VARIABLE_moduleName___Interactor() 23 | 24 | //ViewState 25 | let viewState = ___VARIABLE_moduleName___ViewState() 26 | 27 | // Presenter 28 | let presenter = ___VARIABLE_moduleName___Presenter(router: router, interactor: interactor, viewState: viewState) 29 | 30 | viewState.set(with: presenter) 31 | 32 | // View 33 | let view = ___VARIABLE_moduleName___View(viewState: viewState) 34 | return view 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/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 SwiftUI 10 | 11 | 12 | // Router 13 | protocol ___VARIABLE_moduleName___RouterProtocol: RouterProtocol { 14 | 15 | } 16 | 17 | // Presenter 18 | protocol ___VARIABLE_moduleName___PresenterProtocol: PresenterProtocol { 19 | 20 | } 21 | 22 | // Interactor 23 | protocol ___VARIABLE_moduleName___InteractorProtocol: InteractorProtocol { 24 | 25 | } 26 | 27 | // ViewState 28 | protocol ___VARIABLE_moduleName___ViewStateProtocol: ViewStateProtocol { 29 | func set(with presenter: ___VARIABLE_moduleName___PresenterProtocol) 30 | } 31 | -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/Module.xctemplate/___FILEBASENAME___/Interactor/___FILEBASENAME___Interactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | 10 | import Foundation 11 | 12 | final class ___VARIABLE_moduleName___Interactor: ___VARIABLE_moduleName___InteractorProtocol { 13 | 14 | } 15 | 16 | // MARK: Private 17 | extension ___VARIABLE_moduleName___Interactor { 18 | 19 | } -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/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 SwiftUI 10 | 11 | final class ___VARIABLE_moduleName___Presenter: ___VARIABLE_moduleName___PresenterProtocol { 12 | 13 | private let router: ___VARIABLE_moduleName___RouterProtocol 14 | private weak var viewState: ___VARIABLE_moduleName___ViewStateProtocol? 15 | private let interactor: ___VARIABLE_moduleName___InteractorProtocol 16 | 17 | init(router: ___VARIABLE_moduleName___RouterProtocol, 18 | interactor: ___VARIABLE_moduleName___InteractorProtocol, 19 | viewState: ___VARIABLE_moduleName___ViewStateProtocol) { 20 | self.router = router 21 | self.interactor = interactor 22 | self.viewState = viewState 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/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 | final class ___VARIABLE_moduleName___Router: ___VARIABLE_moduleName___RouterProtocol { 12 | private var navigation: any NavigationServiceType 13 | 14 | init(navigation: any NavigationServiceType){ 15 | self.navigation = navigation 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/Module.xctemplate/___FILEBASENAME___/View/___FILEBASENAME___View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ___VARIABLE_moduleName___View: View { 12 | 13 | @StateObject var viewState: ___VARIABLE_moduleName___ViewState 14 | 15 | var body: some View { 16 | Text("Hello iOS") 17 | } 18 | } 19 | 20 | struct ___VARIABLE_moduleName___Previews: PreviewProvider { 21 | static var previews: some View { 22 | ApplicationViewBuilder.stub.build(view: .___VARIABLE_moduleName___) 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/Module.xctemplate/___FILEBASENAME___/ViewState/___FILEBASENAME___ViewState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import SwiftUI 10 | 11 | final class ___VARIABLE_moduleName___ViewState: ObservableObject, ___VARIABLE_moduleName___ViewStateProtocol { 12 | private let id = UUID() 13 | private var presenter: ___VARIABLE_moduleName___PresenterProtocol? 14 | 15 | func set(with presener: ___VARIABLE_moduleName___PresenterProtocol) { 16 | self.presenter = presener 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/Service.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maukur/SwiftUI-Viper-Architecture/a937ceb96e8af93b6838197a4ae11c6d520f26de/File Templates/Artem Tishchenko/Service.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/Service.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maukur/SwiftUI-Viper-Architecture/a937ceb96e8af93b6838197a4ae11c6d520f26de/File Templates/Artem Tishchenko/Service.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/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 | -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/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 | -------------------------------------------------------------------------------- /File Templates/Artem Tishchenko/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/Artem Tishchenko/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 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/Base.xctemplate/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/Base.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.Xcode3.ProjectTemplateUnitKind 7 | Identifier 8 | com.artemtishchenko.unit.cocoaTouchApplicationBase 9 | Ancestors 10 | 11 | com.apple.dt.unit.applicationBase 12 | com.apple.dt.unit.iosBase 13 | 14 | Targets 15 | 16 | 17 | TargetIdentifier 18 | com.apple.dt.cocoaTouchApplicationTarget 19 | SharedSettings 20 | 21 | ASSETCATALOG_COMPILER_APPICON_NAME 22 | AppIcon 23 | LD_RUNPATH_SEARCH_PATHS 24 | $(inherited) @executable_path/Frameworks 25 | 26 | 27 | 28 | Options 29 | 30 | 31 | Identifier 32 | universalDeviceFamily 33 | Name 34 | Devices: 35 | Description 36 | Which device family to create a project for 37 | SortOrder 38 | 1 39 | Type 40 | popup 41 | Default 42 | Universal 43 | Values 44 | 45 | Universal 46 | iPhone 47 | iPad 48 | 49 | Units 50 | 51 | iPhone 52 | 53 | Nodes 54 | 55 | Info.plist:UISupportedInterfaceOrientations~iPhone 56 | Resources/Assets.xcassets 57 | 58 | Definitions 59 | 60 | Resources/Assets.xcassets 61 | 62 | Group 63 | 64 | Resources 65 | 66 | Path 67 | Images-iPhone.xcassets 68 | SortOrder 69 | 100 70 | 71 | 72 | 73 | iPad 74 | 75 | Project 76 | 77 | SharedSettings 78 | 79 | TARGETED_DEVICE_FAMILY 80 | 2 81 | 82 | 83 | Nodes 84 | 85 | Info.plist:UISupportedInterfaceOrientations~iPad 86 | Resources/Assets.xcassets 87 | 88 | Definitions 89 | 90 | Resources/Assets.xcassets 91 | 92 | Group 93 | 94 | Resources 95 | 96 | Path 97 | Images-iPad.xcassets 98 | SortOrder 99 | 100 100 | 101 | 102 | 103 | Universal 104 | 105 | Project 106 | 107 | SharedSettings 108 | 109 | TARGETED_DEVICE_FAMILY 110 | 1,2 111 | 112 | 113 | Nodes 114 | 115 | Info.plist:UISupportedInterfaceOrientations~iPhone 116 | Info.plist:UISupportedInterfaceOrientations~iPad 117 | Resources/Assets.xcassets 118 | 119 | Definitions 120 | 121 | Resources/Assets.xcassets 122 | 123 | Group 124 | 125 | Resources 126 | 127 | Path 128 | Images-Universal.xcassets 129 | SortOrder 130 | 100 131 | 132 | 133 | 134 | 135 | 136 | 137 | Identifier 138 | hasUnitTests 139 | Name 140 | Include Unit Tests 141 | NotPersisted 142 | 143 | SortOrder 144 | 100 145 | Type 146 | checkbox 147 | Default 148 | true 149 | Units 150 | 151 | true 152 | 153 | Components 154 | 155 | 156 | Identifier 157 | com.apple.dt.unit.cocoaTouchApplicationUnitTestBundle 158 | Name 159 | ___PACKAGENAME___Tests 160 | 161 | 162 | 163 | 164 | 165 | 166 | Identifier 167 | hasUITests 168 | Name 169 | Include UI Tests 170 | NotPersisted 171 | 172 | SortOrder 173 | 101 174 | Type 175 | checkbox 176 | Default 177 | true 178 | Units 179 | 180 | true 181 | 182 | Components 183 | 184 | 185 | Identifier 186 | com.apple.dt.unit.cocoaTouchApplicationUITestBundle 187 | Name 188 | ___PACKAGENAME___UITests 189 | 190 | 191 | 192 | 193 | 194 | 195 | Identifier 196 | languageChoice 197 | Units 198 | 199 | Swift 200 | 201 | Nodes 202 | 203 | Definitions 204 | 205 | 206 | 207 | 208 | 209 | Nodes 210 | 211 | Info.plist:iPhone 212 | Info.plist:UIRequiredDeviceCapabilities:base 213 | Info.plist:LaunchScreen 214 | Base.lproj/LaunchScreen.storyboard 215 | 216 | Definitions 217 | 218 | Info.plist:iPhone 219 | <key>LSRequiresIPhoneOS</key> 220 | <true/> 221 | Info.plist:UIRequiredDeviceCapabilities 222 | 223 | Beginning 224 | <key>UIRequiredDeviceCapabilities</key> 225 | <array> 226 | End 227 | </array> 228 | Indent 229 | 1 230 | 231 | Info.plist:UIRequiredDeviceCapabilities:base 232 | <string>armv7</string> 233 | Info.plist:statusBarTintForNavBar 234 | <key>UIStatusBarTintParameters</key> 235 | <dict> 236 | <key>UINavigationBar</key> 237 | <dict> 238 | <key>Style</key> 239 | <string>UIBarStyleDefault</string> 240 | <key>Translucent</key> 241 | <false/> 242 | </dict> 243 | </dict> 244 | 245 | Info.plist:LaunchScreen 246 | <key>UILaunchStoryboardName</key> 247 | <string>LaunchScreen</string> 248 | 249 | Base.lproj/LaunchScreen.storyboard 250 | 251 | 252 | Path 253 | LaunchScreen.storyboard 254 | SortOrder 255 | 101 256 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/ApplicationViewBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import SwiftUI 10 | 11 | final class ApplicationViewBuilder : Assembly, ObservableObject { 12 | 13 | required init(container: Container) { 14 | super.init(container: container) 15 | } 16 | 17 | @ViewBuilder 18 | func build(view: Views) -> some View { 19 | switch view { 20 | case .main: 21 | buildMain() 22 | } 23 | } 24 | 25 | @ViewBuilder 26 | fileprivate func buildMain() -> some View { 27 | container.resolve(MainAssembly.self).build() 28 | } 29 | 30 | } 31 | 32 | extension ApplicationViewBuilder { 33 | 34 | static var stub: ApplicationViewBuilder { 35 | return ApplicationViewBuilder( 36 | container: RootApp().container 37 | ) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/Architecture/InteractorProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | protocol InteractorProtocol: AnyObject { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/Architecture/PresenterProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | protocol PresenterProtocol: AnyObject { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/Architecture/RouterProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | protocol RouterProtocol: AnyObject { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/Architecture/ViewStateProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | protocol ViewStateProtocol : AnyObject { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/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: AnyObject { 12 | associatedtype Container 13 | var container: Container {get set} 14 | init(container: Container) 15 | } 16 | 17 | class Assembly: AssemblyType { 18 | 19 | var container: Container 20 | 21 | required init(container: Container) { 22 | self.container = container 23 | } 24 | } 25 | 26 | // Box 27 | extension Assembly { 28 | func weakBox(_ configure: () -> T) -> T { 29 | return self.container.weakBox(configure) 30 | } 31 | 32 | func strongBox(_ configure: () -> T) -> T { 33 | return self.container.strongBox(configure) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/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: AnyObject { 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 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/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/Artem Tishchenko/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/Artem Tishchenko/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: AnyObject { 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/Artem Tishchenko/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: AnyObject { 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/Artem Tishchenko/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/Artem Tishchenko/VIPER.xctemplate/Classes/Modules/Main/Assembly/MainAssembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | 10 | import SwiftUI 11 | 12 | final class MainAssembly: Assembly { 13 | 14 | func build() -> some View { 15 | 16 | let navigation = container.resolve(NavigationAssembly.self).build() 17 | 18 | // Router 19 | let router = MainRouter(navigation: navigation) 20 | 21 | // Interactor 22 | let interactor = MainInteractor() 23 | 24 | //ViewState 25 | let viewState = MainViewState() 26 | 27 | // Presenter 28 | let presenter = MainPresenter(router: router, 29 | interactor: interactor, 30 | viewState: viewState) 31 | 32 | viewState.set(with: presenter) 33 | 34 | // View 35 | let view = MainView(viewState: viewState) 36 | return view 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/Modules/Main/Contracts/MainContracts.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import SwiftUI 10 | 11 | 12 | // Router 13 | protocol MainRouterProtocol: RouterProtocol { 14 | 15 | } 16 | 17 | // Presenter 18 | protocol MainPresenterProtocol: PresenterProtocol { 19 | 20 | } 21 | 22 | // Interactor 23 | protocol MainInteractorProtocol: InteractorProtocol { 24 | 25 | } 26 | 27 | // ViewState 28 | protocol MainViewStateProtocol: ViewStateProtocol { 29 | func set(with presenter: MainPresenterProtocol) 30 | } 31 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/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 | final class MainInteractor: MainInteractorProtocol { 12 | 13 | } 14 | 15 | // MARK: Private 16 | extension MainInteractor { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/Modules/Main/Presenter/MainPresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import SwiftUI 10 | 11 | final class MainPresenter: MainPresenterProtocol { 12 | 13 | private let router: MainRouterProtocol 14 | private weak var viewState: MainViewStateProtocol? 15 | private let interactor: MainInteractorProtocol 16 | 17 | init(router: MainRouterProtocol, 18 | interactor: MainInteractorProtocol, 19 | viewState: MainViewStateProtocol) { 20 | self.router = router 21 | self.interactor = interactor 22 | self.viewState = viewState 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/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 | final class MainRouter: MainRouterProtocol { 12 | var navigation: any NavigationServiceType 13 | 14 | init(navigation: any NavigationServiceType){ 15 | self.navigation = navigation 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/Modules/Main/View/MainView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct MainView: View { 12 | 13 | @StateObject var viewState: MainViewState 14 | 15 | var body: some View { 16 | Text("Hello iOS") 17 | } 18 | } 19 | 20 | struct MainPreviews: PreviewProvider { 21 | static var previews: some View { 22 | ApplicationViewBuilder.stub.build(view: .main) 23 | } 24 | } 25 | 26 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/Modules/Main/ViewState/MainViewState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import SwiftUI 10 | 11 | final class MainViewState: ObservableObject, MainViewStateProtocol { 12 | private let id = UUID() 13 | private var presenter: MainPresenterProtocol? 14 | 15 | func set(with presener: MainPresenterProtocol) { 16 | self.presenter = presener 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/Services/NavigationService/NavigationAssembly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | final class NavigationAssembly: Assembly { 12 | 13 | //Only one navigation should use in app 14 | static let navigation: any NavigationServiceType = NavigationService() 15 | 16 | func build() -> any NavigationServiceType { 17 | return NavigationAssembly.navigation 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/Services/NavigationService/NavigationService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | 10 | import SwiftUI 11 | 12 | public class NavigationService: NavigationServiceType { 13 | 14 | public let id = UUID() 15 | 16 | public static func == (lhs: NavigationService, rhs: NavigationService) -> Bool { 17 | lhs.id == rhs.id 18 | } 19 | 20 | @Published var modalView: Views? 21 | @Published var popupView: Views? 22 | @Published var items: [Views] = [] 23 | @Published var alert: CustomAlert? 24 | } 25 | 26 | 27 | enum Views: Identifiable, Equatable, Hashable { 28 | 29 | var id: String { stringKey } 30 | 31 | static func == (lhs: Views, rhs: Views) -> Bool { 32 | lhs.stringKey == rhs.stringKey 33 | } 34 | 35 | func hash(into hasher: inout Hasher) { 36 | hasher.combine(self.stringKey) 37 | } 38 | 39 | case main 40 | 41 | var stringKey: String { 42 | switch self { 43 | case .main: 44 | return "main" 45 | } 46 | } 47 | } 48 | 49 | 50 | enum CustomAlert: Equatable, Hashable { 51 | static func == (lhs: CustomAlert, rhs: CustomAlert) -> Bool { 52 | lhs.hashValue == rhs.hashValue 53 | } 54 | 55 | func hash(into hasher: inout Hasher) { 56 | switch self { 57 | case .defaultAlert: 58 | hasher.combine("defaultAlert") 59 | } 60 | } 61 | 62 | case defaultAlert(yesAction: (()->Void)?, noAction: (()->Void)?) 63 | } 64 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Classes/Services/NavigationService/NavigationServiceType.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import Foundation 10 | 11 | /// The `NavigationServiceType` protocol defines a navigation service in the application, 12 | /// providing management of the view stack, modal windows, popups, and alerts. 13 | protocol NavigationServiceType: ObservableObject, Identifiable { 14 | 15 | /// An array of views that make up the current navigation stack. 16 | /// Used for managing transitions between screens. 17 | /// By default, this array is empty, and the root page is bound to the NavigationStack body in the RootView body. 18 | var items: [Views] { get set } 19 | 20 | /// The current modal view, if active. 21 | /// Can be `nil` if there is no active modal window. 22 | var modalView: Views? { get set } 23 | 24 | /// The current popup view, if active. 25 | /// Can be `nil` if no popup is displayed. 26 | var popupView: Views? { get set } 27 | 28 | /// The current alert (dialog window), if active. 29 | /// Can be `nil` if there are no active alerts. 30 | var alert: CustomAlert? { get set } 31 | } 32 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Resources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/RootApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import SwiftUI 10 | 11 | @main 12 | struct RootApp: App { 13 | 14 | @ObservedObject var appViewBuilder: ApplicationViewBuilder 15 | @ObservedObject var navigationService: NavigationService 16 | 17 | let container: DependencyContainer = { 18 | let factory = AssemblyFactory() 19 | let container = DependencyContainer(assemblyFactory: factory) 20 | 21 | // Services 22 | container.apply(NavigationAssembly.self) 23 | 24 | // Modules 25 | container.apply(MainAssembly.self) 26 | 27 | return container 28 | }() 29 | 30 | init() { 31 | navigationService = container.resolve(NavigationAssembly.self).build() as! NavigationService 32 | 33 | appViewBuilder = ApplicationViewBuilder(container: container) 34 | } 35 | 36 | 37 | var body: some Scene { 38 | WindowGroup { 39 | RootView(navigationService: navigationService, 40 | appViewBuilder: appViewBuilder) 41 | } 42 | } 43 | 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/RootView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | // Created by ___FULLUSERNAME___ on ___DATE___ 6 | // ___COPYRIGHT___ 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct RootView: View { 12 | 13 | @ObservedObject var navigationService: NavigationService 14 | @ObservedObject var appViewBuilder: ApplicationViewBuilder 15 | 16 | var body: some View { 17 | NavigationStack(path: $navigationService.items) { 18 | appViewBuilder.build(view: .main) 19 | .navigationDestination(for: Views.self) { path in 20 | switch path { 21 | default: 22 | fatalError() 23 | } 24 | } 25 | } 26 | .fullScreenCover(item: $navigationService.popupView) { item in 27 | appViewBuilder.build(view: item) 28 | .presentationBackground(.clear) 29 | } 30 | .fullScreenCover(item: $navigationService.modalView) { item in 31 | appViewBuilder.build(view: item) 32 | } 33 | .alert(isPresented: .constant($navigationService.alert.wrappedValue != nil)) { 34 | switch navigationService.alert { 35 | case .defaultAlert(let yesAction, let noAction): 36 | return Alert(title: Text("Title"), 37 | primaryButton: .default(Text("Yes"), action: yesAction), 38 | secondaryButton: .destructive(Text("No"), action: noAction)) 39 | case .none: 40 | fatalError() 41 | } 42 | } 43 | 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maukur/SwiftUI-Viper-Architecture/a937ceb96e8af93b6838197a4ae11c6d520f26de/Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maukur/SwiftUI-Viper-Architecture/a937ceb96e8af93b6838197a4ae11c6d520f26de/Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /Project Templates/iOS/Artem Tishchenko/VIPER.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.Xcode3.ProjectTemplateUnitKind 7 | Identifier 8 | com.artemtishchenko.unit.VIPER 9 | Ancestors 10 | 11 | com.artemtishchenko.unit.cocoaTouchApplicationBase 12 | com.apple.dt.unit.languageChoice 13 | 14 | Concrete 15 | 16 | Description 17 | This template provides a starting point for an SwiftUI 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 | RootApp.swift 32 | RootView.swift 33 | ApplicationViewBuilder.swift 34 | 35 | Classes/Architecture/InteractorProtocol.swift 36 | Classes/Architecture/PresenterProtocol.swift 37 | Classes/Architecture/RouterProtocol.swift 38 | Classes/Architecture/ViewStateProtocol.swift 39 | 40 | Classes/Modules/Main/Contracts/MainContracts.swift 41 | Classes/Modules/Main/Assembly/MainAssembly.swift 42 | Classes/Modules/Main/View/MainView.swift 43 | Classes/Modules/Main/ViewState/MainViewState.swift 44 | Classes/Modules/Main/Interactor/MainInteractor.swift 45 | Classes/Modules/Main/Presenter/MainPresenter.swift 46 | Classes/Modules/Main/Router/MainRouter.swift 47 | 48 | Classes/Services/NavigationService/NavigationAssembly.swift 49 | Classes/Services/NavigationService/NavigationServiceType.swift 50 | Classes/Services/NavigationService/NavigationService.swift 51 | 52 | Classes/Library/Swilby/Assembly.swift 53 | Classes/Library/Swilby/AssemblyFactory.swift 54 | Classes/Library/Swilby/DependencyContainer.swift 55 | Classes/Library/Swilby/ObjectKey.swift 56 | Classes/Library/Swilby/StrongBox.swift 57 | Classes/Library/Swilby/WeakBox.swift 58 | Classes/Library/Swilby/WeakContainer.swift 59 | 60 | 61 | 62 | 63 | 64 | Definitions 65 | 66 | 67 | 68 | RootApp.swift 69 | 70 | Path 71 | RootApp.swift 72 | 73 | 74 | 75 | 76 | RootView.swift 77 | 78 | Path 79 | RootView.swift 80 | 81 | 82 | 83 | 84 | ApplicationViewBuilder.swift 85 | 86 | Path 87 | ApplicationViewBuilder.swift 88 | 89 | 90 | 91 | 92 | Classes/Services/NavigationService/NavigationAssembly.swift 93 | 94 | Group 95 | 96 | Classes 97 | Services 98 | NavigationService 99 | 100 | Path 101 | Classes/Services/NavigationService/NavigationAssembly.swift 102 | 103 | 104 | Classes/Services/NavigationService/NavigationServiceType.swift 105 | 106 | Group 107 | 108 | Classes 109 | Services 110 | NavigationService 111 | 112 | Path 113 | Classes/Services/NavigationService/NavigationServiceType.swift 114 | 115 | 116 | Classes/Services/NavigationService/NavigationService.swift 117 | 118 | Group 119 | 120 | Classes 121 | Services 122 | NavigationService 123 | 124 | Path 125 | Classes/Services/NavigationService/NavigationService.swift 126 | 127 | 128 | 129 | 130 | Classes/Architecture/InteractorProtocol.swift 131 | 132 | Group 133 | 134 | Classes 135 | Architecture 136 | 137 | Path 138 | Classes/Architecture/InteractorProtocol.swift 139 | 140 | 141 | Classes/Architecture/ViewStateProtocol.swift 142 | 143 | Group 144 | 145 | Classes 146 | Architecture 147 | 148 | Path 149 | Classes/Architecture/ViewStateProtocol.swift 150 | 151 | 152 | Classes/Architecture/RouterProtocol.swift 153 | 154 | Group 155 | 156 | Classes 157 | Architecture 158 | 159 | Path 160 | Classes/Architecture/RouterProtocol.swift 161 | 162 | 163 | Classes/Architecture/PresenterProtocol.swift 164 | 165 | Group 166 | 167 | Classes 168 | Architecture 169 | 170 | Path 171 | Classes/Architecture/PresenterProtocol.swift 172 | 173 | 174 | 175 | 176 | 177 | Classes/Modules/Main/Contracts/MainContracts.swift 178 | 179 | Group 180 | 181 | Classes 182 | Modules 183 | Main 184 | Contracts 185 | 186 | Path 187 | Classes/Modules/Main/Contracts/MainContracts.swift 188 | 189 | 190 | Classes/Modules/Main/Assembly/MainAssembly.swift 191 | 192 | Group 193 | 194 | Classes 195 | Modules 196 | Main 197 | Assembly 198 | 199 | Path 200 | Classes/Modules/Main/Assembly/MainAssembly.swift 201 | 202 | 203 | Classes/Modules/Main/View/MainView.swift 204 | 205 | Group 206 | 207 | Classes 208 | Modules 209 | Main 210 | View 211 | 212 | Path 213 | Classes/Modules/Main/View/MainView.swift 214 | 215 | 216 | Classes/Modules/Main/ViewState/MainViewState.swift 217 | 218 | Group 219 | 220 | Classes 221 | Modules 222 | Main 223 | ViewState 224 | 225 | Path 226 | Classes/Modules/Main/ViewState/MainViewState.swift 227 | 228 | 229 | Classes/Modules/Main/Interactor/MainInteractor.swift 230 | 231 | Group 232 | 233 | Classes 234 | Modules 235 | Main 236 | Interactor 237 | 238 | Path 239 | Classes/Modules/Main/Interactor/MainInteractor.swift 240 | 241 | 242 | Classes/Modules/Main/Presenter/MainPresenter.swift 243 | 244 | Group 245 | 246 | Classes 247 | Modules 248 | Main 249 | Presenter 250 | 251 | Path 252 | Classes/Modules/Main/Presenter/MainPresenter.swift 253 | 254 | 255 | Classes/Modules/Main/Router/MainRouter.swift 256 | 257 | Group 258 | 259 | Classes 260 | Modules 261 | Main 262 | Router 263 | 264 | Path 265 | Classes/Modules/Main/Router/MainRouter.swift 266 | 267 | 268 | 269 | 270 | Classes/Library/Swilby/Assembly.swift 271 | 272 | Group 273 | 274 | Classes 275 | Library 276 | Swilby 277 | 278 | Path 279 | Classes/Library/Swilby/Assembly.swift 280 | 281 | 282 | 283 | Classes/Library/Swilby/AssemblyFactory.swift 284 | 285 | Group 286 | 287 | Classes 288 | Library 289 | Swilby 290 | 291 | Path 292 | Classes/Library/Swilby/AssemblyFactory.swift 293 | 294 | 295 | Classes/Library/Swilby/DependencyContainer.swift 296 | 297 | Group 298 | 299 | Classes 300 | Library 301 | Swilby 302 | 303 | Path 304 | Classes/Library/Swilby/DependencyContainer.swift 305 | 306 | Classes/Library/Swilby/ObjectKey.swift 307 | 308 | Group 309 | 310 | Classes 311 | Library 312 | Swilby 313 | 314 | Path 315 | Classes/Library/Swilby/ObjectKey.swift 316 | 317 | Classes/Library/Swilby/StrongBox.swift 318 | 319 | Group 320 | 321 | Classes 322 | Library 323 | Swilby 324 | 325 | Path 326 | Classes/Library/Swilby/StrongBox.swift 327 | 328 | Classes/Library/Swilby/WeakBox.swift 329 | 330 | Group 331 | 332 | Classes 333 | Library 334 | Swilby 335 | 336 | Path 337 | Classes/Library/Swilby/WeakBox.swift 338 | 339 | Classes/Library/Swilby/WeakContainer.swift 340 | 341 | Group 342 | 343 | Classes 344 | Library 345 | Swilby 346 | 347 | Path 348 | Classes/Library/Swilby/WeakContainer.swift 349 | 350 | 351 | 352 | 353 | 354 | -------------------------------------------------------------------------------- /README-RU.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | SwiftUI Viper Architecture - The development paradigm of clean, testable code and modular iOS applications. 4 | 5 | This repository contains Xcode templates for quickly creating a project, modules, and services. 6 | 7 | * [Viper](#viper) 8 | + [Introduction](#introduction) 9 | + [SwiftUI Viper Architecture](#swiftUI-viper-architecture) 10 | * [Installation](#installation) 11 | * [Requirements](#requirements) 12 | * [Example project](#example-project) 13 | * [Usage](#usage) 14 | + [Create a new Project](#create-a-new-project) 15 | + [Create a new Module](#create-a-new-module) 16 | + [Create a new Service](#create-a-new-service) 17 | * [Author](#author) 18 | * [License](#license) 19 | * [Special Thanks](#special-thanks) 20 | 21 | 22 | ## Viper 23 | 24 | ### Introduction 25 | 26 | VIPER (View, Interactor, Presenter, Entity, Router) — это архитектурный паттерн для построения приложений. В SwiftUI, этот паттерн не так распространен как в UIKit, но его все еще можно использовать для организации кода, если прибегнуть к маленькой хитрости и добавить такую сущность как ViewState. 27 | 28 | Для чего нужен ViewState и на какой концепции он основан ? 29 | 30 | ViewState (состояние представления) аналогичен @IBOutlet свойствам и данным, хранящимся в viewController, но в новой концепции с использованием @Published свойств и view. 31 | 32 | Давайте проясним это с помощью аналогии: 33 | 34 | #### Storyboard и ViewController: 35 | - Storyboard представляет собой визуальное представление пользовательского интерфейса, в котором вы размещаете элементы интерфейса, такие как кнопки, текстовые поля и др. Он отвечает за организацию и расположение элементов. 36 | - ViewController - это объект, управляющий взаимодействием между данными и интерфейсом. Он содержит логику, которая обрабатывает ввод пользователя, обновляет представление и работает с данными. 37 | #### View и ViewState: 38 | - View представляет собой базовый строительный блок в пользовательском интерфейсе. Это абстракция, которая отображает часть пользовательского интерфейса, такую как кнопка, текстовое поле, изображение и т.д. 39 | - ViewState - это абстракция, представляющая собой состояние представления в Swift. Она содержит данные, необходимые для отображения текущего состояния интерфейса. Это может быть, например, текущий текст в текстовом поле, выбранный элемент в таблице и т.д. 40 | 41 | ### SwiftUI Viper Architecture: 42 | 43 | #### View: 44 | - Отвечает за отображение данных пользователю и взаимодействие с пользователем. 45 | - Обрабатывает пользовательский ввод и передает его презентеру для обработки. 46 | 47 | #### ViewState: 48 | - Контролирует взаимодействие между Presenter и View. 49 | - Отвечает за хранение отображаемых данных в пользовательском интерфейсе. 50 | - Принимает пользовательский ввод от представления и преобразует в команды для презентера. 51 | 52 | #### Interactor: 53 | - Содержит бизнес-логику и правила для обработки данных. 54 | - Отвечает за запросы к хранилищу данных (например, базе данных, сети) и их обработку перед представлением. 55 | - Не содержит кода, связанного с отображением или интерфейсом пользователя. 56 | 57 | #### Presenter: 58 | - Отвечает за обработку данных от интерактора и подготовку их для отображения в пользовательском интерфейсе. 59 | - Контролирует взаимодействие между интерактором и представлением. 60 | - Принимает пользовательский ввод от представления, обрабатывает его и преобразует в команды для интерактора. 61 | 62 | #### Entity (Model): 63 | - Представляет собой объекты данных, которые используются в приложении. 64 | - Обычно является простыми объектами данных без методов, содержащими только свойства. 65 | 66 | #### Router: 67 | - Отвечает за навигацию между экранами приложения. 68 | - Решает, какой экран должен быть показан в ответ на определенные действия пользователя. 69 | 70 | ## Installation 71 | 72 | Only need execute this command in terminal: 73 | 74 | ```swift 75 | swift install.swift 76 | ``` 77 | 78 | ## Requirements 79 | 80 | * Xcode 10+ 81 | * Swift 4.2+ 82 | 83 | ## Example project 84 | 85 | [Download](https://github.com/maukur/SwiftUI-Viper-Example/) example project built on the basis of this paradigm. 86 | 87 | 88 | ## Usage 89 | 90 | ### Create a new Project 91 | 92 | ```swift 93 | Open Xcode 94 | File > New > Project or press shortcuts ⇧⌘N 95 | Choice VIPER Architecture 96 | Profit! 🎉 97 | ``` 98 | #### Project structure 99 | ```swift 100 | ┌── ApplicationViewBuilder.swift 101 | ├── RootApp.swift 102 | ├── RootView.swift 103 | └── Classes 104 | ├── Modules 105 | │ └── Main 106 | │ ├── Assembly 107 | │ │ └── MainAssembly.swift 108 | │ ├── Contracts 109 | │ │ └── MainContracts.swift 110 | │ ├── Interactor 111 | │ │ └── MainInteractor.swift 112 | │ ├── Presenter 113 | │ │ └── MainPresenter.swift 114 | │ ├── Router 115 | │ │ └── MainRouter.swift 116 | │ ├── View 117 | │ │ └── MainView.swift 118 | │ └── ViewState 119 | │ └── MainViewState.swift 120 | ├── Services 121 | │ └── NavigationService 122 | │ ├── NavigationAssembly.swift 123 | │ ├── NavigationService.swift 124 | │ └── NavigationServiceType.swift 125 | ├── Architecture 126 | │ ├── InteractorProtocol.swift 127 | │ ├── PresenterProtocol.swift 128 | │ ├── RouterProtocol.swift 129 | │ └── ViewStateProtocol.swift 130 | └── Library 131 | └── Swilby 132 | ├── Assembly.swift 133 | ├── AssemblyFactory.swift 134 | ├── DependencyContainer.swift 135 | ├── ObjectKey.swift 136 | ├── StrongBox.swift 137 | ├── WeakBox.swift 138 | └── WeakContainer.swift 139 | ``` 140 | 141 | ### Create a new Module 142 | 143 | 144 |
145 |
146 |
147 |
148 | 149 | ```swift 150 | Open Xcode Project 151 | Select Modules in Xcode Project Navigator 152 | Create new file 153 | File > New > File... or press shortcuts ⌘N 154 | Choice Module or Service 155 | Enter Name 156 | After you have created a Module you need to remove the reference on the folder 157 | Highlight the Folder in the Xcode Project Navigator 158 | Press Backspace Key 159 | Press "Remove Reference" in the alert window 160 | Now you need to return your Folder to the project. 161 | Drag the Folder from the Finder to the Xcode project 162 | Profit! 🎉 163 | ``` 164 | 165 | #### Module structure 166 | You can use different modules in one project based on the complexity of your screen. 167 | One screen - one module. 168 | 169 | All your modules should be in the "Modules" folder along the path "Classes/Assemblys/Modules" 170 | 171 | ```swift 172 | ┌── Assembly 173 | ├── Contracts 174 | ├── Interactor 175 | ├── Presenter 176 | ├── Router 177 | ├── View 178 | └── ViewState 179 | ``` 180 | #### Setup Modules 181 | Important! You need to add your Service, Module to the DI Container in the RootApp.swift 182 | 183 | ```swift 184 | container.apply(MainAssembly.self) 185 | // add your module here 186 | ``` 187 | 188 | ### Create a new Service 189 | 190 | 191 |
192 |
193 |
194 |
195 | 196 | ```swift 197 | Open Xcode Project 198 | Select Services in Xcode Project Navigator 199 | Create new file 200 | File > New > File... or press shortcuts ⌘N 201 | Choice Module or Service 202 | Enter Name (if you want to create "Service" you must specify at the end of the name "Service" for example - NetworkService or SettingsService) 203 | After you have created a Service you need to remove the reference on the folder 204 | Highlight the Folder in the Xcode Project Navigator 205 | Press Backspace Key 206 | Press "Remove Reference" in the alert window 207 | Now you need to return your Folder to the project. 208 | Drag the Folder from the Finder to the Xcode project 209 | Profit! 🎉 210 | ``` 211 | #### Service structure 212 | 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. 213 | 214 | All your services should be in the "Services" folder along the path "Classes/Assemblys/Services" 215 | 216 | You can learn more about the principle of developing SoA from [wikipedia](https://en.wikipedia.org/wiki/Service-oriented_architecture) 217 | 218 | ```swift 219 | ┌── ServiceAssembly 220 | ├── ServiceProtocol 221 | └── ServiceImplementation 222 | ``` 223 | #### Setup Services 224 | Important! You need to add your Service, Module to the DI Container in the RootApp.swift 225 | 226 | ```swift 227 | container.apply(NavigationServiceAssembly.self) 228 | // add your service here 229 | ``` 230 | 231 | 232 | ## Author 233 | 234 | 🧑🏻‍💻 Artem Tishchenko [Personal Blog](https://www.linkedin.com/in/tim-tis/) 235 | 236 | 237 | ## License 238 | 239 | MIT License 240 | 241 | Copyright (c) 2023 Artem Tishchenko 242 | 243 | Permission is hereby granted, free of charge, to any person obtaining a copy 244 | of this software and associated documentation files (the "Software"), to deal 245 | in the Software without restriction, including without limitation the rights 246 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 247 | copies of the Software, and to permit persons to whom the Software is 248 | furnished to do so, subject to the following conditions: 249 | 250 | The above copyright notice and this permission notice shall be included in all 251 | copies or substantial portions of the Software. 252 | 253 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 254 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 255 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 256 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 257 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 258 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 259 | SOFTWARE. 260 | 261 | BASED ON: [Core iOS Application Architecture](https://github.com/bartleby/Core-iOS-Application-Architecture) 262 | 263 | ## Special thanks 264 | * Artem Korenev - [LinkedIn](https://www.linkedin.com/in/artem-korenev-42b320243/) 265 | * Aleksei Artemev - [iDevs.io](https://idevs.io) 266 | * CustomerTimes iOS team - [Customertimes.com](https://customertimes.com/) 267 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | SwiftUI Viper Architecture - The development paradigm of clean, testable code and modular iOS applications. 4 | 5 | This repository contains Xcode templates for quickly creating a project, modules, and services. 6 | 7 | * [Viper](#viper) 8 | + [Introduction](#introduction) 9 | + [SwiftUI Viper Architecture](#swiftUI-viper-architecture) 10 | * [Installation](#installation) 11 | * [Requirements](#requirements) 12 | * [Example project](#example-project) 13 | * [Usage](#usage) 14 | + [Create a new Project](#create-a-new-project) 15 | + [Create a new Module](#create-a-new-module) 16 | + [Create a new Service](#create-a-new-service) 17 | * [Author](#author) 18 | * [License](#license) 19 | * [Special Thanks](#special-thanks) 20 | 21 | 22 | ## Viper 23 | 24 | ### Introduction 25 | 26 | VIPER (View, Interactor, Presenter, Entity, Router) is an architectural pattern for building applications. In SwiftUI, this pattern isn't as commonly used as in UIKit, but it can still be employed for code organization with a little trickery, by introducing an entity like ViewState. 27 | 28 | What is ViewState used for and what concept is it based on? 29 | 30 | ViewState (View State) is similar to @IBOutlet properties and data stored in a viewController, but in the new concept, it utilizes @Published properties and views. 31 | 32 | Let's clarify this with an analogy: 33 | 34 | #### Storyboard and ViewController: 35 | - Storyboard is a visual representation of the user interface where you place interface elements such as buttons, text fields, and others. It is responsible for organizing and positioning these elements. 36 | - ViewController is an object that manages the interaction between data and the interface. It contains the logic that handles user input, updates the view, and works with data. 37 | #### View and ViewState: 38 | - View is the basic building block in the user interface. It's an abstraction that represents a part of the user interface, such as a button, text field, image, etc. 39 | - ViewState is an abstraction representing the state of a view in Swift. It contains the data needed to display the current state of the interface. This could be, for example, the current text in a text field, the selected item in a table, etc. 40 | 41 | ### SwiftUI Viper Architecture: 42 | 43 | #### View: 44 | - Responsible for presenting data to the user and interacting with them. 45 | - Handles user input and passes it to the presenter for processing. 46 | 47 | #### ViewState: 48 | - Controls the interaction between the Presenter and the View. 49 | - Responsible for storing displayed data in the user interface. 50 | - Receives user input from the view and translates it into commands for the presenter. 51 | 52 | #### Interactor: 53 | - Contains the business logic and rules for processing data. 54 | - Handles requests to the data store (e.g., database, network) and processes them before presenting them. 55 | - Does not contain code related to presentation or user interface. 56 | 57 | #### Presenter: 58 | - Responsible for processing data from the interactor and preparing it for display in the user interface. 59 | - Controls the interaction between the interactor and the view. 60 | - Receives user input from the view, processes it, and converts it into commands for the interactor. 61 | 62 | #### Entity (Model): 63 | - Represents data objects used in the application. 64 | - Typically, they are simple data objects without methods, containing only properties. 65 | 66 | #### Router: 67 | - Handles navigation between screens in the application. 68 | - Decides which screen should be shown in response to specific user actions. 69 | 70 | ## Installation 71 | 72 | Only need execute this command in terminal: 73 | 74 | ```swift 75 | swift install.swift 76 | ``` 77 | 78 | ## Requirements 79 | 80 | * Xcode 14+ 81 | * Swift 5.7+ 82 | 83 | ## Example project 84 | 85 | [Download](https://github.com/maukur/SwiftUI-Viper-Example/) example project built on the basis of this paradigm. 86 | 87 | 88 | ## Usage 89 | 90 | ### Create a new Project 91 | 92 | ```swift 93 | Open Xcode 94 | File > New > Project or press shortcuts ⇧⌘N 95 | Select VIPER Architecture 96 | Profit! 🎉 97 | ``` 98 | #### Project structure 99 | ```swift 100 | ┌── ApplicationViewBuilder.swift 101 | ├── RootApp.swift 102 | ├── RootView.swift 103 | └── Classes 104 | ├── Modules 105 | │   └── Main 106 | │      ├── Assembly 107 | │      │   └── MainAssembly.swift 108 | │      ├── Contracts 109 | │      │   └── MainContracts.swift 110 | │      ├── Interactor 111 | │      │   └── MainInteractor.swift 112 | │      ├── Presenter 113 | │      │   └── MainPresenter.swift 114 | │      ├── Router 115 | │      │   └── MainRouter.swift 116 | │      ├── View 117 | │      │   └── MainView.swift 118 | │      └── ViewState 119 | │      └── MainViewState.swift 120 | ├── Services 121 | │   └── NavigationService 122 | │   ├── NavigationAssembly.swift 123 | │   ├── NavigationService.swift 124 | │   └── NavigationServiceType.swift 125 | ├── Architecture 126 | │   ├── InteractorProtocol.swift 127 | │   ├── PresenterProtocol.swift 128 | │   ├── RouterProtocol.swift 129 | │   └── ViewStateProtocol.swift 130 | └── Library 131 | └── Swilby 132 | ├── Assembly.swift 133 | ├── AssemblyFactory.swift 134 | ├── DependencyContainer.swift 135 | ├── ObjectKey.swift 136 | ├── StrongBox.swift 137 | ├── WeakBox.swift 138 | └── WeakContainer.swift 139 | ``` 140 | 141 | ### Create a new Module 142 | 143 | 144 |
145 |
146 |
147 |
148 | 149 | ```swift 150 | Open Xcode Project 151 | Select Modules in Xcode Project Navigator 152 | Create new file 153 | File > New > File... or press shortcuts ⌘N 154 | Select Module or Service 155 | Enter Name 156 | After you have created a Module you need to remove the reference on the folder 157 | Highlight the Folder in the Xcode Project Navigator 158 | Press Backspace Key 159 | Press "Remove Reference" in the alert window 160 | Now you need to return your Folder to the project. 161 | Drag the Folder from the Finder to the Xcode project 162 | Profit! 🎉 163 | ``` 164 | 165 | #### Module structure 166 | You can use different modules in one project based on the complexity of your screen. 167 | One screen - one module. 168 | 169 | All your modules should be in the "Modules" folder along the path "Classes/Assemblys/Modules" 170 | 171 | ```swift 172 | ┌── Assembly 173 | ├── Contracts 174 | ├── Interactor 175 | ├── Presenter 176 | ├── Router 177 | ├── View 178 | └── ViewState 179 | ``` 180 | #### Setup Modules 181 | Important! You need to add your Service, Module to the DI Container in the RootApp.swift 182 | 183 | ```swift 184 | container.apply(MainAssembly.self) 185 | // add your module here 186 | ``` 187 | 188 | ### Create a new Service 189 | 190 | 191 |
192 |
193 |
194 |
195 | 196 | ```swift 197 | Open Xcode Project 198 | Select Services in Xcode Project Navigator 199 | Create new file 200 | File > New > File... or press shortcuts ⌘N 201 | Select Module or Service 202 | Enter Name (if you want to create "Service" you must specify at the end of the name "Service" for example - NetworkService or SettingsService) 203 | After you have created a Service you need to remove the reference on the folder 204 | Highlight the Folder in the Xcode Project Navigator 205 | Press Backspace Key 206 | Press "Remove Reference" in the alert window 207 | Now you need to return your Folder to the project. 208 | Drag the Folder from the Finder to the Xcode project 209 | Profit! 🎉 210 | ``` 211 | #### Service structure 212 | 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. 213 | 214 | All your services should be in the "Services" folder along the path "Classes/Assemblys/Services" 215 | 216 | You can learn more about the principle of developing SoA from [wikipedia](https://en.wikipedia.org/wiki/Service-oriented_architecture) 217 | 218 | ```swift 219 | ┌── ServiceAssembly 220 | ├── ServiceProtocol 221 | └── ServiceImplementation 222 | ``` 223 | #### Setup Services 224 | Important! You need to add your Service, Module to the DI Container in the RootApp.swift 225 | 226 | ```swift 227 | container.apply(NavigationServiceAssembly.self) 228 | // add your service here 229 | ``` 230 | 231 | 232 | ## Author 233 | 234 | 🧑🏻‍💻 Artem Tishchenko [Personal Blog](https://www.linkedin.com/in/tim-tis/) 235 | 236 | 237 | ## License 238 | 239 | MIT License 240 | 241 | Copyright (c) 2023 Artem Tishchenko 242 | 243 | Permission is hereby granted, free of charge, to any person obtaining a copy 244 | of this software and associated documentation files (the "Software"), to deal 245 | in the Software without restriction, including without limitation the rights 246 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 247 | copies of the Software, and to permit persons to whom the Software is 248 | furnished to do so, subject to the following conditions: 249 | 250 | The above copyright notice and this permission notice shall be included in all 251 | copies or substantial portions of the Software. 252 | 253 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 254 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 255 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 256 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 257 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 258 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 259 | SOFTWARE. 260 | 261 | BASED ON: [Core iOS Application Architecture](https://github.com/bartleby/Core-iOS-Application-Architecture) 262 | 263 | ## Special thanks 264 | * Artem Korenev - [LinkedIn](https://www.linkedin.com/in/artem-korenev-42b320243/) 265 | * Aleksei Artemev - [iDevs.io](https://idevs.io) 266 | * CustomerTimes iOS team - [Customertimes.com](https://customertimes.com/) 267 | 268 | ## ☕️ Donate: 269 | If you find this repository useful, you can thank me 270 | 271 | [![](https://img.shields.io/badge/-Buy%20me%20a%20coffee-EDD347?logo=ko-fi)](https://www.buymeacoffee.com/x68mf5jw4yl) 272 | 273 | Or give a star the repository 274 | 275 | [![](https://img.shields.io/badge/Star_on-GitHub-lightgrey.svg)](https://github.com/maukur/SwiftUI-Viper-Architecture)
276 | 277 | 278 | -------------------------------------------------------------------------------- /install.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // Core-iOS-Application-Architecture 4 | // 5 | // Created by Artem Tishchenko on 11/11/2023 6 | // Copyright All rights reserved. 7 | // Special thanks Alexey Artemev from iDevs 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 | } --------------------------------------------------------------------------------