├── .gitignore ├── Examples └── ExampleProject │ ├── ExampleProject.xcodeproj │ ├── project.pbxproj │ └── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ ├── ExampleProject │ ├── Assets.xcassets │ │ ├── AccentColor.colorset │ │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── ExampleProjectApp.swift │ ├── Info.plist │ ├── Modules │ │ ├── Counter │ │ │ ├── CounterDefinitions.swift │ │ │ ├── CounterInteractor.swift │ │ │ ├── CounterModule.swift │ │ │ ├── CounterState.swift │ │ │ └── CounterView.swift │ │ └── MainModule │ │ │ ├── MainCoordinator.swift │ │ │ ├── MainDefinitions.swift │ │ │ ├── MainInteractor.swift │ │ │ ├── MainModule.swift │ │ │ ├── MainState.swift │ │ │ └── MainView.swift │ └── Preview Content │ │ └── Preview Assets.xcassets │ │ └── Contents.json │ ├── ExampleProjectTests │ ├── ExampleProjectTests.swift │ └── Info.plist │ └── ExampleProjectUITests │ ├── ExampleProjectUITests.swift │ └── Info.plist ├── LICENSE ├── README.md ├── Swift Module Architecture.xctemplate ├── SwiftUI View │ ├── ___FILEBASENAME___Definitions.swift │ ├── ___FILEBASENAME___Interactor.swift │ ├── ___FILEBASENAME___Module.swift │ ├── ___FILEBASENAME___State.swift │ └── ___FILEBASENAME___View.swift ├── SwiftUI ViewgenerateCoordinator │ ├── ___FILEBASENAME___Coordinator.swift │ ├── ___FILEBASENAME___Definitions.swift │ ├── ___FILEBASENAME___Interactor.swift │ ├── ___FILEBASENAME___Module.swift │ ├── ___FILEBASENAME___State.swift │ └── ___FILEBASENAME___View.swift ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist └── UIViewRepresentable │ ├── ___FILEBASENAME___Definitions.swift │ ├── ___FILEBASENAME___Interactor.swift │ ├── ___FILEBASENAME___Module.swift │ ├── ___FILEBASENAME___State.swift │ └── ___FILEBASENAME___View.swift └── module-architecture.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | F61DC5C026C95A6500B678EF /* MainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61DC5BA26C95A6500B678EF /* MainCoordinator.swift */; }; 11 | F61DC5C126C95A6500B678EF /* MainModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61DC5BB26C95A6500B678EF /* MainModule.swift */; }; 12 | F61DC5C226C95A6500B678EF /* MainInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61DC5BC26C95A6500B678EF /* MainInteractor.swift */; }; 13 | F61DC5C326C95A6500B678EF /* MainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61DC5BD26C95A6500B678EF /* MainView.swift */; }; 14 | F61DC5C426C95A6500B678EF /* MainDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61DC5BE26C95A6500B678EF /* MainDefinitions.swift */; }; 15 | F61DC5C526C95A6500B678EF /* MainState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F61DC5BF26C95A6500B678EF /* MainState.swift */; }; 16 | F6E8894C26C7E8BB00922E39 /* ExampleProjectApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E8894B26C7E8BB00922E39 /* ExampleProjectApp.swift */; }; 17 | F6E8895026C7E8BD00922E39 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F6E8894F26C7E8BD00922E39 /* Assets.xcassets */; }; 18 | F6E8895326C7E8BD00922E39 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F6E8895226C7E8BD00922E39 /* Preview Assets.xcassets */; }; 19 | F6E8895E26C7E8BD00922E39 /* ExampleProjectTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E8895D26C7E8BD00922E39 /* ExampleProjectTests.swift */; }; 20 | F6E8896926C7E8BD00922E39 /* ExampleProjectUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E8896826C7E8BD00922E39 /* ExampleProjectUITests.swift */; }; 21 | F6E8899326C7ED9600922E39 /* CounterModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E8898E26C7ED9600922E39 /* CounterModule.swift */; }; 22 | F6E8899426C7ED9600922E39 /* CounterInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E8898F26C7ED9600922E39 /* CounterInteractor.swift */; }; 23 | F6E8899526C7ED9600922E39 /* CounterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E8899026C7ED9600922E39 /* CounterView.swift */; }; 24 | F6E8899626C7ED9600922E39 /* CounterDefinitions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E8899126C7ED9600922E39 /* CounterDefinitions.swift */; }; 25 | F6E8899726C7ED9600922E39 /* CounterState.swift in Sources */ = {isa = PBXBuildFile; fileRef = F6E8899226C7ED9600922E39 /* CounterState.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | F6E8895A26C7E8BD00922E39 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = F6E8894026C7E8BB00922E39 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = F6E8894726C7E8BB00922E39; 34 | remoteInfo = ExampleProject; 35 | }; 36 | F6E8896526C7E8BD00922E39 /* PBXContainerItemProxy */ = { 37 | isa = PBXContainerItemProxy; 38 | containerPortal = F6E8894026C7E8BB00922E39 /* Project object */; 39 | proxyType = 1; 40 | remoteGlobalIDString = F6E8894726C7E8BB00922E39; 41 | remoteInfo = ExampleProject; 42 | }; 43 | /* End PBXContainerItemProxy section */ 44 | 45 | /* Begin PBXFileReference section */ 46 | F61DC5BA26C95A6500B678EF /* MainCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainCoordinator.swift; sourceTree = ""; }; 47 | F61DC5BB26C95A6500B678EF /* MainModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainModule.swift; sourceTree = ""; }; 48 | F61DC5BC26C95A6500B678EF /* MainInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainInteractor.swift; sourceTree = ""; }; 49 | F61DC5BD26C95A6500B678EF /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = ""; }; 50 | F61DC5BE26C95A6500B678EF /* MainDefinitions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainDefinitions.swift; sourceTree = ""; }; 51 | F61DC5BF26C95A6500B678EF /* MainState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainState.swift; sourceTree = ""; }; 52 | F6E8894826C7E8BB00922E39 /* ExampleProject.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ExampleProject.app; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | F6E8894B26C7E8BB00922E39 /* ExampleProjectApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleProjectApp.swift; sourceTree = ""; }; 54 | F6E8894F26C7E8BD00922E39 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | F6E8895226C7E8BD00922E39 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 56 | F6E8895426C7E8BD00922E39 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 57 | F6E8895926C7E8BD00922E39 /* ExampleProjectTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleProjectTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | F6E8895D26C7E8BD00922E39 /* ExampleProjectTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleProjectTests.swift; sourceTree = ""; }; 59 | F6E8895F26C7E8BD00922E39 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | F6E8896426C7E8BD00922E39 /* ExampleProjectUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ExampleProjectUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | F6E8896826C7E8BD00922E39 /* ExampleProjectUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleProjectUITests.swift; sourceTree = ""; }; 62 | F6E8896A26C7E8BD00922E39 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | F6E8898E26C7ED9600922E39 /* CounterModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterModule.swift; sourceTree = ""; }; 64 | F6E8898F26C7ED9600922E39 /* CounterInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterInteractor.swift; sourceTree = ""; }; 65 | F6E8899026C7ED9600922E39 /* CounterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterView.swift; sourceTree = ""; }; 66 | F6E8899126C7ED9600922E39 /* CounterDefinitions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterDefinitions.swift; sourceTree = ""; }; 67 | F6E8899226C7ED9600922E39 /* CounterState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterState.swift; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | F6E8894526C7E8BB00922E39 /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | F6E8895626C7E8BD00922E39 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | F6E8896126C7E8BD00922E39 /* Frameworks */ = { 86 | isa = PBXFrameworksBuildPhase; 87 | buildActionMask = 2147483647; 88 | files = ( 89 | ); 90 | runOnlyForDeploymentPostprocessing = 0; 91 | }; 92 | /* End PBXFrameworksBuildPhase section */ 93 | 94 | /* Begin PBXGroup section */ 95 | F6E8893F26C7E8BB00922E39 = { 96 | isa = PBXGroup; 97 | children = ( 98 | F6E8894A26C7E8BB00922E39 /* ExampleProject */, 99 | F6E8895C26C7E8BD00922E39 /* ExampleProjectTests */, 100 | F6E8896726C7E8BD00922E39 /* ExampleProjectUITests */, 101 | F6E8894926C7E8BB00922E39 /* Products */, 102 | ); 103 | sourceTree = ""; 104 | }; 105 | F6E8894926C7E8BB00922E39 /* Products */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | F6E8894826C7E8BB00922E39 /* ExampleProject.app */, 109 | F6E8895926C7E8BD00922E39 /* ExampleProjectTests.xctest */, 110 | F6E8896426C7E8BD00922E39 /* ExampleProjectUITests.xctest */, 111 | ); 112 | name = Products; 113 | sourceTree = ""; 114 | }; 115 | F6E8894A26C7E8BB00922E39 /* ExampleProject */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | F6E8897626C7E8D800922E39 /* Modules */, 119 | F6E8894B26C7E8BB00922E39 /* ExampleProjectApp.swift */, 120 | F6E8894F26C7E8BD00922E39 /* Assets.xcassets */, 121 | F6E8895426C7E8BD00922E39 /* Info.plist */, 122 | F6E8895126C7E8BD00922E39 /* Preview Content */, 123 | ); 124 | path = ExampleProject; 125 | sourceTree = ""; 126 | }; 127 | F6E8895126C7E8BD00922E39 /* Preview Content */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | F6E8895226C7E8BD00922E39 /* Preview Assets.xcassets */, 131 | ); 132 | path = "Preview Content"; 133 | sourceTree = ""; 134 | }; 135 | F6E8895C26C7E8BD00922E39 /* ExampleProjectTests */ = { 136 | isa = PBXGroup; 137 | children = ( 138 | F6E8895D26C7E8BD00922E39 /* ExampleProjectTests.swift */, 139 | F6E8895F26C7E8BD00922E39 /* Info.plist */, 140 | ); 141 | path = ExampleProjectTests; 142 | sourceTree = ""; 143 | }; 144 | F6E8896726C7E8BD00922E39 /* ExampleProjectUITests */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | F6E8896826C7E8BD00922E39 /* ExampleProjectUITests.swift */, 148 | F6E8896A26C7E8BD00922E39 /* Info.plist */, 149 | ); 150 | path = ExampleProjectUITests; 151 | sourceTree = ""; 152 | }; 153 | F6E8897626C7E8D800922E39 /* Modules */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | F6E8898D26C7ED8700922E39 /* Counter */, 157 | F6E8897726C7E8E400922E39 /* MainModule */, 158 | ); 159 | path = Modules; 160 | sourceTree = ""; 161 | }; 162 | F6E8897726C7E8E400922E39 /* MainModule */ = { 163 | isa = PBXGroup; 164 | children = ( 165 | F61DC5BA26C95A6500B678EF /* MainCoordinator.swift */, 166 | F61DC5BB26C95A6500B678EF /* MainModule.swift */, 167 | F61DC5BC26C95A6500B678EF /* MainInteractor.swift */, 168 | F61DC5BD26C95A6500B678EF /* MainView.swift */, 169 | F61DC5BE26C95A6500B678EF /* MainDefinitions.swift */, 170 | F61DC5BF26C95A6500B678EF /* MainState.swift */, 171 | ); 172 | path = MainModule; 173 | sourceTree = ""; 174 | }; 175 | F6E8898D26C7ED8700922E39 /* Counter */ = { 176 | isa = PBXGroup; 177 | children = ( 178 | F6E8898E26C7ED9600922E39 /* CounterModule.swift */, 179 | F6E8898F26C7ED9600922E39 /* CounterInteractor.swift */, 180 | F6E8899026C7ED9600922E39 /* CounterView.swift */, 181 | F6E8899126C7ED9600922E39 /* CounterDefinitions.swift */, 182 | F6E8899226C7ED9600922E39 /* CounterState.swift */, 183 | ); 184 | path = Counter; 185 | sourceTree = ""; 186 | }; 187 | /* End PBXGroup section */ 188 | 189 | /* Begin PBXNativeTarget section */ 190 | F6E8894726C7E8BB00922E39 /* ExampleProject */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = F6E8896D26C7E8BD00922E39 /* Build configuration list for PBXNativeTarget "ExampleProject" */; 193 | buildPhases = ( 194 | F6E8894426C7E8BB00922E39 /* Sources */, 195 | F6E8894526C7E8BB00922E39 /* Frameworks */, 196 | F6E8894626C7E8BB00922E39 /* Resources */, 197 | ); 198 | buildRules = ( 199 | ); 200 | dependencies = ( 201 | ); 202 | name = ExampleProject; 203 | productName = ExampleProject; 204 | productReference = F6E8894826C7E8BB00922E39 /* ExampleProject.app */; 205 | productType = "com.apple.product-type.application"; 206 | }; 207 | F6E8895826C7E8BD00922E39 /* ExampleProjectTests */ = { 208 | isa = PBXNativeTarget; 209 | buildConfigurationList = F6E8897026C7E8BD00922E39 /* Build configuration list for PBXNativeTarget "ExampleProjectTests" */; 210 | buildPhases = ( 211 | F6E8895526C7E8BD00922E39 /* Sources */, 212 | F6E8895626C7E8BD00922E39 /* Frameworks */, 213 | F6E8895726C7E8BD00922E39 /* Resources */, 214 | ); 215 | buildRules = ( 216 | ); 217 | dependencies = ( 218 | F6E8895B26C7E8BD00922E39 /* PBXTargetDependency */, 219 | ); 220 | name = ExampleProjectTests; 221 | productName = ExampleProjectTests; 222 | productReference = F6E8895926C7E8BD00922E39 /* ExampleProjectTests.xctest */; 223 | productType = "com.apple.product-type.bundle.unit-test"; 224 | }; 225 | F6E8896326C7E8BD00922E39 /* ExampleProjectUITests */ = { 226 | isa = PBXNativeTarget; 227 | buildConfigurationList = F6E8897326C7E8BD00922E39 /* Build configuration list for PBXNativeTarget "ExampleProjectUITests" */; 228 | buildPhases = ( 229 | F6E8896026C7E8BD00922E39 /* Sources */, 230 | F6E8896126C7E8BD00922E39 /* Frameworks */, 231 | F6E8896226C7E8BD00922E39 /* Resources */, 232 | ); 233 | buildRules = ( 234 | ); 235 | dependencies = ( 236 | F6E8896626C7E8BD00922E39 /* PBXTargetDependency */, 237 | ); 238 | name = ExampleProjectUITests; 239 | productName = ExampleProjectUITests; 240 | productReference = F6E8896426C7E8BD00922E39 /* ExampleProjectUITests.xctest */; 241 | productType = "com.apple.product-type.bundle.ui-testing"; 242 | }; 243 | /* End PBXNativeTarget section */ 244 | 245 | /* Begin PBXProject section */ 246 | F6E8894026C7E8BB00922E39 /* Project object */ = { 247 | isa = PBXProject; 248 | attributes = { 249 | LastSwiftUpdateCheck = 1250; 250 | LastUpgradeCheck = 1250; 251 | TargetAttributes = { 252 | F6E8894726C7E8BB00922E39 = { 253 | CreatedOnToolsVersion = 12.5.1; 254 | }; 255 | F6E8895826C7E8BD00922E39 = { 256 | CreatedOnToolsVersion = 12.5.1; 257 | TestTargetID = F6E8894726C7E8BB00922E39; 258 | }; 259 | F6E8896326C7E8BD00922E39 = { 260 | CreatedOnToolsVersion = 12.5.1; 261 | TestTargetID = F6E8894726C7E8BB00922E39; 262 | }; 263 | }; 264 | }; 265 | buildConfigurationList = F6E8894326C7E8BB00922E39 /* Build configuration list for PBXProject "ExampleProject" */; 266 | compatibilityVersion = "Xcode 9.3"; 267 | developmentRegion = en; 268 | hasScannedForEncodings = 0; 269 | knownRegions = ( 270 | en, 271 | Base, 272 | ); 273 | mainGroup = F6E8893F26C7E8BB00922E39; 274 | productRefGroup = F6E8894926C7E8BB00922E39 /* Products */; 275 | projectDirPath = ""; 276 | projectRoot = ""; 277 | targets = ( 278 | F6E8894726C7E8BB00922E39 /* ExampleProject */, 279 | F6E8895826C7E8BD00922E39 /* ExampleProjectTests */, 280 | F6E8896326C7E8BD00922E39 /* ExampleProjectUITests */, 281 | ); 282 | }; 283 | /* End PBXProject section */ 284 | 285 | /* Begin PBXResourcesBuildPhase section */ 286 | F6E8894626C7E8BB00922E39 /* Resources */ = { 287 | isa = PBXResourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | F6E8895326C7E8BD00922E39 /* Preview Assets.xcassets in Resources */, 291 | F6E8895026C7E8BD00922E39 /* Assets.xcassets in Resources */, 292 | ); 293 | runOnlyForDeploymentPostprocessing = 0; 294 | }; 295 | F6E8895726C7E8BD00922E39 /* Resources */ = { 296 | isa = PBXResourcesBuildPhase; 297 | buildActionMask = 2147483647; 298 | files = ( 299 | ); 300 | runOnlyForDeploymentPostprocessing = 0; 301 | }; 302 | F6E8896226C7E8BD00922E39 /* Resources */ = { 303 | isa = PBXResourcesBuildPhase; 304 | buildActionMask = 2147483647; 305 | files = ( 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | /* End PBXResourcesBuildPhase section */ 310 | 311 | /* Begin PBXSourcesBuildPhase section */ 312 | F6E8894426C7E8BB00922E39 /* Sources */ = { 313 | isa = PBXSourcesBuildPhase; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | F6E8894C26C7E8BB00922E39 /* ExampleProjectApp.swift in Sources */, 317 | F61DC5C026C95A6500B678EF /* MainCoordinator.swift in Sources */, 318 | F6E8899326C7ED9600922E39 /* CounterModule.swift in Sources */, 319 | F61DC5C326C95A6500B678EF /* MainView.swift in Sources */, 320 | F61DC5C526C95A6500B678EF /* MainState.swift in Sources */, 321 | F6E8899726C7ED9600922E39 /* CounterState.swift in Sources */, 322 | F6E8899426C7ED9600922E39 /* CounterInteractor.swift in Sources */, 323 | F61DC5C226C95A6500B678EF /* MainInteractor.swift in Sources */, 324 | F6E8899626C7ED9600922E39 /* CounterDefinitions.swift in Sources */, 325 | F61DC5C126C95A6500B678EF /* MainModule.swift in Sources */, 326 | F61DC5C426C95A6500B678EF /* MainDefinitions.swift in Sources */, 327 | F6E8899526C7ED9600922E39 /* CounterView.swift in Sources */, 328 | ); 329 | runOnlyForDeploymentPostprocessing = 0; 330 | }; 331 | F6E8895526C7E8BD00922E39 /* Sources */ = { 332 | isa = PBXSourcesBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | F6E8895E26C7E8BD00922E39 /* ExampleProjectTests.swift in Sources */, 336 | ); 337 | runOnlyForDeploymentPostprocessing = 0; 338 | }; 339 | F6E8896026C7E8BD00922E39 /* Sources */ = { 340 | isa = PBXSourcesBuildPhase; 341 | buildActionMask = 2147483647; 342 | files = ( 343 | F6E8896926C7E8BD00922E39 /* ExampleProjectUITests.swift in Sources */, 344 | ); 345 | runOnlyForDeploymentPostprocessing = 0; 346 | }; 347 | /* End PBXSourcesBuildPhase section */ 348 | 349 | /* Begin PBXTargetDependency section */ 350 | F6E8895B26C7E8BD00922E39 /* PBXTargetDependency */ = { 351 | isa = PBXTargetDependency; 352 | target = F6E8894726C7E8BB00922E39 /* ExampleProject */; 353 | targetProxy = F6E8895A26C7E8BD00922E39 /* PBXContainerItemProxy */; 354 | }; 355 | F6E8896626C7E8BD00922E39 /* PBXTargetDependency */ = { 356 | isa = PBXTargetDependency; 357 | target = F6E8894726C7E8BB00922E39 /* ExampleProject */; 358 | targetProxy = F6E8896526C7E8BD00922E39 /* PBXContainerItemProxy */; 359 | }; 360 | /* End PBXTargetDependency section */ 361 | 362 | /* Begin XCBuildConfiguration section */ 363 | F6E8896B26C7E8BD00922E39 /* Debug */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ALWAYS_SEARCH_USER_PATHS = NO; 367 | CLANG_ANALYZER_NONNULL = YES; 368 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 369 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 370 | CLANG_CXX_LIBRARY = "libc++"; 371 | CLANG_ENABLE_MODULES = YES; 372 | CLANG_ENABLE_OBJC_ARC = YES; 373 | CLANG_ENABLE_OBJC_WEAK = YES; 374 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 375 | CLANG_WARN_BOOL_CONVERSION = YES; 376 | CLANG_WARN_COMMA = YES; 377 | CLANG_WARN_CONSTANT_CONVERSION = YES; 378 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 379 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 380 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 381 | CLANG_WARN_EMPTY_BODY = YES; 382 | CLANG_WARN_ENUM_CONVERSION = YES; 383 | CLANG_WARN_INFINITE_RECURSION = YES; 384 | CLANG_WARN_INT_CONVERSION = YES; 385 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 386 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 387 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 388 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 389 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 390 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 391 | CLANG_WARN_STRICT_PROTOTYPES = YES; 392 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 393 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 394 | CLANG_WARN_UNREACHABLE_CODE = YES; 395 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 396 | COPY_PHASE_STRIP = NO; 397 | DEBUG_INFORMATION_FORMAT = dwarf; 398 | ENABLE_STRICT_OBJC_MSGSEND = YES; 399 | ENABLE_TESTABILITY = YES; 400 | GCC_C_LANGUAGE_STANDARD = gnu11; 401 | GCC_DYNAMIC_NO_PIC = NO; 402 | GCC_NO_COMMON_BLOCKS = YES; 403 | GCC_OPTIMIZATION_LEVEL = 0; 404 | GCC_PREPROCESSOR_DEFINITIONS = ( 405 | "DEBUG=1", 406 | "$(inherited)", 407 | ); 408 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 409 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 410 | GCC_WARN_UNDECLARED_SELECTOR = YES; 411 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 412 | GCC_WARN_UNUSED_FUNCTION = YES; 413 | GCC_WARN_UNUSED_VARIABLE = YES; 414 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 415 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 416 | MTL_FAST_MATH = YES; 417 | ONLY_ACTIVE_ARCH = YES; 418 | SDKROOT = iphoneos; 419 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 420 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 421 | }; 422 | name = Debug; 423 | }; 424 | F6E8896C26C7E8BD00922E39 /* Release */ = { 425 | isa = XCBuildConfiguration; 426 | buildSettings = { 427 | ALWAYS_SEARCH_USER_PATHS = NO; 428 | CLANG_ANALYZER_NONNULL = YES; 429 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 430 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 431 | CLANG_CXX_LIBRARY = "libc++"; 432 | CLANG_ENABLE_MODULES = YES; 433 | CLANG_ENABLE_OBJC_ARC = YES; 434 | CLANG_ENABLE_OBJC_WEAK = YES; 435 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 436 | CLANG_WARN_BOOL_CONVERSION = YES; 437 | CLANG_WARN_COMMA = YES; 438 | CLANG_WARN_CONSTANT_CONVERSION = YES; 439 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 440 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 441 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 442 | CLANG_WARN_EMPTY_BODY = YES; 443 | CLANG_WARN_ENUM_CONVERSION = YES; 444 | CLANG_WARN_INFINITE_RECURSION = YES; 445 | CLANG_WARN_INT_CONVERSION = YES; 446 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 447 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 448 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 449 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 450 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 451 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 452 | CLANG_WARN_STRICT_PROTOTYPES = YES; 453 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 454 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 455 | CLANG_WARN_UNREACHABLE_CODE = YES; 456 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 457 | COPY_PHASE_STRIP = NO; 458 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 459 | ENABLE_NS_ASSERTIONS = NO; 460 | ENABLE_STRICT_OBJC_MSGSEND = YES; 461 | GCC_C_LANGUAGE_STANDARD = gnu11; 462 | GCC_NO_COMMON_BLOCKS = YES; 463 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 464 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 465 | GCC_WARN_UNDECLARED_SELECTOR = YES; 466 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 467 | GCC_WARN_UNUSED_FUNCTION = YES; 468 | GCC_WARN_UNUSED_VARIABLE = YES; 469 | IPHONEOS_DEPLOYMENT_TARGET = 14.5; 470 | MTL_ENABLE_DEBUG_INFO = NO; 471 | MTL_FAST_MATH = YES; 472 | SDKROOT = iphoneos; 473 | SWIFT_COMPILATION_MODE = wholemodule; 474 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 475 | VALIDATE_PRODUCT = YES; 476 | }; 477 | name = Release; 478 | }; 479 | F6E8896E26C7E8BD00922E39 /* Debug */ = { 480 | isa = XCBuildConfiguration; 481 | buildSettings = { 482 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 483 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 484 | CODE_SIGN_STYLE = Automatic; 485 | DEVELOPMENT_ASSET_PATHS = "\"ExampleProject/Preview Content\""; 486 | DEVELOPMENT_TEAM = ""; 487 | ENABLE_PREVIEWS = YES; 488 | INFOPLIST_FILE = ExampleProject/Info.plist; 489 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 490 | LD_RUNPATH_SEARCH_PATHS = ( 491 | "$(inherited)", 492 | "@executable_path/Frameworks", 493 | ); 494 | PRODUCT_BUNDLE_IDENTIFIER = com.mvcs.ExampleProject; 495 | PRODUCT_NAME = "$(TARGET_NAME)"; 496 | SWIFT_VERSION = 5.0; 497 | TARGETED_DEVICE_FAMILY = "1,2"; 498 | }; 499 | name = Debug; 500 | }; 501 | F6E8896F26C7E8BD00922E39 /* Release */ = { 502 | isa = XCBuildConfiguration; 503 | buildSettings = { 504 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 505 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 506 | CODE_SIGN_STYLE = Automatic; 507 | DEVELOPMENT_ASSET_PATHS = "\"ExampleProject/Preview Content\""; 508 | DEVELOPMENT_TEAM = ""; 509 | ENABLE_PREVIEWS = YES; 510 | INFOPLIST_FILE = ExampleProject/Info.plist; 511 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 512 | LD_RUNPATH_SEARCH_PATHS = ( 513 | "$(inherited)", 514 | "@executable_path/Frameworks", 515 | ); 516 | PRODUCT_BUNDLE_IDENTIFIER = com.mvcs.ExampleProject; 517 | PRODUCT_NAME = "$(TARGET_NAME)"; 518 | SWIFT_VERSION = 5.0; 519 | TARGETED_DEVICE_FAMILY = "1,2"; 520 | }; 521 | name = Release; 522 | }; 523 | F6E8897126C7E8BD00922E39 /* Debug */ = { 524 | isa = XCBuildConfiguration; 525 | buildSettings = { 526 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 527 | BUNDLE_LOADER = "$(TEST_HOST)"; 528 | CODE_SIGN_STYLE = Automatic; 529 | DEVELOPMENT_TEAM = ""; 530 | INFOPLIST_FILE = ExampleProjectTests/Info.plist; 531 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 532 | LD_RUNPATH_SEARCH_PATHS = ( 533 | "$(inherited)", 534 | "@executable_path/Frameworks", 535 | "@loader_path/Frameworks", 536 | ); 537 | PRODUCT_BUNDLE_IDENTIFIER = com.mvcs.ExampleProjectTests; 538 | PRODUCT_NAME = "$(TARGET_NAME)"; 539 | SWIFT_VERSION = 5.0; 540 | TARGETED_DEVICE_FAMILY = "1,2"; 541 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ExampleProject.app/ExampleProject"; 542 | }; 543 | name = Debug; 544 | }; 545 | F6E8897226C7E8BD00922E39 /* Release */ = { 546 | isa = XCBuildConfiguration; 547 | buildSettings = { 548 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 549 | BUNDLE_LOADER = "$(TEST_HOST)"; 550 | CODE_SIGN_STYLE = Automatic; 551 | DEVELOPMENT_TEAM = ""; 552 | INFOPLIST_FILE = ExampleProjectTests/Info.plist; 553 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 554 | LD_RUNPATH_SEARCH_PATHS = ( 555 | "$(inherited)", 556 | "@executable_path/Frameworks", 557 | "@loader_path/Frameworks", 558 | ); 559 | PRODUCT_BUNDLE_IDENTIFIER = com.mvcs.ExampleProjectTests; 560 | PRODUCT_NAME = "$(TARGET_NAME)"; 561 | SWIFT_VERSION = 5.0; 562 | TARGETED_DEVICE_FAMILY = "1,2"; 563 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ExampleProject.app/ExampleProject"; 564 | }; 565 | name = Release; 566 | }; 567 | F6E8897426C7E8BD00922E39 /* Debug */ = { 568 | isa = XCBuildConfiguration; 569 | buildSettings = { 570 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 571 | CODE_SIGN_STYLE = Automatic; 572 | DEVELOPMENT_TEAM = ""; 573 | INFOPLIST_FILE = ExampleProjectUITests/Info.plist; 574 | LD_RUNPATH_SEARCH_PATHS = ( 575 | "$(inherited)", 576 | "@executable_path/Frameworks", 577 | "@loader_path/Frameworks", 578 | ); 579 | PRODUCT_BUNDLE_IDENTIFIER = com.mvcs.ExampleProjectUITests; 580 | PRODUCT_NAME = "$(TARGET_NAME)"; 581 | SWIFT_VERSION = 5.0; 582 | TARGETED_DEVICE_FAMILY = "1,2"; 583 | TEST_TARGET_NAME = ExampleProject; 584 | }; 585 | name = Debug; 586 | }; 587 | F6E8897526C7E8BD00922E39 /* Release */ = { 588 | isa = XCBuildConfiguration; 589 | buildSettings = { 590 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 591 | CODE_SIGN_STYLE = Automatic; 592 | DEVELOPMENT_TEAM = ""; 593 | INFOPLIST_FILE = ExampleProjectUITests/Info.plist; 594 | LD_RUNPATH_SEARCH_PATHS = ( 595 | "$(inherited)", 596 | "@executable_path/Frameworks", 597 | "@loader_path/Frameworks", 598 | ); 599 | PRODUCT_BUNDLE_IDENTIFIER = com.mvcs.ExampleProjectUITests; 600 | PRODUCT_NAME = "$(TARGET_NAME)"; 601 | SWIFT_VERSION = 5.0; 602 | TARGETED_DEVICE_FAMILY = "1,2"; 603 | TEST_TARGET_NAME = ExampleProject; 604 | }; 605 | name = Release; 606 | }; 607 | /* End XCBuildConfiguration section */ 608 | 609 | /* Begin XCConfigurationList section */ 610 | F6E8894326C7E8BB00922E39 /* Build configuration list for PBXProject "ExampleProject" */ = { 611 | isa = XCConfigurationList; 612 | buildConfigurations = ( 613 | F6E8896B26C7E8BD00922E39 /* Debug */, 614 | F6E8896C26C7E8BD00922E39 /* Release */, 615 | ); 616 | defaultConfigurationIsVisible = 0; 617 | defaultConfigurationName = Release; 618 | }; 619 | F6E8896D26C7E8BD00922E39 /* Build configuration list for PBXNativeTarget "ExampleProject" */ = { 620 | isa = XCConfigurationList; 621 | buildConfigurations = ( 622 | F6E8896E26C7E8BD00922E39 /* Debug */, 623 | F6E8896F26C7E8BD00922E39 /* Release */, 624 | ); 625 | defaultConfigurationIsVisible = 0; 626 | defaultConfigurationName = Release; 627 | }; 628 | F6E8897026C7E8BD00922E39 /* Build configuration list for PBXNativeTarget "ExampleProjectTests" */ = { 629 | isa = XCConfigurationList; 630 | buildConfigurations = ( 631 | F6E8897126C7E8BD00922E39 /* Debug */, 632 | F6E8897226C7E8BD00922E39 /* Release */, 633 | ); 634 | defaultConfigurationIsVisible = 0; 635 | defaultConfigurationName = Release; 636 | }; 637 | F6E8897326C7E8BD00922E39 /* Build configuration list for PBXNativeTarget "ExampleProjectUITests" */ = { 638 | isa = XCConfigurationList; 639 | buildConfigurations = ( 640 | F6E8897426C7E8BD00922E39 /* Debug */, 641 | F6E8897526C7E8BD00922E39 /* Release */, 642 | ); 643 | defaultConfigurationIsVisible = 0; 644 | defaultConfigurationName = Release; 645 | }; 646 | /* End XCConfigurationList section */ 647 | }; 648 | rootObject = F6E8894026C7E8BB00922E39 /* Project object */; 649 | } 650 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/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 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "scale" : "2x", 6 | "size" : "20x20" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "scale" : "3x", 11 | "size" : "20x20" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "scale" : "2x", 16 | "size" : "29x29" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "scale" : "3x", 21 | "size" : "29x29" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "scale" : "2x", 26 | "size" : "40x40" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "40x40" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "scale" : "2x", 36 | "size" : "60x60" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "scale" : "3x", 41 | "size" : "60x60" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "scale" : "1x", 46 | "size" : "20x20" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "scale" : "2x", 51 | "size" : "20x20" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "scale" : "1x", 56 | "size" : "29x29" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "scale" : "2x", 61 | "size" : "29x29" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "scale" : "1x", 66 | "size" : "40x40" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "scale" : "2x", 71 | "size" : "40x40" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "scale" : "1x", 76 | "size" : "76x76" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "scale" : "2x", 81 | "size" : "76x76" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "scale" : "2x", 86 | "size" : "83.5x83.5" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "scale" : "1x", 91 | "size" : "1024x1024" 92 | } 93 | ], 94 | "info" : { 95 | "author" : "xcode", 96 | "version" : 1 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/ExampleProjectApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleProjectApp.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 14/08/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct ExampleProjectApp: App { 12 | 13 | private var mainModule: MainModuleType { 14 | MainModule(counterModule: counterModule) 15 | } 16 | 17 | private var counterModule: CounterModuleType { 18 | CounterModule() 19 | } 20 | 21 | var body: some Scene { 22 | WindowGroup { 23 | mainModule.createMainView() 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | 28 | UIApplicationSupportsIndirectInputEvents 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UISupportedInterfaceOrientations 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UISupportedInterfaceOrientations~ipad 43 | 44 | UIInterfaceOrientationPortrait 45 | UIInterfaceOrientationPortraitUpsideDown 46 | UIInterfaceOrientationLandscapeLeft 47 | UIInterfaceOrientationLandscapeRight 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Modules/Counter/CounterDefinitions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CounterDefinitions.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 14/08/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | protocol CounterInteractorType: AnyObject { 12 | func incrementCounter() 13 | } 14 | 15 | protocol CounterModuleType { 16 | func createCounterView(startCounterAt value: Int) -> AnyView 17 | } 18 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Modules/Counter/CounterInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CounterInteractor.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 14/08/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class CounterInteractor { 11 | 12 | var state: CounterState 13 | 14 | init(state: CounterState) { 15 | 16 | self.state = state 17 | } 18 | } 19 | 20 | extension CounterInteractor: CounterInteractorType { 21 | func incrementCounter() { 22 | state.counter += 1 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Modules/Counter/CounterModule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CounterModule.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 14/08/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | class CounterModule { 11 | 12 | init() {} 13 | } 14 | 15 | extension CounterModule: CounterModuleType { 16 | 17 | func createCounterView(startCounterAt value: Int) -> AnyView { 18 | let state = CounterState(counter: value) 19 | let interactor = CounterInteractor(state: state) 20 | let view = CounterView(state: state, interactor: interactor) 21 | 22 | return AnyView(view) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Modules/Counter/CounterState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CounterState.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 14/08/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class CounterState: ObservableObject { 11 | 12 | @Published var counter: Int 13 | 14 | init(counter: Int = 0) { 15 | 16 | self.counter = counter 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Modules/Counter/CounterView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CounterView.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 14/08/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct CounterView: View { 11 | 12 | @ObservedObject var state: CounterState 13 | var interactor: CounterInteractorType 14 | 15 | var body: some View { 16 | Text("Counter: \(state.counter)") 17 | .padding(.bottom, 10) 18 | Button("Increment") { 19 | interactor.incrementCounter() 20 | } 21 | } 22 | } 23 | 24 | #if DEBUG 25 | struct CounterView_Previews: PreviewProvider { 26 | 27 | class MockInteractor: CounterInteractorType { 28 | 29 | var state: CounterState 30 | 31 | init(state: CounterState) { 32 | 33 | self.state = state 34 | } 35 | 36 | func incrementCounter() {} 37 | } 38 | 39 | static var previews: some View { 40 | let state = CounterState() 41 | let interactor = MockInteractor(state: state) 42 | let view = CounterView(state: state, interactor: interactor) 43 | 44 | return view 45 | } 46 | } 47 | #endif 48 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Modules/MainModule/MainCoordinator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainCoordinator.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 15/08/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | class MainCoordinator { 11 | 12 | private let counterModule: CounterModuleType 13 | @ObservedObject var state = CoordinatorState() 14 | 15 | init(counterModule: CounterModuleType) { 16 | self.counterModule = counterModule 17 | } 18 | 19 | @ViewBuilder func embededNavigation(content: Content) -> some View { 20 | content.modifier(NavigatableModifier(coordinatorState: self.state)) 21 | } 22 | 23 | class CoordinatorState: ObservableObject { 24 | @Published var shouldNavigate: Bool = false 25 | var destinationView: AnyView = AnyView(EmptyView()) 26 | } 27 | 28 | private struct NavigatableModifier: ViewModifier { 29 | @ObservedObject var coordinatorState: CoordinatorState 30 | 31 | func body(content: Self.Content) -> some View { 32 | NavigationView { 33 | ZStack { 34 | NavigationLink( 35 | "", 36 | destination: self.coordinatorState.destinationView, 37 | isActive: self.$coordinatorState.shouldNavigate 38 | ).hidden() 39 | content 40 | } 41 | }.navigationViewStyle(StackNavigationViewStyle()) 42 | } 43 | } 44 | } 45 | extension MainCoordinator: MainCoordinatorType { 46 | 47 | func presentView() { 48 | 49 | let counterView = self.counterModule.createCounterView(startCounterAt: 0) 50 | state.destinationView = AnyView(counterView) 51 | state.shouldNavigate = true 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Modules/MainModule/MainDefinitions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainDefinitions.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 15/08/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | protocol MainModuleType: AnyObject { 11 | func createMainView() -> AnyView 12 | } 13 | 14 | protocol MainInteractorType: AnyObject { 15 | func wantsToNavigateToEmptyView() 16 | } 17 | 18 | protocol MainCoordinatorType: AnyObject { 19 | 20 | func presentView() 21 | } 22 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Modules/MainModule/MainInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainInteractor.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 15/08/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class MainInteractor { 11 | 12 | var state: MainState 13 | var coordinator: MainCoordinatorType 14 | 15 | init( 16 | state: MainState, 17 | coordinator: MainCoordinatorType 18 | ) { 19 | 20 | self.state = state 21 | self.coordinator = coordinator 22 | } 23 | } 24 | 25 | extension MainInteractor: MainInteractorType { 26 | 27 | func wantsToNavigateToEmptyView() { 28 | coordinator.presentView() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Modules/MainModule/MainModule.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainModule.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 15/08/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | class MainModule { 11 | let counterModule: CounterModuleType 12 | 13 | init(counterModule: CounterModuleType) { 14 | self.counterModule = counterModule 15 | } 16 | } 17 | 18 | extension MainModule: MainModuleType { 19 | 20 | func createMainView() -> AnyView { 21 | let state = MainState() 22 | let coordinator = MainCoordinator(counterModule: counterModule) 23 | let interactor = MainInteractor(state: state, coordinator: coordinator) 24 | let view = MainView(state: state, interactor: interactor) 25 | 26 | return AnyView(coordinator.embededNavigation(content: view)) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Modules/MainModule/MainState.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainState.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 15/08/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class MainState: ObservableObject { 11 | 12 | @Published var someText: String 13 | 14 | init(someText: String = "") { 15 | 16 | self.someText = someText 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Modules/MainModule/MainView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MainView.swift 3 | // ExampleProject 4 | // 5 | // Created by Marcus Santos on 15/08/2021. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct MainView: View { 11 | 12 | @ObservedObject var state: MainState 13 | var interactor: MainInteractorType 14 | 15 | var body: some View { 16 | Button("Navigate to Counter screen") { 17 | interactor.wantsToNavigateToEmptyView() 18 | } 19 | } 20 | } 21 | 22 | #if DEBUG 23 | struct MainView_Previews: PreviewProvider { 24 | 25 | class MockInteractor: MainInteractorType { 26 | 27 | init() {} 28 | 29 | func wantsToNavigateToEmptyView() {} 30 | } 31 | 32 | static var previews: some View { 33 | let state = MainState() 34 | let interactor = MockInteractor() 35 | let view = MainView(state: state, interactor: interactor) 36 | 37 | return view 38 | } 39 | } 40 | #endif 41 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProject/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProjectTests/ExampleProjectTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleProjectTests.swift 3 | // ExampleProjectTests 4 | // 5 | // Created by Marcus Santos on 14/08/2021. 6 | // 7 | 8 | import XCTest 9 | @testable import ExampleProject 10 | 11 | class ExampleProjectTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | } 25 | 26 | func testPerformanceExample() throws { 27 | // This is an example of a performance test case. 28 | self.measure { 29 | // Put the code you want to measure the time of here. 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProjectTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProjectUITests/ExampleProjectUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleProjectUITests.swift 3 | // ExampleProjectUITests 4 | // 5 | // Created by Marcus Santos on 14/08/2021. 6 | // 7 | 8 | import XCTest 9 | 10 | class ExampleProjectUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use recording to get started writing UI tests. 31 | // Use XCTAssert and related functions to verify your tests produce the correct results. 32 | } 33 | 34 | func testLaunchPerformance() throws { 35 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 36 | // This measures how long it takes to launch your application. 37 | measure(metrics: [XCTApplicationLaunchMetric()]) { 38 | XCUIApplication().launch() 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Examples/ExampleProject/ExampleProjectUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Marcus Santos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Swiftui-architecture-template 3 | 4 | Swiftui Module Architecture is a simple Xcode template that creates boilerplate code for a modular and testable Application. 5 | 6 | The template contains no third party dependencies and it's intended to enforce clean architecture good pratices. 7 | 8 | This template contains 5 boilerplate files plus an optional *Coordinator*: 9 | 10 | * __View__: SwiftUI or UIViewRepresentable 11 | * __Interactor__: Business logic layer 12 | * __Coordinator__(optional): Holds other modules to navigate to 13 | * __State__: Observable object that contains the state of the views 14 | * __Module__: Instantiates the whole module and sets the communication delegates 15 | * __Definitions__: contains protocols or other helpers that shapes how the communication will happen between the different layers of the architecture. 16 | 17 | ![Module architecture diagram](https://github.com/mvcsantos/swiftui-architecture-template/blob/master/module-architecture.png) 18 | 19 | 20 | ## Installation 21 | 22 | Copy the *Swift Module Architecture.xctemplate* into the following directory: 23 | 24 | ```bash 25 | ~/Library/Developer/Xcode/Templates/Custom/ 26 | ``` 27 | 28 | 29 | ## Contributing 30 | Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change. 31 | 32 | 33 | ## License 34 | [MIT](https://choosealicense.com/licenses/mit/) 35 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/SwiftUI View/___FILEBASENAME___Definitions.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import Foundation 4 | import SwiftUI 5 | 6 | protocol ___VARIABLE_productName:identifier___InteractorType: AnyObject {} 7 | 8 | protocol ___VARIABLE_productName:identifier___ModuleType: AnyObject { 9 | func create___VARIABLE_productName:identifier___View() -> AnyView 10 | } 11 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/SwiftUI View/___FILEBASENAME___Interactor.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import Foundation 4 | 5 | class ___FILEBASENAMEASIDENTIFIER___ { 6 | 7 | var state: ___VARIABLE_productName:identifier___State 8 | 9 | init(state: ___VARIABLE_productName:identifier___State) { 10 | 11 | self.state = state 12 | } 13 | } 14 | 15 | extension ___FILEBASENAMEASIDENTIFIER___: ___VARIABLE_productName:identifier___InteractorType {} 16 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/SwiftUI View/___FILEBASENAME___Module.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import SwiftUI 4 | 5 | class ___FILEBASENAMEASIDENTIFIER___ { 6 | 7 | init() {} 8 | } 9 | 10 | extension ___FILEBASENAMEASIDENTIFIER___: ___FILEBASENAMEASIDENTIFIER___Type { 11 | 12 | func create___VARIABLE_productName:identifier___View() -> AnyView { 13 | let state = ___VARIABLE_productName:identifier___State(someText: "Hello World!") 14 | let interactor = ___VARIABLE_productName:identifier___Interactor(state: state) 15 | let view = ___VARIABLE_productName:identifier___View(state: state, interactor: interactor) 16 | 17 | return AnyView(view) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/SwiftUI View/___FILEBASENAME___State.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import Foundation 4 | 5 | class ___FILEBASENAMEASIDENTIFIER___: ObservableObject { 6 | 7 | @Published var someText: String 8 | 9 | init(someText: String = "") { 10 | 11 | self.someText = someText 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/SwiftUI View/___FILEBASENAME___View.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import SwiftUI 4 | 5 | struct ___FILEBASENAMEASIDENTIFIER___: View { 6 | 7 | @ObservedObject var state: ___VARIABLE_productName:identifier___State 8 | var interactor: ___VARIABLE_productName:identifier___InteractorType 9 | 10 | var body: some View { 11 | Text(state.someText) 12 | } 13 | } 14 | 15 | #if DEBUG 16 | struct ___FILEBASENAMEASIDENTIFIER____Previews: PreviewProvider { 17 | 18 | class MockInteractor: ___VARIABLE_productName:identifier___InteractorType { 19 | var state: ___VARIABLE_productName:identifier___State 20 | 21 | init(state: ___VARIABLE_productName:identifier___State) { 22 | 23 | self.state = state 24 | } 25 | } 26 | 27 | static var previews: some View { 28 | let state = ___VARIABLE_productName:identifier___State() 29 | let interactor = MockInteractor(state: state) 30 | let view = ___VARIABLE_productName:identifier___View(state: state, interactor: interactor) 31 | 32 | return view 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/SwiftUI ViewgenerateCoordinator/___FILEBASENAME___Coordinator.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import SwiftUI 4 | 5 | class ___FILEBASENAMEASIDENTIFIER___ { 6 | 7 | @ObservedObject var state = CoordinatorState() 8 | 9 | init() {} 10 | 11 | @ViewBuilder func embededNavigation(content: Content) -> some View { 12 | content.modifier(NavigatableModifier(coordinatorState: self.state)) 13 | } 14 | 15 | class CoordinatorState: ObservableObject { 16 | @Published var shouldNavigate: Bool = false 17 | var destinationView: AnyView = AnyView(EmptyView()) 18 | } 19 | 20 | private struct NavigatableModifier: ViewModifier { 21 | @ObservedObject var coordinatorState: CoordinatorState 22 | 23 | func body(content: Self.Content) -> some View { 24 | NavigationView { 25 | ZStack { 26 | NavigationLink( 27 | "", 28 | destination: self.coordinatorState.destinationView, 29 | isActive: self.$coordinatorState.shouldNavigate 30 | ).hidden() 31 | content 32 | } 33 | }.navigationViewStyle(StackNavigationViewStyle()) 34 | } 35 | } 36 | } 37 | extension ___FILEBASENAMEASIDENTIFIER___: ___VARIABLE_productName:identifier___CoordinatorType { 38 | 39 | func presentView() { 40 | 41 | state.destinationView = AnyView(Text("Hello from the other side!")) 42 | state.shouldNavigate = true 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/SwiftUI ViewgenerateCoordinator/___FILEBASENAME___Definitions.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import SwiftUI 4 | 5 | protocol ___VARIABLE_productName:identifier___ModuleType: AnyObject { 6 | func create___VARIABLE_productName:identifier___View() -> AnyView 7 | } 8 | 9 | protocol ___VARIABLE_productName:identifier___InteractorType: AnyObject { 10 | func wantsToNavigateToEmptyView() 11 | } 12 | 13 | protocol ___VARIABLE_productName:identifier___CoordinatorType: AnyObject { 14 | 15 | func presentView() 16 | } 17 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/SwiftUI ViewgenerateCoordinator/___FILEBASENAME___Interactor.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import Foundation 4 | 5 | class ___FILEBASENAMEASIDENTIFIER___ { 6 | 7 | var state: ___VARIABLE_productName:identifier___State 8 | var coordinator: ___VARIABLE_productName:identifier___CoordinatorType 9 | 10 | init( 11 | state: ___VARIABLE_productName:identifier___State, 12 | coordinator: ___VARIABLE_productName:identifier___CoordinatorType 13 | ) { 14 | 15 | self.state = state 16 | self.coordinator = coordinator 17 | } 18 | } 19 | 20 | extension ___FILEBASENAMEASIDENTIFIER___: ___VARIABLE_productName:identifier___InteractorType { 21 | 22 | func wantsToNavigateToEmptyView() { 23 | coordinator.presentView() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/SwiftUI ViewgenerateCoordinator/___FILEBASENAME___Module.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import SwiftUI 4 | 5 | class ___FILEBASENAMEASIDENTIFIER___ { 6 | 7 | init() {} 8 | } 9 | 10 | extension ___FILEBASENAMEASIDENTIFIER___: ___FILEBASENAMEASIDENTIFIER___Type { 11 | 12 | func create___VARIABLE_productName:identifier___View() -> AnyView { 13 | let state = ___VARIABLE_productName:identifier___State() 14 | let coordinator = ___VARIABLE_productName:identifier___Coordinator() 15 | let interactor = ___VARIABLE_productName:identifier___Interactor(state: state, coordinator: coordinator) 16 | let view = ___VARIABLE_productName:identifier___View(state: state, interactor: interactor) 17 | 18 | return AnyView(coordinator.embededNavigation(content: view)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/SwiftUI ViewgenerateCoordinator/___FILEBASENAME___State.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import Foundation 4 | 5 | class ___FILEBASENAMEASIDENTIFIER___: ObservableObject { 6 | 7 | @Published var someText: String 8 | 9 | init(someText: String = "") { 10 | 11 | self.someText = someText 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/SwiftUI ViewgenerateCoordinator/___FILEBASENAME___View.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import SwiftUI 4 | 5 | struct ___FILEBASENAMEASIDENTIFIER___: View { 6 | 7 | @ObservedObject var state: ___VARIABLE_productName:identifier___State 8 | var interactor: ___VARIABLE_productName:identifier___InteractorType 9 | 10 | var body: some View { 11 | Button("Navigate") { 12 | interactor.wantsToNavigateToEmptyView() 13 | } 14 | } 15 | } 16 | 17 | #if DEBUG 18 | struct ___FILEBASENAMEASIDENTIFIER____Previews: PreviewProvider { 19 | 20 | class MockInteractor: ___VARIABLE_productName:identifier___InteractorType { 21 | 22 | init() {} 23 | 24 | func wantsToNavigateToEmptyView() {} 25 | } 26 | 27 | static var previews: some View { 28 | let state = ___VARIABLE_productName:identifier___State() 29 | let interactor = MockInteractor() 30 | let view = ___VARIABLE_productName:identifier___View(state: state, interactor: interactor) 31 | 32 | return view 33 | } 34 | } 35 | #endif 36 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvcsantos/swiftui-architecture-template/2a50c1c72842b690a65cd15bd28d31c1cf794e5d/Swift Module Architecture.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvcsantos/swiftui-architecture-template/2a50c1c72842b690a65cd15bd28d31c1cf794e5d/Swift Module Architecture.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Kind 6 | Xcode.IDEFoundation.TextSubstitutionFileTemplateKind 7 | Description 8 | An empty Swift file. 9 | Summary 10 | An empty Swift file 11 | SortOrder 12 | 30 13 | AllowedTypes 14 | 15 | public.swift-source 16 | 17 | DefaultCompletionName 18 | File 19 | Options 20 | 21 | 22 | Identifier 23 | productName 24 | Required 25 | 26 | Name 27 | Name: 28 | Description 29 | The name of the module to create 30 | Type 31 | text 32 | Default 33 | MyModule 34 | 35 | 36 | Identifier 37 | viewType 38 | Required 39 | 40 | Name 41 | View type: 42 | Description 43 | The type of view to use 44 | Type 45 | popup 46 | Default 47 | SwiftUI View 48 | Values 49 | 50 | SwiftUI View 51 | UIViewRepresentable 52 | 53 | 54 | 55 | 56 | Identifier 57 | generateCoordinator 58 | Name 59 | Also create a Coordinator 60 | Description 61 | Whether to include a Coordinator or not 62 | Type 63 | checkbox 64 | RequiredOptions 65 | 66 | viewType 67 | 68 | SwiftUI View 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/UIViewRepresentable/___FILEBASENAME___Definitions.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import Foundation 4 | 5 | protocol ___VARIABLE_productName:identifier___ViewDelegate: AnyObject {} 6 | 7 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/UIViewRepresentable/___FILEBASENAME___Interactor.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import SwiftUI 4 | 5 | class ___FILEBASENAMEASIDENTIFIER___ { 6 | 7 | @ObservedObject var state: ___VARIABLE_productName:identifier___State 8 | 9 | init() { 10 | 11 | self.state = ___VARIABLE_productName:identifier___State() 12 | } 13 | } 14 | 15 | extension ___FILEBASENAMEASIDENTIFIER___: ___VARIABLE_productName:identifier___ViewDelegate {} 16 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/UIViewRepresentable/___FILEBASENAME___Module.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import SwiftUI 4 | 5 | class ___FILEBASENAMEASIDENTIFIER___ { 6 | 7 | init() {} 8 | 9 | func create___VARIABLE_productName:identifier___View() -> some View { 10 | 11 | let interactor = ___VARIABLE_productName:identifier___Interactor() 12 | 13 | var view = ___VARIABLE_productName:identifier___View(state: interactor.state) 14 | view.delegate = interactor 15 | 16 | return view 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/UIViewRepresentable/___FILEBASENAME___State.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import Foundation 4 | 5 | class ___FILEBASENAMEASIDENTIFIER___: ObservableObject { 6 | 7 | @Published var someVariable: String 8 | 9 | init(example: String = "") { 10 | 11 | self.someVariable = example 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Swift Module Architecture.xctemplate/UIViewRepresentable/___FILEBASENAME___View.swift: -------------------------------------------------------------------------------- 1 | //___FILEHEADER___ 2 | 3 | import SwiftUI 4 | 5 | final class ___FILEBASENAMEASIDENTIFIER___: UIViewRepresentable { 6 | 7 | @ObservedObject var state: ___VARIABLE_productName:identifier___State 8 | var delegate: ___FILEBASENAMEASIDENTIFIER___Delegate? 9 | 10 | init(state: ___VARIABLE_productName:identifier___State, delegate: ___FILEBASENAMEASIDENTIFIER___Delegate? = nil) { 11 | self.state = state 12 | self.delegate = delegate 13 | } 14 | 15 | func makeCoordinator() -> Coordinator { 16 | return Coordinator(parent: self) 17 | } 18 | 19 | func updateUIView(_ uiView: UIView, context: UIViewRepresentableContext<___FILEBASENAMEASIDENTIFIER___>) { 20 | 21 | } 22 | 23 | func makeUIView(context: UIViewRepresentableContext<___FILEBASENAMEASIDENTIFIER___>) -> UIView { 24 | 25 | return UIView() 26 | } 27 | 28 | class Coordinator: NSObject { 29 | let parent: ___FILEBASENAMEASIDENTIFIER___ 30 | init(parent: ___FILEBASENAMEASIDENTIFIER___) { 31 | self.parent = parent 32 | } 33 | } 34 | } 35 | 36 | #if DEBUG 37 | struct ___FILEBASENAMEASIDENTIFIER____Previews: PreviewProvider { 38 | 39 | static var previews: some View { 40 | let interactor = ___VARIABLE_productName:identifier___Interactor() 41 | let view = ___FILEBASENAMEASIDENTIFIER___(state: interactor.state) 42 | view.delegate = interactor 43 | 44 | return view 45 | } 46 | } 47 | #endif 48 | -------------------------------------------------------------------------------- /module-architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mvcsantos/swiftui-architecture-template/2a50c1c72842b690a65cd15bd28d31c1cf794e5d/module-architecture.png --------------------------------------------------------------------------------