├── CleanSwiftWithSwiftUI.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcshareddata │ └── xcschemes │ │ └── CleanSwiftWithSwiftUI.xcscheme └── xcuserdata │ └── patrykstrzemiecki.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist ├── CleanSwiftWithSwiftUI ├── AppDelegate.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Factories │ └── ScenesFactory.swift ├── Info.plist ├── SceneDelegate.swift └── Scenes │ ├── Details │ ├── DetailsSceneConfigurator.swift │ ├── DetailsSceneInteractor.swift │ ├── DetailsScenePresenter.swift │ ├── DetailsSceneRouter.swift │ ├── DetailsSceneView.swift │ ├── DetailsSceneViewController.swift │ └── Models │ │ └── DetailsSceneViewModel.swift │ ├── Other │ ├── OtherSceneConfigurator.swift │ ├── OtherSceneInteractor.swift │ ├── OtherScenePresenter.swift │ ├── OtherSceneRouter.swift │ ├── OtherSceneView.swift │ └── OtherSceneViewController.swift │ └── Welcome │ ├── Models │ └── WelcomeSceneViewModel.swift │ ├── WelcomeSceneConfigurator.swift │ ├── WelcomeSceneInteractor.swift │ ├── WelcomeScenePresenter.swift │ ├── WelcomeSceneRouter.swift │ ├── WelcomeSceneView.swift │ └── WelcomeSceneViewController.swift ├── CleanSwiftWithSwiftUITests └── CleanSwiftWithSwiftUITests.swift ├── CleanSwiftWithSwiftUnitTests └── Scenes │ ├── Other │ ├── OtherSceneConfiguratorTests.swift │ ├── OtherSceneInteractorTests.swift │ ├── OtherScenePresenterTests.swift │ ├── OtherSceneRouterTests.swift │ ├── OtherSceneViewControllerTests.swift │ └── OtherSceneViewModelTests.swift │ └── Welcome │ ├── WelcomeSceneConfiguratorTests.swift │ ├── WelcomeSceneInteractorTests.swift │ ├── WelcomeScenePresenterTests.swift │ ├── WelcomeSceneRouterTests.swift │ ├── WelcomeSceneViewControllerTests.swift │ └── WelcomeSceneViewModelTests.swift ├── README.md └── XcodeTemplates └── CleanSwift + SwiftUI ├── Scene.xctemplate ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist ├── ___FILEBASENAME___Configurator.swift ├── ___FILEBASENAME___Interactor.swift ├── ___FILEBASENAME___Presenter.swift ├── ___FILEBASENAME___Router.swift ├── ___FILEBASENAME___View.swift └── ___FILEBASENAME___ViewController.swift └── Unit Tests.xctemplate ├── TemplateIcon.png ├── TemplateIcon@2x.png ├── TemplateInfo.plist ├── ___FILEBASENAME___ConfiguratorTests.swift ├── ___FILEBASENAME___InteractorTests.swift ├── ___FILEBASENAME___PresenterTests.swift ├── ___FILEBASENAME___RouterTests.swift ├── ___FILEBASENAME___ViewControllerTests.swift └── ___FILEBASENAME___ViewModelTests.swift /CleanSwiftWithSwiftUI.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 55; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 3680184127D3E06D00F9F6F3 /* OtherScenePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680183B27D3E06D00F9F6F3 /* OtherScenePresenter.swift */; }; 11 | 3680184227D3E06D00F9F6F3 /* OtherSceneRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680183C27D3E06D00F9F6F3 /* OtherSceneRouter.swift */; }; 12 | 3680184327D3E06D00F9F6F3 /* OtherSceneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680183D27D3E06D00F9F6F3 /* OtherSceneViewController.swift */; }; 13 | 3680184427D3E06D00F9F6F3 /* OtherSceneInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680183E27D3E06D00F9F6F3 /* OtherSceneInteractor.swift */; }; 14 | 3680184527D3E06D00F9F6F3 /* OtherSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680183F27D3E06D00F9F6F3 /* OtherSceneView.swift */; }; 15 | 3680184627D3E06D00F9F6F3 /* OtherSceneConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680184027D3E06D00F9F6F3 /* OtherSceneConfigurator.swift */; }; 16 | 3680188227D3E53B00F9F6F3 /* OtherSceneViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680187C27D3E53B00F9F6F3 /* OtherSceneViewControllerTests.swift */; }; 17 | 3680188327D3E53B00F9F6F3 /* OtherSceneInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680187D27D3E53B00F9F6F3 /* OtherSceneInteractorTests.swift */; }; 18 | 3680188427D3E53B00F9F6F3 /* OtherSceneViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680187E27D3E53B00F9F6F3 /* OtherSceneViewModelTests.swift */; }; 19 | 3680188527D3E53B00F9F6F3 /* OtherSceneRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680187F27D3E53B00F9F6F3 /* OtherSceneRouterTests.swift */; }; 20 | 3680188627D3E53B00F9F6F3 /* OtherScenePresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680188027D3E53B00F9F6F3 /* OtherScenePresenterTests.swift */; }; 21 | 3680188727D3E53B00F9F6F3 /* OtherSceneConfiguratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680188127D3E53B00F9F6F3 /* OtherSceneConfiguratorTests.swift */; }; 22 | 3680188F27D3E5A600F9F6F3 /* WelcomeSceneViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680188927D3E5A600F9F6F3 /* WelcomeSceneViewControllerTests.swift */; }; 23 | 3680189027D3E5A600F9F6F3 /* WelcomeSceneInteractorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680188A27D3E5A600F9F6F3 /* WelcomeSceneInteractorTests.swift */; }; 24 | 3680189127D3E5A600F9F6F3 /* WelcomeSceneViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680188B27D3E5A600F9F6F3 /* WelcomeSceneViewModelTests.swift */; }; 25 | 3680189227D3E5A600F9F6F3 /* WelcomeSceneRouterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680188C27D3E5A600F9F6F3 /* WelcomeSceneRouterTests.swift */; }; 26 | 3680189327D3E5A600F9F6F3 /* WelcomeScenePresenterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680188D27D3E5A600F9F6F3 /* WelcomeScenePresenterTests.swift */; }; 27 | 3680189427D3E5A600F9F6F3 /* WelcomeSceneConfiguratorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3680188E27D3E5A600F9F6F3 /* WelcomeSceneConfiguratorTests.swift */; }; 28 | 36E4C0DB27C98271002641FA /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C0DA27C98271002641FA /* SceneDelegate.swift */; }; 29 | 36E4C0E227C98272002641FA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 36E4C0E127C98272002641FA /* Assets.xcassets */; }; 30 | 36E4C0E527C98272002641FA /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 36E4C0E327C98272002641FA /* LaunchScreen.storyboard */; }; 31 | 36E4C0ED27C982E6002641FA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C0EC27C982E6002641FA /* AppDelegate.swift */; }; 32 | 36E4C0F627C98331002641FA /* WelcomeScenePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C0F027C98331002641FA /* WelcomeScenePresenter.swift */; }; 33 | 36E4C0F727C98331002641FA /* WelcomeSceneRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C0F127C98331002641FA /* WelcomeSceneRouter.swift */; }; 34 | 36E4C0F827C98331002641FA /* WelcomeSceneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C0F227C98331002641FA /* WelcomeSceneViewController.swift */; }; 35 | 36E4C0F927C98331002641FA /* WelcomeSceneInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C0F327C98331002641FA /* WelcomeSceneInteractor.swift */; }; 36 | 36E4C0FA27C98331002641FA /* WelcomeSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C0F427C98331002641FA /* WelcomeSceneView.swift */; }; 37 | 36E4C0FB27C98331002641FA /* WelcomeSceneConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C0F527C98331002641FA /* WelcomeSceneConfigurator.swift */; }; 38 | 36E4C0FD27C984B7002641FA /* WelcomeSceneViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C0FC27C984B7002641FA /* WelcomeSceneViewModel.swift */; }; 39 | 36E4C10027C98622002641FA /* ScenesFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C0FF27C98622002641FA /* ScenesFactory.swift */; }; 40 | 36E4C10B27C9884C002641FA /* DetailsSceneView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C10327C9884C002641FA /* DetailsSceneView.swift */; }; 41 | 36E4C10C27C9884C002641FA /* DetailsSceneViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C10527C9884C002641FA /* DetailsSceneViewModel.swift */; }; 42 | 36E4C10D27C9884C002641FA /* DetailsScenePresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C10627C9884C002641FA /* DetailsScenePresenter.swift */; }; 43 | 36E4C10E27C9884C002641FA /* DetailsSceneConfigurator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C10727C9884C002641FA /* DetailsSceneConfigurator.swift */; }; 44 | 36E4C10F27C9884C002641FA /* DetailsSceneViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C10827C9884C002641FA /* DetailsSceneViewController.swift */; }; 45 | 36E4C11027C9884C002641FA /* DetailsSceneRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C10927C9884C002641FA /* DetailsSceneRouter.swift */; }; 46 | 36E4C11127C9884C002641FA /* DetailsSceneInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36E4C10A27C9884C002641FA /* DetailsSceneInteractor.swift */; }; 47 | /* End PBXBuildFile section */ 48 | 49 | /* Begin PBXContainerItemProxy section */ 50 | 3680186927D3E1B300F9F6F3 /* PBXContainerItemProxy */ = { 51 | isa = PBXContainerItemProxy; 52 | containerPortal = 36E4C0CD27C98271002641FA /* Project object */; 53 | proxyType = 1; 54 | remoteGlobalIDString = 36E4C0D427C98271002641FA; 55 | remoteInfo = CleanSwiftWithSwiftUI; 56 | }; 57 | /* End PBXContainerItemProxy section */ 58 | 59 | /* Begin PBXFileReference section */ 60 | 3680183B27D3E06D00F9F6F3 /* OtherScenePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherScenePresenter.swift; sourceTree = ""; }; 61 | 3680183C27D3E06D00F9F6F3 /* OtherSceneRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSceneRouter.swift; sourceTree = ""; }; 62 | 3680183D27D3E06D00F9F6F3 /* OtherSceneViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSceneViewController.swift; sourceTree = ""; }; 63 | 3680183E27D3E06D00F9F6F3 /* OtherSceneInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSceneInteractor.swift; sourceTree = ""; }; 64 | 3680183F27D3E06D00F9F6F3 /* OtherSceneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSceneView.swift; sourceTree = ""; }; 65 | 3680184027D3E06D00F9F6F3 /* OtherSceneConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSceneConfigurator.swift; sourceTree = ""; }; 66 | 3680186527D3E1B300F9F6F3 /* CleanSwiftWithSwiftUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CleanSwiftWithSwiftUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 67 | 3680187C27D3E53B00F9F6F3 /* OtherSceneViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSceneViewControllerTests.swift; sourceTree = ""; }; 68 | 3680187D27D3E53B00F9F6F3 /* OtherSceneInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSceneInteractorTests.swift; sourceTree = ""; }; 69 | 3680187E27D3E53B00F9F6F3 /* OtherSceneViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSceneViewModelTests.swift; sourceTree = ""; }; 70 | 3680187F27D3E53B00F9F6F3 /* OtherSceneRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSceneRouterTests.swift; sourceTree = ""; }; 71 | 3680188027D3E53B00F9F6F3 /* OtherScenePresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherScenePresenterTests.swift; sourceTree = ""; }; 72 | 3680188127D3E53B00F9F6F3 /* OtherSceneConfiguratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherSceneConfiguratorTests.swift; sourceTree = ""; }; 73 | 3680188927D3E5A600F9F6F3 /* WelcomeSceneViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSceneViewControllerTests.swift; sourceTree = ""; }; 74 | 3680188A27D3E5A600F9F6F3 /* WelcomeSceneInteractorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSceneInteractorTests.swift; sourceTree = ""; }; 75 | 3680188B27D3E5A600F9F6F3 /* WelcomeSceneViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSceneViewModelTests.swift; sourceTree = ""; }; 76 | 3680188C27D3E5A600F9F6F3 /* WelcomeSceneRouterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSceneRouterTests.swift; sourceTree = ""; }; 77 | 3680188D27D3E5A600F9F6F3 /* WelcomeScenePresenterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScenePresenterTests.swift; sourceTree = ""; }; 78 | 3680188E27D3E5A600F9F6F3 /* WelcomeSceneConfiguratorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSceneConfiguratorTests.swift; sourceTree = ""; }; 79 | 36E4C0D527C98271002641FA /* CleanSwiftWithSwiftUI.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CleanSwiftWithSwiftUI.app; sourceTree = BUILT_PRODUCTS_DIR; }; 80 | 36E4C0DA27C98271002641FA /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 81 | 36E4C0E127C98272002641FA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 82 | 36E4C0E427C98272002641FA /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 83 | 36E4C0E627C98272002641FA /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 84 | 36E4C0EC27C982E6002641FA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 85 | 36E4C0F027C98331002641FA /* WelcomeScenePresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeScenePresenter.swift; sourceTree = ""; }; 86 | 36E4C0F127C98331002641FA /* WelcomeSceneRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSceneRouter.swift; sourceTree = ""; }; 87 | 36E4C0F227C98331002641FA /* WelcomeSceneViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSceneViewController.swift; sourceTree = ""; }; 88 | 36E4C0F327C98331002641FA /* WelcomeSceneInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSceneInteractor.swift; sourceTree = ""; }; 89 | 36E4C0F427C98331002641FA /* WelcomeSceneView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSceneView.swift; sourceTree = ""; }; 90 | 36E4C0F527C98331002641FA /* WelcomeSceneConfigurator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSceneConfigurator.swift; sourceTree = ""; }; 91 | 36E4C0FC27C984B7002641FA /* WelcomeSceneViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeSceneViewModel.swift; sourceTree = ""; }; 92 | 36E4C0FF27C98622002641FA /* ScenesFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScenesFactory.swift; sourceTree = ""; }; 93 | 36E4C10327C9884C002641FA /* DetailsSceneView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailsSceneView.swift; sourceTree = ""; }; 94 | 36E4C10527C9884C002641FA /* DetailsSceneViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailsSceneViewModel.swift; sourceTree = ""; }; 95 | 36E4C10627C9884C002641FA /* DetailsScenePresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailsScenePresenter.swift; sourceTree = ""; }; 96 | 36E4C10727C9884C002641FA /* DetailsSceneConfigurator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailsSceneConfigurator.swift; sourceTree = ""; }; 97 | 36E4C10827C9884C002641FA /* DetailsSceneViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailsSceneViewController.swift; sourceTree = ""; }; 98 | 36E4C10927C9884C002641FA /* DetailsSceneRouter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailsSceneRouter.swift; sourceTree = ""; }; 99 | 36E4C10A27C9884C002641FA /* DetailsSceneInteractor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetailsSceneInteractor.swift; sourceTree = ""; }; 100 | /* End PBXFileReference section */ 101 | 102 | /* Begin PBXFrameworksBuildPhase section */ 103 | 3680186227D3E1B300F9F6F3 /* Frameworks */ = { 104 | isa = PBXFrameworksBuildPhase; 105 | buildActionMask = 2147483647; 106 | files = ( 107 | ); 108 | runOnlyForDeploymentPostprocessing = 0; 109 | }; 110 | 36E4C0D227C98271002641FA /* Frameworks */ = { 111 | isa = PBXFrameworksBuildPhase; 112 | buildActionMask = 2147483647; 113 | files = ( 114 | ); 115 | runOnlyForDeploymentPostprocessing = 0; 116 | }; 117 | /* End PBXFrameworksBuildPhase section */ 118 | 119 | /* Begin PBXGroup section */ 120 | 3680182227D3DFB800F9F6F3 /* Other */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 3680183B27D3E06D00F9F6F3 /* OtherScenePresenter.swift */, 124 | 3680183C27D3E06D00F9F6F3 /* OtherSceneRouter.swift */, 125 | 3680183D27D3E06D00F9F6F3 /* OtherSceneViewController.swift */, 126 | 3680183E27D3E06D00F9F6F3 /* OtherSceneInteractor.swift */, 127 | 3680183F27D3E06D00F9F6F3 /* OtherSceneView.swift */, 128 | 3680184027D3E06D00F9F6F3 /* OtherSceneConfigurator.swift */, 129 | ); 130 | path = Other; 131 | sourceTree = ""; 132 | }; 133 | 3680186627D3E1B300F9F6F3 /* CleanSwiftWithSwiftUnitTests */ = { 134 | isa = PBXGroup; 135 | children = ( 136 | 3680186E27D3E1C200F9F6F3 /* Scenes */, 137 | ); 138 | path = CleanSwiftWithSwiftUnitTests; 139 | sourceTree = ""; 140 | }; 141 | 3680186E27D3E1C200F9F6F3 /* Scenes */ = { 142 | isa = PBXGroup; 143 | children = ( 144 | 3680188827D3E59500F9F6F3 /* Welcome */, 145 | 3680186F27D3E1C900F9F6F3 /* Other */, 146 | ); 147 | path = Scenes; 148 | sourceTree = ""; 149 | }; 150 | 3680186F27D3E1C900F9F6F3 /* Other */ = { 151 | isa = PBXGroup; 152 | children = ( 153 | 3680187C27D3E53B00F9F6F3 /* OtherSceneViewControllerTests.swift */, 154 | 3680187D27D3E53B00F9F6F3 /* OtherSceneInteractorTests.swift */, 155 | 3680187E27D3E53B00F9F6F3 /* OtherSceneViewModelTests.swift */, 156 | 3680187F27D3E53B00F9F6F3 /* OtherSceneRouterTests.swift */, 157 | 3680188027D3E53B00F9F6F3 /* OtherScenePresenterTests.swift */, 158 | 3680188127D3E53B00F9F6F3 /* OtherSceneConfiguratorTests.swift */, 159 | ); 160 | path = Other; 161 | sourceTree = ""; 162 | }; 163 | 3680188827D3E59500F9F6F3 /* Welcome */ = { 164 | isa = PBXGroup; 165 | children = ( 166 | 3680188927D3E5A600F9F6F3 /* WelcomeSceneViewControllerTests.swift */, 167 | 3680188A27D3E5A600F9F6F3 /* WelcomeSceneInteractorTests.swift */, 168 | 3680188B27D3E5A600F9F6F3 /* WelcomeSceneViewModelTests.swift */, 169 | 3680188C27D3E5A600F9F6F3 /* WelcomeSceneRouterTests.swift */, 170 | 3680188D27D3E5A600F9F6F3 /* WelcomeScenePresenterTests.swift */, 171 | 3680188E27D3E5A600F9F6F3 /* WelcomeSceneConfiguratorTests.swift */, 172 | ); 173 | path = Welcome; 174 | sourceTree = ""; 175 | }; 176 | 36E4C0CC27C98271002641FA = { 177 | isa = PBXGroup; 178 | children = ( 179 | 36E4C0D727C98271002641FA /* CleanSwiftWithSwiftUI */, 180 | 3680186627D3E1B300F9F6F3 /* CleanSwiftWithSwiftUnitTests */, 181 | 36E4C0D627C98271002641FA /* Products */, 182 | ); 183 | sourceTree = ""; 184 | }; 185 | 36E4C0D627C98271002641FA /* Products */ = { 186 | isa = PBXGroup; 187 | children = ( 188 | 36E4C0D527C98271002641FA /* CleanSwiftWithSwiftUI.app */, 189 | 3680186527D3E1B300F9F6F3 /* CleanSwiftWithSwiftUnitTests.xctest */, 190 | ); 191 | name = Products; 192 | sourceTree = ""; 193 | }; 194 | 36E4C0D727C98271002641FA /* CleanSwiftWithSwiftUI */ = { 195 | isa = PBXGroup; 196 | children = ( 197 | 36E4C0FE27C98618002641FA /* Factories */, 198 | 36E4C0EE27C9831C002641FA /* Scenes */, 199 | 36E4C0DA27C98271002641FA /* SceneDelegate.swift */, 200 | 36E4C0EC27C982E6002641FA /* AppDelegate.swift */, 201 | 36E4C0E127C98272002641FA /* Assets.xcassets */, 202 | 36E4C0E327C98272002641FA /* LaunchScreen.storyboard */, 203 | 36E4C0E627C98272002641FA /* Info.plist */, 204 | ); 205 | path = CleanSwiftWithSwiftUI; 206 | sourceTree = ""; 207 | }; 208 | 36E4C0EE27C9831C002641FA /* Scenes */ = { 209 | isa = PBXGroup; 210 | children = ( 211 | 3680182227D3DFB800F9F6F3 /* Other */, 212 | 36E4C0EF27C98322002641FA /* Welcome */, 213 | 36E4C10227C9884C002641FA /* Details */, 214 | ); 215 | path = Scenes; 216 | sourceTree = ""; 217 | }; 218 | 36E4C0EF27C98322002641FA /* Welcome */ = { 219 | isa = PBXGroup; 220 | children = ( 221 | 36E4C10127C986C0002641FA /* Models */, 222 | 36E4C0F027C98331002641FA /* WelcomeScenePresenter.swift */, 223 | 36E4C0F527C98331002641FA /* WelcomeSceneConfigurator.swift */, 224 | 36E4C0F227C98331002641FA /* WelcomeSceneViewController.swift */, 225 | 36E4C0F127C98331002641FA /* WelcomeSceneRouter.swift */, 226 | 36E4C0F427C98331002641FA /* WelcomeSceneView.swift */, 227 | 36E4C0F327C98331002641FA /* WelcomeSceneInteractor.swift */, 228 | ); 229 | path = Welcome; 230 | sourceTree = ""; 231 | }; 232 | 36E4C0FE27C98618002641FA /* Factories */ = { 233 | isa = PBXGroup; 234 | children = ( 235 | 36E4C0FF27C98622002641FA /* ScenesFactory.swift */, 236 | ); 237 | path = Factories; 238 | sourceTree = ""; 239 | }; 240 | 36E4C10127C986C0002641FA /* Models */ = { 241 | isa = PBXGroup; 242 | children = ( 243 | 36E4C0FC27C984B7002641FA /* WelcomeSceneViewModel.swift */, 244 | ); 245 | path = Models; 246 | sourceTree = ""; 247 | }; 248 | 36E4C10227C9884C002641FA /* Details */ = { 249 | isa = PBXGroup; 250 | children = ( 251 | 36E4C10427C9884C002641FA /* Models */, 252 | 36E4C10627C9884C002641FA /* DetailsScenePresenter.swift */, 253 | 36E4C10727C9884C002641FA /* DetailsSceneConfigurator.swift */, 254 | 36E4C10827C9884C002641FA /* DetailsSceneViewController.swift */, 255 | 36E4C10927C9884C002641FA /* DetailsSceneRouter.swift */, 256 | 36E4C10327C9884C002641FA /* DetailsSceneView.swift */, 257 | 36E4C10A27C9884C002641FA /* DetailsSceneInteractor.swift */, 258 | ); 259 | path = Details; 260 | sourceTree = ""; 261 | }; 262 | 36E4C10427C9884C002641FA /* Models */ = { 263 | isa = PBXGroup; 264 | children = ( 265 | 36E4C10527C9884C002641FA /* DetailsSceneViewModel.swift */, 266 | ); 267 | path = Models; 268 | sourceTree = ""; 269 | }; 270 | /* End PBXGroup section */ 271 | 272 | /* Begin PBXNativeTarget section */ 273 | 3680186427D3E1B300F9F6F3 /* CleanSwiftWithSwiftUnitTests */ = { 274 | isa = PBXNativeTarget; 275 | buildConfigurationList = 3680186B27D3E1B300F9F6F3 /* Build configuration list for PBXNativeTarget "CleanSwiftWithSwiftUnitTests" */; 276 | buildPhases = ( 277 | 3680186127D3E1B300F9F6F3 /* Sources */, 278 | 3680186227D3E1B300F9F6F3 /* Frameworks */, 279 | 3680186327D3E1B300F9F6F3 /* Resources */, 280 | ); 281 | buildRules = ( 282 | ); 283 | dependencies = ( 284 | 3680186A27D3E1B300F9F6F3 /* PBXTargetDependency */, 285 | ); 286 | name = CleanSwiftWithSwiftUnitTests; 287 | productName = CleanSwiftWithSwiftUnitTests; 288 | productReference = 3680186527D3E1B300F9F6F3 /* CleanSwiftWithSwiftUnitTests.xctest */; 289 | productType = "com.apple.product-type.bundle.unit-test"; 290 | }; 291 | 36E4C0D427C98271002641FA /* CleanSwiftWithSwiftUI */ = { 292 | isa = PBXNativeTarget; 293 | buildConfigurationList = 36E4C0E927C98272002641FA /* Build configuration list for PBXNativeTarget "CleanSwiftWithSwiftUI" */; 294 | buildPhases = ( 295 | 36E4C0D127C98271002641FA /* Sources */, 296 | 36E4C0D227C98271002641FA /* Frameworks */, 297 | 36E4C0D327C98271002641FA /* Resources */, 298 | ); 299 | buildRules = ( 300 | ); 301 | dependencies = ( 302 | ); 303 | name = CleanSwiftWithSwiftUI; 304 | productName = CleanSwiftWithSwiftUI; 305 | productReference = 36E4C0D527C98271002641FA /* CleanSwiftWithSwiftUI.app */; 306 | productType = "com.apple.product-type.application"; 307 | }; 308 | /* End PBXNativeTarget section */ 309 | 310 | /* Begin PBXProject section */ 311 | 36E4C0CD27C98271002641FA /* Project object */ = { 312 | isa = PBXProject; 313 | attributes = { 314 | BuildIndependentTargetsInParallel = 1; 315 | LastSwiftUpdateCheck = 1310; 316 | LastUpgradeCheck = 1310; 317 | TargetAttributes = { 318 | 3680186427D3E1B300F9F6F3 = { 319 | CreatedOnToolsVersion = 13.1; 320 | TestTargetID = 36E4C0D427C98271002641FA; 321 | }; 322 | 36E4C0D427C98271002641FA = { 323 | CreatedOnToolsVersion = 13.1; 324 | }; 325 | }; 326 | }; 327 | buildConfigurationList = 36E4C0D027C98271002641FA /* Build configuration list for PBXProject "CleanSwiftWithSwiftUI" */; 328 | compatibilityVersion = "Xcode 13.0"; 329 | developmentRegion = en; 330 | hasScannedForEncodings = 0; 331 | knownRegions = ( 332 | en, 333 | Base, 334 | ); 335 | mainGroup = 36E4C0CC27C98271002641FA; 336 | productRefGroup = 36E4C0D627C98271002641FA /* Products */; 337 | projectDirPath = ""; 338 | projectRoot = ""; 339 | targets = ( 340 | 36E4C0D427C98271002641FA /* CleanSwiftWithSwiftUI */, 341 | 3680186427D3E1B300F9F6F3 /* CleanSwiftWithSwiftUnitTests */, 342 | ); 343 | }; 344 | /* End PBXProject section */ 345 | 346 | /* Begin PBXResourcesBuildPhase section */ 347 | 3680186327D3E1B300F9F6F3 /* Resources */ = { 348 | isa = PBXResourcesBuildPhase; 349 | buildActionMask = 2147483647; 350 | files = ( 351 | ); 352 | runOnlyForDeploymentPostprocessing = 0; 353 | }; 354 | 36E4C0D327C98271002641FA /* Resources */ = { 355 | isa = PBXResourcesBuildPhase; 356 | buildActionMask = 2147483647; 357 | files = ( 358 | 36E4C0E527C98272002641FA /* LaunchScreen.storyboard in Resources */, 359 | 36E4C0E227C98272002641FA /* Assets.xcassets in Resources */, 360 | ); 361 | runOnlyForDeploymentPostprocessing = 0; 362 | }; 363 | /* End PBXResourcesBuildPhase section */ 364 | 365 | /* Begin PBXSourcesBuildPhase section */ 366 | 3680186127D3E1B300F9F6F3 /* Sources */ = { 367 | isa = PBXSourcesBuildPhase; 368 | buildActionMask = 2147483647; 369 | files = ( 370 | 3680188F27D3E5A600F9F6F3 /* WelcomeSceneViewControllerTests.swift in Sources */, 371 | 3680188627D3E53B00F9F6F3 /* OtherScenePresenterTests.swift in Sources */, 372 | 3680188327D3E53B00F9F6F3 /* OtherSceneInteractorTests.swift in Sources */, 373 | 3680188427D3E53B00F9F6F3 /* OtherSceneViewModelTests.swift in Sources */, 374 | 3680189327D3E5A600F9F6F3 /* WelcomeScenePresenterTests.swift in Sources */, 375 | 3680189027D3E5A600F9F6F3 /* WelcomeSceneInteractorTests.swift in Sources */, 376 | 3680189427D3E5A600F9F6F3 /* WelcomeSceneConfiguratorTests.swift in Sources */, 377 | 3680188527D3E53B00F9F6F3 /* OtherSceneRouterTests.swift in Sources */, 378 | 3680189227D3E5A600F9F6F3 /* WelcomeSceneRouterTests.swift in Sources */, 379 | 3680189127D3E5A600F9F6F3 /* WelcomeSceneViewModelTests.swift in Sources */, 380 | 3680188727D3E53B00F9F6F3 /* OtherSceneConfiguratorTests.swift in Sources */, 381 | 3680188227D3E53B00F9F6F3 /* OtherSceneViewControllerTests.swift in Sources */, 382 | ); 383 | runOnlyForDeploymentPostprocessing = 0; 384 | }; 385 | 36E4C0D127C98271002641FA /* Sources */ = { 386 | isa = PBXSourcesBuildPhase; 387 | buildActionMask = 2147483647; 388 | files = ( 389 | 3680184127D3E06D00F9F6F3 /* OtherScenePresenter.swift in Sources */, 390 | 36E4C10F27C9884C002641FA /* DetailsSceneViewController.swift in Sources */, 391 | 36E4C0FB27C98331002641FA /* WelcomeSceneConfigurator.swift in Sources */, 392 | 36E4C0F627C98331002641FA /* WelcomeScenePresenter.swift in Sources */, 393 | 3680184627D3E06D00F9F6F3 /* OtherSceneConfigurator.swift in Sources */, 394 | 3680184327D3E06D00F9F6F3 /* OtherSceneViewController.swift in Sources */, 395 | 36E4C10D27C9884C002641FA /* DetailsScenePresenter.swift in Sources */, 396 | 36E4C11127C9884C002641FA /* DetailsSceneInteractor.swift in Sources */, 397 | 36E4C10C27C9884C002641FA /* DetailsSceneViewModel.swift in Sources */, 398 | 36E4C0ED27C982E6002641FA /* AppDelegate.swift in Sources */, 399 | 36E4C0DB27C98271002641FA /* SceneDelegate.swift in Sources */, 400 | 36E4C10E27C9884C002641FA /* DetailsSceneConfigurator.swift in Sources */, 401 | 36E4C11027C9884C002641FA /* DetailsSceneRouter.swift in Sources */, 402 | 36E4C0F927C98331002641FA /* WelcomeSceneInteractor.swift in Sources */, 403 | 36E4C0F827C98331002641FA /* WelcomeSceneViewController.swift in Sources */, 404 | 36E4C0FD27C984B7002641FA /* WelcomeSceneViewModel.swift in Sources */, 405 | 36E4C10027C98622002641FA /* ScenesFactory.swift in Sources */, 406 | 3680184427D3E06D00F9F6F3 /* OtherSceneInteractor.swift in Sources */, 407 | 3680184227D3E06D00F9F6F3 /* OtherSceneRouter.swift in Sources */, 408 | 3680184527D3E06D00F9F6F3 /* OtherSceneView.swift in Sources */, 409 | 36E4C0FA27C98331002641FA /* WelcomeSceneView.swift in Sources */, 410 | 36E4C0F727C98331002641FA /* WelcomeSceneRouter.swift in Sources */, 411 | 36E4C10B27C9884C002641FA /* DetailsSceneView.swift in Sources */, 412 | ); 413 | runOnlyForDeploymentPostprocessing = 0; 414 | }; 415 | /* End PBXSourcesBuildPhase section */ 416 | 417 | /* Begin PBXTargetDependency section */ 418 | 3680186A27D3E1B300F9F6F3 /* PBXTargetDependency */ = { 419 | isa = PBXTargetDependency; 420 | target = 36E4C0D427C98271002641FA /* CleanSwiftWithSwiftUI */; 421 | targetProxy = 3680186927D3E1B300F9F6F3 /* PBXContainerItemProxy */; 422 | }; 423 | /* End PBXTargetDependency section */ 424 | 425 | /* Begin PBXVariantGroup section */ 426 | 36E4C0E327C98272002641FA /* LaunchScreen.storyboard */ = { 427 | isa = PBXVariantGroup; 428 | children = ( 429 | 36E4C0E427C98272002641FA /* Base */, 430 | ); 431 | name = LaunchScreen.storyboard; 432 | sourceTree = ""; 433 | }; 434 | /* End PBXVariantGroup section */ 435 | 436 | /* Begin XCBuildConfiguration section */ 437 | 3680186C27D3E1B300F9F6F3 /* Debug */ = { 438 | isa = XCBuildConfiguration; 439 | buildSettings = { 440 | BUNDLE_LOADER = "$(TEST_HOST)"; 441 | CODE_SIGN_STYLE = Automatic; 442 | CURRENT_PROJECT_VERSION = 1; 443 | GENERATE_INFOPLIST_FILE = YES; 444 | LD_RUNPATH_SEARCH_PATHS = ( 445 | "$(inherited)", 446 | "@executable_path/Frameworks", 447 | "@loader_path/Frameworks", 448 | ); 449 | MARKETING_VERSION = 1.0; 450 | PRODUCT_BUNDLE_IDENTIFIER = com.CleanSwiftWithSwiftUnitTests; 451 | PRODUCT_NAME = "$(TARGET_NAME)"; 452 | SWIFT_EMIT_LOC_STRINGS = NO; 453 | SWIFT_VERSION = 5.0; 454 | TARGETED_DEVICE_FAMILY = "1,2"; 455 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CleanSwiftWithSwiftUI.app/CleanSwiftWithSwiftUI"; 456 | }; 457 | name = Debug; 458 | }; 459 | 3680186D27D3E1B300F9F6F3 /* Release */ = { 460 | isa = XCBuildConfiguration; 461 | buildSettings = { 462 | BUNDLE_LOADER = "$(TEST_HOST)"; 463 | CODE_SIGN_STYLE = Automatic; 464 | CURRENT_PROJECT_VERSION = 1; 465 | GENERATE_INFOPLIST_FILE = YES; 466 | LD_RUNPATH_SEARCH_PATHS = ( 467 | "$(inherited)", 468 | "@executable_path/Frameworks", 469 | "@loader_path/Frameworks", 470 | ); 471 | MARKETING_VERSION = 1.0; 472 | PRODUCT_BUNDLE_IDENTIFIER = com.CleanSwiftWithSwiftUnitTests; 473 | PRODUCT_NAME = "$(TARGET_NAME)"; 474 | SWIFT_EMIT_LOC_STRINGS = NO; 475 | SWIFT_VERSION = 5.0; 476 | TARGETED_DEVICE_FAMILY = "1,2"; 477 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CleanSwiftWithSwiftUI.app/CleanSwiftWithSwiftUI"; 478 | }; 479 | name = Release; 480 | }; 481 | 36E4C0E727C98272002641FA /* Debug */ = { 482 | isa = XCBuildConfiguration; 483 | buildSettings = { 484 | ALWAYS_SEARCH_USER_PATHS = NO; 485 | CLANG_ANALYZER_NONNULL = YES; 486 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 487 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 488 | CLANG_CXX_LIBRARY = "libc++"; 489 | CLANG_ENABLE_MODULES = YES; 490 | CLANG_ENABLE_OBJC_ARC = YES; 491 | CLANG_ENABLE_OBJC_WEAK = YES; 492 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 493 | CLANG_WARN_BOOL_CONVERSION = YES; 494 | CLANG_WARN_COMMA = YES; 495 | CLANG_WARN_CONSTANT_CONVERSION = YES; 496 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 497 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 498 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 499 | CLANG_WARN_EMPTY_BODY = YES; 500 | CLANG_WARN_ENUM_CONVERSION = YES; 501 | CLANG_WARN_INFINITE_RECURSION = YES; 502 | CLANG_WARN_INT_CONVERSION = YES; 503 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 504 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 505 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 506 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 507 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 508 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 509 | CLANG_WARN_STRICT_PROTOTYPES = YES; 510 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 511 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 512 | CLANG_WARN_UNREACHABLE_CODE = YES; 513 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 514 | COPY_PHASE_STRIP = NO; 515 | DEBUG_INFORMATION_FORMAT = dwarf; 516 | ENABLE_STRICT_OBJC_MSGSEND = YES; 517 | ENABLE_TESTABILITY = YES; 518 | GCC_C_LANGUAGE_STANDARD = gnu11; 519 | GCC_DYNAMIC_NO_PIC = NO; 520 | GCC_NO_COMMON_BLOCKS = YES; 521 | GCC_OPTIMIZATION_LEVEL = 0; 522 | GCC_PREPROCESSOR_DEFINITIONS = ( 523 | "DEBUG=1", 524 | "$(inherited)", 525 | ); 526 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 527 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 528 | GCC_WARN_UNDECLARED_SELECTOR = YES; 529 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 530 | GCC_WARN_UNUSED_FUNCTION = YES; 531 | GCC_WARN_UNUSED_VARIABLE = YES; 532 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 533 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 534 | MTL_FAST_MATH = YES; 535 | ONLY_ACTIVE_ARCH = YES; 536 | SDKROOT = iphoneos; 537 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 538 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 539 | }; 540 | name = Debug; 541 | }; 542 | 36E4C0E827C98272002641FA /* Release */ = { 543 | isa = XCBuildConfiguration; 544 | buildSettings = { 545 | ALWAYS_SEARCH_USER_PATHS = NO; 546 | CLANG_ANALYZER_NONNULL = YES; 547 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 548 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; 549 | CLANG_CXX_LIBRARY = "libc++"; 550 | CLANG_ENABLE_MODULES = YES; 551 | CLANG_ENABLE_OBJC_ARC = YES; 552 | CLANG_ENABLE_OBJC_WEAK = YES; 553 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 554 | CLANG_WARN_BOOL_CONVERSION = YES; 555 | CLANG_WARN_COMMA = YES; 556 | CLANG_WARN_CONSTANT_CONVERSION = YES; 557 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 558 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 559 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 560 | CLANG_WARN_EMPTY_BODY = YES; 561 | CLANG_WARN_ENUM_CONVERSION = YES; 562 | CLANG_WARN_INFINITE_RECURSION = YES; 563 | CLANG_WARN_INT_CONVERSION = YES; 564 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 565 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 566 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 567 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 568 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 569 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 570 | CLANG_WARN_STRICT_PROTOTYPES = YES; 571 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 572 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 573 | CLANG_WARN_UNREACHABLE_CODE = YES; 574 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 575 | COPY_PHASE_STRIP = NO; 576 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 577 | ENABLE_NS_ASSERTIONS = NO; 578 | ENABLE_STRICT_OBJC_MSGSEND = YES; 579 | GCC_C_LANGUAGE_STANDARD = gnu11; 580 | GCC_NO_COMMON_BLOCKS = YES; 581 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 582 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 583 | GCC_WARN_UNDECLARED_SELECTOR = YES; 584 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 585 | GCC_WARN_UNUSED_FUNCTION = YES; 586 | GCC_WARN_UNUSED_VARIABLE = YES; 587 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 588 | MTL_ENABLE_DEBUG_INFO = NO; 589 | MTL_FAST_MATH = YES; 590 | SDKROOT = iphoneos; 591 | SWIFT_COMPILATION_MODE = wholemodule; 592 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 593 | VALIDATE_PRODUCT = YES; 594 | }; 595 | name = Release; 596 | }; 597 | 36E4C0EA27C98272002641FA /* Debug */ = { 598 | isa = XCBuildConfiguration; 599 | buildSettings = { 600 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 601 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 602 | CODE_SIGN_STYLE = Automatic; 603 | CURRENT_PROJECT_VERSION = 1; 604 | GENERATE_INFOPLIST_FILE = YES; 605 | INFOPLIST_FILE = CleanSwiftWithSwiftUI/Info.plist; 606 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 607 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 608 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 609 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 610 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 611 | LD_RUNPATH_SEARCH_PATHS = ( 612 | "$(inherited)", 613 | "@executable_path/Frameworks", 614 | ); 615 | MARKETING_VERSION = 1.0; 616 | PRODUCT_BUNDLE_IDENTIFIER = com.CleanSwiftWithSwiftUI; 617 | PRODUCT_NAME = "$(TARGET_NAME)"; 618 | SWIFT_EMIT_LOC_STRINGS = YES; 619 | SWIFT_VERSION = 5.0; 620 | TARGETED_DEVICE_FAMILY = 1; 621 | }; 622 | name = Debug; 623 | }; 624 | 36E4C0EB27C98272002641FA /* Release */ = { 625 | isa = XCBuildConfiguration; 626 | buildSettings = { 627 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 628 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 629 | CODE_SIGN_STYLE = Automatic; 630 | CURRENT_PROJECT_VERSION = 1; 631 | GENERATE_INFOPLIST_FILE = YES; 632 | INFOPLIST_FILE = CleanSwiftWithSwiftUI/Info.plist; 633 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 634 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 635 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 636 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 637 | IPHONEOS_DEPLOYMENT_TARGET = 15.0; 638 | LD_RUNPATH_SEARCH_PATHS = ( 639 | "$(inherited)", 640 | "@executable_path/Frameworks", 641 | ); 642 | MARKETING_VERSION = 1.0; 643 | PRODUCT_BUNDLE_IDENTIFIER = com.CleanSwiftWithSwiftUI; 644 | PRODUCT_NAME = "$(TARGET_NAME)"; 645 | SWIFT_EMIT_LOC_STRINGS = YES; 646 | SWIFT_VERSION = 5.0; 647 | TARGETED_DEVICE_FAMILY = 1; 648 | }; 649 | name = Release; 650 | }; 651 | /* End XCBuildConfiguration section */ 652 | 653 | /* Begin XCConfigurationList section */ 654 | 3680186B27D3E1B300F9F6F3 /* Build configuration list for PBXNativeTarget "CleanSwiftWithSwiftUnitTests" */ = { 655 | isa = XCConfigurationList; 656 | buildConfigurations = ( 657 | 3680186C27D3E1B300F9F6F3 /* Debug */, 658 | 3680186D27D3E1B300F9F6F3 /* Release */, 659 | ); 660 | defaultConfigurationIsVisible = 0; 661 | defaultConfigurationName = Release; 662 | }; 663 | 36E4C0D027C98271002641FA /* Build configuration list for PBXProject "CleanSwiftWithSwiftUI" */ = { 664 | isa = XCConfigurationList; 665 | buildConfigurations = ( 666 | 36E4C0E727C98272002641FA /* Debug */, 667 | 36E4C0E827C98272002641FA /* Release */, 668 | ); 669 | defaultConfigurationIsVisible = 0; 670 | defaultConfigurationName = Release; 671 | }; 672 | 36E4C0E927C98272002641FA /* Build configuration list for PBXNativeTarget "CleanSwiftWithSwiftUI" */ = { 673 | isa = XCConfigurationList; 674 | buildConfigurations = ( 675 | 36E4C0EA27C98272002641FA /* Debug */, 676 | 36E4C0EB27C98272002641FA /* Release */, 677 | ); 678 | defaultConfigurationIsVisible = 0; 679 | defaultConfigurationName = Release; 680 | }; 681 | /* End XCConfigurationList section */ 682 | }; 683 | rootObject = 36E4C0CD27C98271002641FA /* Project object */; 684 | } 685 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI.xcodeproj/xcshareddata/xcschemes/CleanSwiftWithSwiftUI.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 34 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI.xcodeproj/xcuserdata/patrykstrzemiecki.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI.xcodeproj/xcuserdata/patrykstrzemiecki.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CleanSwiftWithSwiftUI.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 3680186427D3E1B300F9F6F3 16 | 17 | primary 18 | 19 | 20 | 36E4C0D427C98271002641FA 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | 8 | @main 9 | class AppDelegate: UIResponder, UIApplicationDelegate {} 10 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/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 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/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 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Factories/ScenesFactory.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ScenesFactory.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | 8 | protocol ScenesFactory { 9 | func makeWelcomeScene() -> UIViewController 10 | func makeDetailsScene( 11 | viewModel: DetailsSceneViewModel 12 | ) -> UIViewController 13 | func makeOtherScene() -> UIViewController 14 | } 15 | 16 | final class DefaultScenesFactory: ScenesFactory { 17 | func makeWelcomeScene() -> UIViewController { 18 | let viewModel = DefaultWelcomeSceneViewModel( 19 | text: "Hello World", 20 | buttonText: "Tap me" 21 | ) 22 | return DefaultWelcomeSceneConfigurator() 23 | .configured(with: viewModel) 24 | } 25 | 26 | func makeDetailsScene( 27 | viewModel: DetailsSceneViewModel 28 | ) -> UIViewController { 29 | DefaultDetailsSceneConfigurator().configured(with: viewModel) 30 | } 31 | 32 | func makeOtherScene() -> UIViewController { 33 | DefaultOtherSceneConfigurator() 34 | .configured(with: DefaultOtherSceneViewModel()) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | 8 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 9 | var window: UIWindow? 10 | 11 | private lazy var scenesFactory: ScenesFactory = DefaultScenesFactory() 12 | 13 | func scene( 14 | _ scene: UIScene, 15 | willConnectTo session: UISceneSession, 16 | options connectionOptions: UIScene.ConnectionOptions 17 | ) { 18 | guard let windowScene = (scene as? UIWindowScene) else { 19 | assertionFailure() 20 | return 21 | } 22 | window = UIWindow(frame: windowScene.coordinateSpace.bounds) 23 | window?.windowScene = windowScene 24 | window?.rootViewController = UINavigationController( 25 | rootViewController: scenesFactory.makeWelcomeScene() 26 | ) 27 | window?.makeKeyAndVisible() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Details/DetailsSceneConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsSceneConfigurator.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import Foundation 7 | 8 | protocol DetailsSceneConfigurator { 9 | func configured( 10 | with viewModel: DetailsSceneViewModel 11 | ) -> DetailsSceneViewController 12 | } 13 | 14 | final class DefaultDetailsSceneConfigurator: DetailsSceneConfigurator { 15 | func configured( 16 | with viewModel: DetailsSceneViewModel 17 | ) -> DetailsSceneViewController { 18 | var viewModel = viewModel 19 | let viewController = DetailsSceneViewController( 20 | rootView: DetailsSceneView(viewModel: viewModel) 21 | ) 22 | let interactor = DetailsSceneInteractor() 23 | let presenter = DetailsScenePresenter() 24 | let router = DetailsSceneRouter() 25 | router.source = viewController 26 | presenter.viewController = viewController 27 | interactor.presenter = presenter 28 | viewController.interactor = interactor 29 | viewController.router = router 30 | viewController.viewModel = viewModel 31 | viewModel.delegate = viewController 32 | return viewController 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Details/DetailsSceneInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsSceneInteractor.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import Foundation 7 | 8 | protocol DetailsSceneInteractorInput {} 9 | 10 | typealias DetailsSceneInteractorOutput = DetailsSceneInteractorInput 11 | 12 | final class DetailsSceneInteractor { 13 | var presenter: DetailsScenePresenterInput? 14 | } 15 | 16 | extension DetailsSceneInteractor: DetailsSceneInteractorInput {} 17 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Details/DetailsScenePresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsScenePresenter.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | 8 | protocol DetailsScenePresenterInput {} 9 | 10 | typealias DetailsScenePresenterOutput = DetailsSceneViewControllerInput 11 | 12 | final class DetailsScenePresenter { 13 | weak var viewController: DetailsScenePresenterOutput? 14 | } 15 | 16 | extension DetailsScenePresenter: DetailsScenePresenterInput {} 17 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Details/DetailsSceneRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsSceneRouter.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | 8 | protocol DetailsSceneRoutingLogic {} 9 | 10 | final class DetailsSceneRouter: NSObject { 11 | weak var source: UIViewController? 12 | } 13 | 14 | extension DetailsSceneRouter: DetailsSceneRoutingLogic {} 15 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Details/DetailsSceneView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsSceneView.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct DetailsSceneView: View { 9 | let viewModel: DetailsSceneViewModel 10 | 11 | var body: some View { 12 | VStack { 13 | Text(viewModel.text) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Details/DetailsSceneViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsSceneViewController.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | import SwiftUI 8 | 9 | protocol DetailsSceneViewControllerInput: AnyObject { 10 | var viewModel: DetailsSceneViewModel? { get set } 11 | } 12 | 13 | typealias DetailsSceneViewControllerOutput = DetailsSceneInteractorInput 14 | 15 | final class DetailsSceneViewController: UIHostingController { 16 | var interactor: DetailsSceneViewControllerOutput? 17 | var router: DetailsSceneRoutingLogic? 18 | var viewModel: DetailsSceneViewModel? 19 | } 20 | 21 | extension DetailsSceneViewController: DetailsSceneViewControllerInput {} 22 | 23 | extension DetailsSceneViewController: DetailsSceneViewDelegate {} 24 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Details/Models/DetailsSceneViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DetailsSceneViewModel.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import SwiftUI 7 | 8 | protocol DetailsSceneViewDelegate: AnyObject {} 9 | 10 | protocol DetailsSceneViewModel { 11 | var delegate: DetailsSceneViewDelegate? { get set } 12 | var text: String { get } 13 | } 14 | 15 | final class DefaultDetailsSceneViewModel: DetailsSceneViewModel { 16 | var delegate: DetailsSceneViewDelegate? 17 | @Published private(set) var text: String 18 | 19 | init( 20 | text: String 21 | ) { 22 | self.text = text 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Other/OtherSceneConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherSceneConfigurator.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import Foundation 7 | 8 | protocol OtherSceneConfigurator { 9 | func configured( 10 | with viewModel: OtherSceneViewModel 11 | ) -> OtherSceneViewController 12 | } 13 | 14 | // swiftlint:disable colon 15 | final class DefaultOtherSceneConfigurator: 16 | OtherSceneConfigurator { 17 | func configured( 18 | with viewModel: OtherSceneViewModel 19 | ) -> OtherSceneViewController { 20 | var viewModel = viewModel 21 | let viewController = OtherSceneViewController( 22 | rootView: OtherSceneView(viewModel: viewModel) 23 | ) 24 | let interactor = OtherSceneInteractor() 25 | let presenter = OtherScenePresenter() 26 | let router = OtherSceneRouter() 27 | router.source = viewController 28 | presenter.viewController = viewController 29 | interactor.presenter = presenter 30 | viewController.interactor = interactor 31 | viewController.router = router 32 | viewController.viewModel = viewModel 33 | viewModel.delegate = viewController 34 | return viewController 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Other/OtherSceneInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherSceneInteractor.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import Foundation 7 | 8 | protocol OtherSceneInteractorInput {} 9 | 10 | typealias OtherSceneInteractorOutput 11 | = OtherSceneInteractorInput 12 | 13 | final class OtherSceneInteractor { 14 | var presenter: OtherScenePresenterInput? 15 | } 16 | 17 | // swiftlint:disable colon 18 | extension OtherSceneInteractor: 19 | OtherSceneInteractorInput {} 20 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Other/OtherScenePresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherScenePresenter.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | 8 | protocol OtherScenePresenterInput {} 9 | 10 | typealias OtherScenePresenterOutput 11 | = OtherSceneViewControllerInput 12 | 13 | final class OtherScenePresenter { 14 | weak var viewController: OtherScenePresenterOutput? 15 | } 16 | 17 | // swiftlint:disable colon 18 | extension OtherScenePresenter: 19 | OtherScenePresenterInput {} 20 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Other/OtherSceneRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherSceneRouter.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | 8 | protocol OtherSceneRoutingLogic {} 9 | 10 | final class OtherSceneRouter: NSObject { 11 | weak var source: UIViewController? 12 | } 13 | 14 | // swiftlint:disable colon 15 | extension OtherSceneRouter: 16 | OtherSceneRoutingLogic {} 17 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Other/OtherSceneView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherSceneView.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import SwiftUI 7 | 8 | protocol OtherSceneViewDelegate: AnyObject {} 9 | 10 | protocol OtherSceneViewModel { 11 | var delegate: OtherSceneViewDelegate? { get set } 12 | } 13 | 14 | // swiftlint:disable colon 15 | final class DefaultOtherSceneViewModel: 16 | OtherSceneViewModel { 17 | var delegate: OtherSceneViewDelegate? 18 | } 19 | 20 | struct OtherSceneView: View { 21 | let viewModel: OtherSceneViewModel 22 | 23 | var body: some View { 24 | VStack { 25 | Text("TODO") 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Other/OtherSceneViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherSceneViewController.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | import SwiftUI 8 | 9 | protocol OtherSceneViewControllerInput: AnyObject { 10 | var router: OtherSceneRoutingLogic? { get set } 11 | var viewModel: OtherSceneViewModel? { get set } 12 | } 13 | 14 | typealias OtherSceneViewControllerOutput 15 | = OtherSceneInteractorInput 16 | 17 | // swiftlint:disable colon 18 | final class OtherSceneViewController: 19 | UIHostingController { 20 | var interactor: OtherSceneViewControllerOutput? 21 | var router: OtherSceneRoutingLogic? 22 | var viewModel: OtherSceneViewModel? 23 | } 24 | 25 | // swiftlint:disable colon 26 | extension OtherSceneViewController: 27 | OtherSceneViewControllerInput {} 28 | 29 | // swiftlint:disable colon 30 | extension OtherSceneViewController: 31 | OtherSceneViewDelegate {} 32 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Welcome/Models/WelcomeSceneViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeSceneViewModel.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import SwiftUI 7 | 8 | protocol WelcomeSceneViewDelegate: AnyObject { 9 | func didSelectButton(_ sender: WelcomeSceneViewModel?) 10 | } 11 | 12 | protocol WelcomeSceneViewModel { 13 | var delegate: WelcomeSceneViewDelegate? { get set } 14 | var text: String { get } 15 | var buttonText: String { get } 16 | } 17 | 18 | final class DefaultWelcomeSceneViewModel: WelcomeSceneViewModel { 19 | var delegate: WelcomeSceneViewDelegate? 20 | @Published private(set) var text: String 21 | @Published private(set) var buttonText: String 22 | 23 | init( 24 | text: String, 25 | buttonText: String 26 | ) { 27 | self.text = text 28 | self.buttonText = buttonText 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Welcome/WelcomeSceneConfigurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeSceneConfigurator.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import Foundation 7 | 8 | protocol WelcomeSceneConfigurator { 9 | func configured( 10 | with viewModel: WelcomeSceneViewModel 11 | ) -> WelcomeSceneViewController 12 | } 13 | 14 | final class DefaultWelcomeSceneConfigurator: WelcomeSceneConfigurator { 15 | func configured( 16 | with viewModel: WelcomeSceneViewModel 17 | ) -> WelcomeSceneViewController { 18 | var viewModel = viewModel 19 | let viewController = WelcomeSceneViewController( 20 | rootView: WelcomeSceneView(viewModel: viewModel) 21 | ) 22 | let interactor = WelcomeSceneInteractor() 23 | let presenter = WelcomeScenePresenter() 24 | let router = WelcomeSceneRouter() 25 | router.source = viewController 26 | presenter.viewController = viewController 27 | interactor.presenter = presenter 28 | viewController.interactor = interactor 29 | viewController.router = router 30 | viewController.viewModel = viewModel 31 | viewModel.delegate = viewController 32 | return viewController 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Welcome/WelcomeSceneInteractor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeSceneInteractor.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import Foundation 7 | 8 | protocol WelcomeSceneInteractorInput { 9 | func details() 10 | } 11 | 12 | typealias WelcomeSceneInteractorOutput = WelcomeSceneInteractorInput 13 | 14 | final class WelcomeSceneInteractor { 15 | var presenter: WelcomeScenePresenterInput? 16 | } 17 | 18 | extension WelcomeSceneInteractor: WelcomeSceneInteractorInput { 19 | func details() { 20 | #warning("TODO business logic here") 21 | presenter?.showDetails() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Welcome/WelcomeScenePresenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeScenePresenter.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | 8 | protocol WelcomeScenePresenterInput { 9 | func showDetails() 10 | } 11 | 12 | typealias WelcomeScenePresenterOutput = WelcomeSceneViewControllerInput 13 | 14 | final class WelcomeScenePresenter { 15 | weak var viewController: WelcomeScenePresenterOutput? 16 | } 17 | 18 | extension WelcomeScenePresenter: WelcomeScenePresenterInput { 19 | func showDetails() { 20 | viewController?.router?.showDetails( 21 | viewModel: DefaultDetailsSceneViewModel(text: "Details") 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Welcome/WelcomeSceneRouter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeSceneRouter.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | 8 | protocol WelcomeSceneRoutingLogic { 9 | func showDetails(viewModel: DetailsSceneViewModel) 10 | } 11 | 12 | final class WelcomeSceneRouter: NSObject { 13 | weak var source: UIViewController? 14 | 15 | private let scenesFactory: ScenesFactory 16 | 17 | init(scenesFactory: ScenesFactory = DefaultScenesFactory()) { 18 | self.scenesFactory = scenesFactory 19 | } 20 | } 21 | 22 | extension WelcomeSceneRouter: WelcomeSceneRoutingLogic { 23 | func showDetails(viewModel: DetailsSceneViewModel) { 24 | source?.navigationController?.pushViewController( 25 | Bool.random() 26 | ? scenesFactory.makeDetailsScene(viewModel: viewModel) 27 | : scenesFactory.makeOtherScene(), 28 | animated: true 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Welcome/WelcomeSceneView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeSceneView.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import SwiftUI 7 | 8 | struct WelcomeSceneView: View { 9 | let viewModel: WelcomeSceneViewModel 10 | 11 | var body: some View { 12 | VStack { 13 | Text(viewModel.text) 14 | Divider() 15 | Button(viewModel.buttonText) { 16 | viewModel.delegate?.didSelectButton(viewModel) 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUI/Scenes/Welcome/WelcomeSceneViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeSceneViewController.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | import UIKit 7 | import SwiftUI 8 | 9 | protocol WelcomeSceneViewControllerInput: AnyObject { 10 | var router: WelcomeSceneRoutingLogic? { get set } 11 | var viewModel: WelcomeSceneViewModel? { get set } 12 | } 13 | 14 | typealias WelcomeSceneViewControllerOutput = WelcomeSceneInteractorInput 15 | 16 | final class WelcomeSceneViewController: UIHostingController { 17 | var interactor: WelcomeSceneViewControllerOutput? 18 | var router: WelcomeSceneRoutingLogic? 19 | var viewModel: WelcomeSceneViewModel? 20 | } 21 | 22 | extension WelcomeSceneViewController: WelcomeSceneViewControllerInput {} 23 | 24 | extension WelcomeSceneViewController: WelcomeSceneViewDelegate { 25 | func didSelectButton(_ sender: WelcomeSceneViewModel?) { 26 | interactor?.details() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUITests/CleanSwiftWithSwiftUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CleanSwiftWithSwiftUITests.swift 3 | // CleanSwiftWithSwiftUITests 4 | // 5 | // Created by Patryk Strzemiecki on 05/03/2022. 6 | // 7 | 8 | import XCTest 9 | 10 | class CleanSwiftWithSwiftUITests: 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 | 16 | override func tearDownWithError() throws { 17 | // Put teardown code here. This method is called after the invocation of each test method in the class. 18 | } 19 | 20 | func testExample() throws { 21 | // This is an example of a functional test case. 22 | // Use XCTAssert and related functions to verify your tests produce the correct results. 23 | } 24 | 25 | func testPerformanceExample() throws { 26 | // This is an example of a performance test case. 27 | measure { 28 | // Put the code you want to measure the time of here. 29 | } 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Other/OtherSceneConfiguratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherSceneConfiguratorTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | // swiftlint:disable force_cast 10 | final class OtherSceneConfiguratorTests: XCTestCase { 11 | 12 | private var sut: OtherSceneConfigurator! 13 | private var viewModel: OtherSceneViewModelMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewModel = OtherSceneViewModelMock() 21 | sut = DefaultOtherSceneConfigurator() 22 | } 23 | 24 | override func tearDown() { 25 | sut = nil 26 | viewModel = nil 27 | 28 | super.tearDown() 29 | } 30 | 31 | func test_configurator_configure_allDependenciesProperlyConfigured() { 32 | let configured = sut.configured(with: viewModel) 33 | 34 | XCTAssertTrue(configured.router is OtherSceneRouter) 35 | XCTAssertTrue( 36 | (configured.router as! OtherSceneRouter) 37 | .source is OtherSceneViewController 38 | ) 39 | XCTAssertTrue( 40 | configured.interactor is OtherSceneInteractor 41 | ) 42 | XCTAssertTrue( 43 | (configured.interactor as! OtherSceneInteractor) 44 | .presenter is OtherScenePresenter 45 | ) 46 | XCTAssertTrue( 47 | configured.interactor is OtherSceneInteractor 48 | ) 49 | XCTAssertNotNil(configured.viewModel) 50 | XCTAssertTrue( 51 | viewModel.delegate is OtherSceneViewController 52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Other/OtherSceneInteractorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherSceneInteractorTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | final class OtherSceneInteractorTests: XCTestCase { 10 | 11 | private var sut: OtherSceneInteractor! 12 | private var presenter: OtherScenePresenterMock! 13 | 14 | override func setUp() { 15 | super.setUp() 16 | 17 | UIView.setAnimationsEnabled(false) 18 | 19 | presenter = OtherScenePresenterMock() 20 | sut = OtherSceneInteractor() 21 | sut.presenter = presenter 22 | } 23 | 24 | override func tearDown() { 25 | sut = nil 26 | presenter = nil 27 | 28 | super.tearDown() 29 | } 30 | } 31 | 32 | // swiftlint:disable colon 33 | final class OtherScenePresenterMock: 34 | OtherScenePresenterInput {} 35 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Other/OtherScenePresenterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherScenePresenterTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | final class OtherScenePresenterTests: XCTestCase { 10 | 11 | private var sut: OtherScenePresenter! 12 | private var viewController: OtherSceneViewControllerMock! 13 | private var router: OtherSceneRouterMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewController = OtherSceneViewControllerMock() 21 | router = OtherSceneRouterMock() 22 | viewController.router = router 23 | sut = OtherScenePresenter() 24 | sut.viewController = viewController 25 | } 26 | 27 | override func tearDown() { 28 | sut = nil 29 | viewController = nil 30 | router = nil 31 | 32 | super.tearDown() 33 | } 34 | } 35 | 36 | // swiftlint:disable colon 37 | final class OtherSceneViewControllerMock: 38 | OtherSceneViewControllerInput { 39 | var viewModel: OtherSceneViewModel? 40 | var router: OtherSceneRoutingLogic? 41 | } 42 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Other/OtherSceneRouterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherSceneRouterTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | final class OtherSceneRouterTests: XCTestCase { 10 | 11 | private var sut: OtherSceneRouter! 12 | private var viewController: UIViewControllerMock! 13 | private var navigationController: UINavigationControllerMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewController = UIViewControllerMock() 21 | navigationController 22 | = UINavigationControllerMock(rootViewController: UIViewController()) 23 | navigationController.pushViewController(viewController, animated: false) 24 | sut = OtherSceneRouter() 25 | sut.source = viewController 26 | } 27 | 28 | override func tearDown() { 29 | sut = nil 30 | viewController = nil 31 | navigationController = nil 32 | 33 | super.tearDown() 34 | } 35 | } 36 | 37 | private final class UIViewControllerMock: UIViewController {} 38 | 39 | private final class UINavigationControllerMock: UINavigationController {} 40 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Other/OtherSceneViewControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherSceneViewControllerTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | final class OtherSceneViewControllerTests: XCTestCase { 10 | 11 | private var sut: OtherSceneViewController! 12 | private var interactor: OtherSceneInteractorMock! 13 | private var router: OtherSceneRouterMock! 14 | private var viewModel: OtherSceneViewModelMock! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | 19 | UIView.setAnimationsEnabled(false) 20 | 21 | interactor = OtherSceneInteractorMock() 22 | router = OtherSceneRouterMock() 23 | viewModel = OtherSceneViewModelMock() 24 | sut = OtherSceneViewController( 25 | rootView: OtherSceneView(viewModel: viewModel) 26 | ) 27 | sut.interactor = interactor 28 | sut.router = router 29 | } 30 | 31 | override func tearDown() { 32 | sut = nil 33 | interactor = nil 34 | router = nil 35 | viewModel = nil 36 | 37 | super.tearDown() 38 | } 39 | } 40 | 41 | // swiftlint:disable colon 42 | final class OtherSceneInteractorMock: 43 | OtherSceneInteractorInput {} 44 | 45 | // swiftlint:disable colon 46 | final class OtherSceneRouterMock: 47 | OtherSceneRoutingLogic {} 48 | 49 | final class OtherSceneViewModelMock: OtherSceneViewModel { 50 | var delegate: OtherSceneViewDelegate? 51 | } -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Other/OtherSceneViewModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OtherSceneViewModelTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | // swiftlint:disable force_cast 10 | final class OtherSceneViewModelTests: XCTestCase { 11 | 12 | private var sut: OtherSceneViewModel! 13 | private var viewController: UIViewControllerMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewController = UIViewControllerMock() 21 | sut = DefaultOtherSceneViewModel() 22 | sut.delegate = viewController 23 | } 24 | 25 | override func tearDown() { 26 | sut = nil 27 | viewController = nil 28 | 29 | super.tearDown() 30 | } 31 | } 32 | 33 | // swiftlint:disable colon 34 | private final class UIViewControllerMock: 35 | UIViewController, OtherSceneViewDelegate {} 36 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Welcome/WelcomeSceneConfiguratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeSceneConfiguratorTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | // swiftlint:disable force_cast 10 | final class WelcomeSceneConfiguratorTests: XCTestCase { 11 | 12 | private var sut: WelcomeSceneConfigurator! 13 | private var viewModel: WelcomeSceneViewModelMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewModel = WelcomeSceneViewModelMock() 21 | sut = DefaultWelcomeSceneConfigurator() 22 | } 23 | 24 | override func tearDown() { 25 | sut = nil 26 | viewModel = nil 27 | 28 | super.tearDown() 29 | } 30 | 31 | func test_configurator_configure_allDependenciesProperlyConfigured() { 32 | let configured = sut.configured(with: viewModel) 33 | 34 | XCTAssertTrue(configured.router is WelcomeSceneRouter) 35 | XCTAssertTrue( 36 | (configured.router as! WelcomeSceneRouter) 37 | .source is WelcomeSceneViewController 38 | ) 39 | XCTAssertTrue( 40 | configured.interactor is WelcomeSceneInteractor 41 | ) 42 | XCTAssertTrue( 43 | (configured.interactor as! WelcomeSceneInteractor) 44 | .presenter is WelcomeScenePresenter 45 | ) 46 | XCTAssertTrue( 47 | configured.interactor is WelcomeSceneInteractor 48 | ) 49 | XCTAssertNotNil(configured.viewModel) 50 | XCTAssertTrue( 51 | viewModel.delegate is WelcomeSceneViewController 52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Welcome/WelcomeSceneInteractorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeSceneInteractorTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | final class WelcomeSceneInteractorTests: XCTestCase { 10 | 11 | private var sut: WelcomeSceneInteractor! 12 | private var presenter: WelcomeScenePresenterMock! 13 | 14 | override func setUp() { 15 | super.setUp() 16 | 17 | UIView.setAnimationsEnabled(false) 18 | 19 | presenter = WelcomeScenePresenterMock() 20 | sut = WelcomeSceneInteractor() 21 | sut.presenter = presenter 22 | } 23 | 24 | override func tearDown() { 25 | sut = nil 26 | presenter = nil 27 | 28 | super.tearDown() 29 | } 30 | } 31 | 32 | // swiftlint:disable colon 33 | final class WelcomeScenePresenterMock: 34 | WelcomeScenePresenterInput { 35 | 36 | var showDetailsCalled = true 37 | func showDetails() { 38 | showDetailsCalled = false 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Welcome/WelcomeScenePresenterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeScenePresenterTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | final class WelcomeScenePresenterTests: XCTestCase { 10 | 11 | private var sut: WelcomeScenePresenter! 12 | private var viewController: WelcomeSceneViewControllerMock! 13 | private var router: WelcomeSceneRouterMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewController = WelcomeSceneViewControllerMock() 21 | router = WelcomeSceneRouterMock() 22 | viewController.router = router 23 | sut = WelcomeScenePresenter() 24 | sut.viewController = viewController 25 | } 26 | 27 | override func tearDown() { 28 | sut = nil 29 | viewController = nil 30 | router = nil 31 | 32 | super.tearDown() 33 | } 34 | } 35 | 36 | // swiftlint:disable colon 37 | final class WelcomeSceneViewControllerMock: 38 | WelcomeSceneViewControllerInput { 39 | var viewModel: WelcomeSceneViewModel? 40 | var router: WelcomeSceneRoutingLogic? 41 | } 42 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Welcome/WelcomeSceneRouterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeSceneRouterTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | final class WelcomeSceneRouterTests: XCTestCase { 10 | 11 | private var sut: WelcomeSceneRouter! 12 | private var viewController: UIViewControllerMock! 13 | private var navigationController: UINavigationControllerMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewController = UIViewControllerMock() 21 | navigationController 22 | = UINavigationControllerMock(rootViewController: UIViewController()) 23 | navigationController.pushViewController(viewController, animated: false) 24 | sut = WelcomeSceneRouter() 25 | sut.source = viewController 26 | } 27 | 28 | override func tearDown() { 29 | sut = nil 30 | viewController = nil 31 | navigationController = nil 32 | 33 | super.tearDown() 34 | } 35 | } 36 | 37 | private final class UIViewControllerMock: UIViewController {} 38 | 39 | private final class UINavigationControllerMock: UINavigationController {} 40 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Welcome/WelcomeSceneViewControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeSceneViewControllerTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | final class WelcomeSceneViewControllerTests: XCTestCase { 10 | 11 | private var sut: WelcomeSceneViewController! 12 | private var interactor: WelcomeSceneInteractorMock! 13 | private var router: WelcomeSceneRouterMock! 14 | private var viewModel: WelcomeSceneViewModelMock! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | 19 | UIView.setAnimationsEnabled(false) 20 | 21 | interactor = WelcomeSceneInteractorMock() 22 | router = WelcomeSceneRouterMock() 23 | viewModel = WelcomeSceneViewModelMock() 24 | sut = WelcomeSceneViewController( 25 | rootView: WelcomeSceneView(viewModel: viewModel) 26 | ) 27 | sut.interactor = interactor 28 | sut.router = router 29 | } 30 | 31 | override func tearDown() { 32 | sut = nil 33 | interactor = nil 34 | router = nil 35 | viewModel = nil 36 | 37 | super.tearDown() 38 | } 39 | } 40 | 41 | // swiftlint:disable colon 42 | final class WelcomeSceneInteractorMock: 43 | WelcomeSceneInteractorInput { 44 | 45 | var detailsCalled = true 46 | func details() { 47 | detailsCalled = false 48 | } 49 | } 50 | 51 | // swiftlint:disable colon 52 | final class WelcomeSceneRouterMock: 53 | WelcomeSceneRoutingLogic { 54 | 55 | var showDetailsCalled = false 56 | func showDetails(viewModel: DetailsSceneViewModel) { 57 | showDetailsCalled = true 58 | } 59 | } 60 | 61 | final class WelcomeSceneViewModelMock: WelcomeSceneViewModel { 62 | var text: String = "" 63 | var buttonText: String = "" 64 | 65 | var delegate: WelcomeSceneViewDelegate? 66 | } 67 | -------------------------------------------------------------------------------- /CleanSwiftWithSwiftUnitTests/Scenes/Welcome/WelcomeSceneViewModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeSceneViewModelTests.swift 3 | // CleanSwiftWithSwiftUI 4 | // 5 | 6 | @testable import CleanSwiftWithSwiftUI 7 | import XCTest 8 | 9 | // swiftlint:disable force_cast 10 | final class WelcomeSceneViewModelTests: XCTestCase { 11 | 12 | private var sut: WelcomeSceneViewModel! 13 | private var viewController: UIViewControllerMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewController = UIViewControllerMock() 21 | sut = DefaultWelcomeSceneViewModel(text: "", buttonText: "") 22 | sut.delegate = viewController 23 | } 24 | 25 | override func tearDown() { 26 | sut = nil 27 | viewController = nil 28 | 29 | super.tearDown() 30 | } 31 | } 32 | 33 | // swiftlint:disable colon 34 | private final class UIViewControllerMock: 35 | UIViewController, WelcomeSceneViewDelegate { 36 | 37 | var didSelectButtonCalled = false 38 | func didSelectButton(_ sender: WelcomeSceneViewModel?) { 39 | didSelectButtonCalled = true 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CleanSwiftWithSwiftUI 2 | Clean Swift Architectural Design Pattern with use of SwiftUI to keep the navigation layer in UIKit but the views in SwiftUI 3 | 4 | [check my article on Netguru's blog](https://www.netguru.com/codestories/clean-swift-with-swiftui-ios) 5 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Scene.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strzempa/CleanSwiftWithSwiftUI/00172764602354895dcca1350121b3b0312ed18e/XcodeTemplates/CleanSwift + SwiftUI/Scene.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Scene.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strzempa/CleanSwiftWithSwiftUI/00172764602354895dcca1350121b3b0312ed18e/XcodeTemplates/CleanSwift + SwiftUI/Scene.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Scene.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DefaultCompletionName 6 | MyScene 7 | Description 8 | This generates a new configurator to set up your scene 9 | Kind 10 | Xcode.IDEKit.TextSubstitutionFileTemplateKind 11 | Options 12 | 13 | 14 | Description 15 | The name of the scene to create 16 | Identifier 17 | sceneName 18 | Name 19 | New Scene Name: 20 | NotPersisted 21 | 22 | Required 23 | 24 | Type 25 | text 26 | 27 | 28 | Default 29 | ___VARIABLE_sceneName:identifier___Scene 30 | Identifier 31 | productName 32 | Type 33 | static 34 | 35 | 36 | Default 37 | ___VARIABLE_sceneName:identifier___SceneConfigurator 38 | Description 39 | The configurator name 40 | Identifier 41 | configuratorName 42 | Name 43 | Configurator Name 44 | Required 45 | 46 | Type 47 | static 48 | 49 | 50 | Default 51 | ___VARIABLE_sceneName:identifier___Scene 52 | Identifier 53 | productName 54 | Type 55 | static 56 | 57 | 58 | Default 59 | ___VARIABLE_sceneName:identifier___SceneInteractor 60 | Description 61 | The interactor name 62 | Identifier 63 | interactorName 64 | Name 65 | Interactor Name: 66 | Required 67 | 68 | Type 69 | static 70 | 71 | 72 | Default 73 | ___VARIABLE_sceneName:identifier___Scene 74 | Identifier 75 | productName 76 | Type 77 | static 78 | 79 | 80 | Default 81 | ___VARIABLE_sceneName:identifier___SceneView 82 | Description 83 | The view name 84 | Identifier 85 | viewName 86 | Name 87 | View Name: 88 | Required 89 | 90 | Type 91 | static 92 | 93 | 94 | Default 95 | ___VARIABLE_sceneName:identifier___Scene 96 | Identifier 97 | productName 98 | Type 99 | static 100 | 101 | 102 | Default 103 | ___VARIABLE_sceneName:identifier___SceneViewController 104 | Description 105 | The view controller name 106 | Identifier 107 | viewControllerName 108 | Name 109 | View Controller Name: 110 | Required 111 | 112 | Type 113 | static 114 | 115 | 116 | Default 117 | ___VARIABLE_sceneName:identifier___Scene 118 | Identifier 119 | productName 120 | Type 121 | static 122 | 123 | 124 | Default 125 | ___VARIABLE_sceneName:identifier___SceneRouter 126 | Description 127 | The router name 128 | Identifier 129 | routerName 130 | Name 131 | Router Name: 132 | Required 133 | 134 | Type 135 | static 136 | 137 | 138 | Default 139 | ___VARIABLE_sceneName:identifier___Scene 140 | Identifier 141 | productName 142 | Type 143 | static 144 | 145 | 146 | Default 147 | ___VARIABLE_sceneName:identifier___ScenePresenter 148 | Description 149 | The presenter name 150 | Identifier 151 | presenterName 152 | Name 153 | Presenter Name: 154 | Required 155 | 156 | Type 157 | static 158 | 159 | 160 | Platforms 161 | 162 | com.apple.platform.iphoneos 163 | 164 | SortOrder 165 | 6 166 | Summary 167 | This generates a new configurator to set up your scene 168 | 169 | 170 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Scene.xctemplate/___FILEBASENAME___Configurator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | import Foundation 7 | 8 | protocol ___VARIABLE_sceneName___SceneConfigurator { 9 | func configured( 10 | with viewModel: ___VARIABLE_sceneName___SceneViewModel 11 | ) -> ___VARIABLE_sceneName___SceneViewController 12 | } 13 | 14 | // swiftlint:disable colon 15 | final class Default___VARIABLE_sceneName___SceneConfigurator: 16 | ___VARIABLE_sceneName___SceneConfigurator { 17 | func configured( 18 | with viewModel: ___VARIABLE_sceneName___SceneViewModel 19 | ) -> ___VARIABLE_sceneName___SceneViewController { 20 | var viewModel = viewModel 21 | let viewController = ___VARIABLE_sceneName___SceneViewController( 22 | rootView: ___VARIABLE_sceneName___SceneView(viewModel: viewModel) 23 | ) 24 | let interactor = ___VARIABLE_sceneName___SceneInteractor() 25 | let presenter = ___VARIABLE_sceneName___ScenePresenter() 26 | let router = ___VARIABLE_sceneName___SceneRouter() 27 | router.source = viewController 28 | presenter.viewController = viewController 29 | interactor.presenter = presenter 30 | viewController.interactor = interactor 31 | viewController.router = router 32 | viewController.viewModel = viewModel 33 | viewModel.delegate = viewController 34 | return viewController 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Scene.xctemplate/___FILEBASENAME___Interactor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | import Foundation 7 | 8 | protocol ___VARIABLE_sceneName___SceneInteractorInput {} 9 | 10 | typealias ___VARIABLE_sceneName___SceneInteractorOutput 11 | = ___VARIABLE_sceneName___SceneInteractorInput 12 | 13 | final class ___VARIABLE_sceneName___SceneInteractor { 14 | var presenter: ___VARIABLE_sceneName___ScenePresenterInput? 15 | } 16 | 17 | // swiftlint:disable colon 18 | extension ___VARIABLE_sceneName___SceneInteractor: 19 | ___VARIABLE_sceneName___SceneInteractorInput {} 20 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Scene.xctemplate/___FILEBASENAME___Presenter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | import UIKit 7 | 8 | protocol ___VARIABLE_sceneName___ScenePresenterInput {} 9 | 10 | typealias ___VARIABLE_sceneName___ScenePresenterOutput 11 | = ___VARIABLE_sceneName___SceneViewControllerInput 12 | 13 | final class ___VARIABLE_sceneName___ScenePresenter { 14 | weak var viewController: ___VARIABLE_sceneName___ScenePresenterOutput? 15 | } 16 | 17 | // swiftlint:disable colon 18 | extension ___VARIABLE_sceneName___ScenePresenter: 19 | ___VARIABLE_sceneName___ScenePresenterInput {} 20 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Scene.xctemplate/___FILEBASENAME___Router.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | import UIKit 7 | 8 | protocol ___VARIABLE_sceneName___SceneRoutingLogic {} 9 | 10 | final class ___VARIABLE_sceneName___SceneRouter: NSObject { 11 | weak var source: UIViewController? 12 | } 13 | 14 | // swiftlint:disable colon 15 | extension ___VARIABLE_sceneName___SceneRouter: 16 | ___VARIABLE_sceneName___SceneRoutingLogic {} 17 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Scene.xctemplate/___FILEBASENAME___View.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | import SwiftUI 7 | 8 | protocol ___VARIABLE_sceneName___SceneViewDelegate: AnyObject {} 9 | 10 | protocol ___VARIABLE_sceneName___SceneViewModel { 11 | var delegate: ___VARIABLE_sceneName___SceneViewDelegate? { get set } 12 | } 13 | 14 | // swiftlint:disable colon 15 | final class Default___VARIABLE_sceneName___SceneViewModel: 16 | ___VARIABLE_sceneName___SceneViewModel { 17 | var delegate: ___VARIABLE_sceneName___SceneViewDelegate? 18 | } 19 | 20 | struct ___VARIABLE_sceneName___SceneView: View { 21 | let viewModel: ___VARIABLE_sceneName___SceneViewModel 22 | 23 | var body: some View { 24 | VStack { 25 | Text("TODO") 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Scene.xctemplate/___FILEBASENAME___ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | import UIKit 7 | import SwiftUI 8 | 9 | protocol ___VARIABLE_sceneName___SceneViewControllerInput: AnyObject { 10 | var router: ___VARIABLE_sceneName___SceneRoutingLogic? { get set } 11 | var viewModel: ___VARIABLE_sceneName___SceneViewModel? { get set } 12 | } 13 | 14 | typealias ___VARIABLE_sceneName___SceneViewControllerOutput 15 | = ___VARIABLE_sceneName___SceneInteractorInput 16 | 17 | // swiftlint:disable colon 18 | final class ___VARIABLE_sceneName___SceneViewController: 19 | UIHostingController<___VARIABLE_sceneName___SceneView> { 20 | var interactor: ___VARIABLE_sceneName___SceneViewControllerOutput? 21 | var router: ___VARIABLE_sceneName___SceneRoutingLogic? 22 | var viewModel: ___VARIABLE_sceneName___SceneViewModel? 23 | } 24 | 25 | // swiftlint:disable colon 26 | extension ___VARIABLE_sceneName___SceneViewController: 27 | ___VARIABLE_sceneName___SceneViewControllerInput {} 28 | 29 | // swiftlint:disable colon 30 | extension ___VARIABLE_sceneName___SceneViewController: 31 | ___VARIABLE_sceneName___SceneViewDelegate {} 32 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Unit Tests.xctemplate/TemplateIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strzempa/CleanSwiftWithSwiftUI/00172764602354895dcca1350121b3b0312ed18e/XcodeTemplates/CleanSwift + SwiftUI/Unit Tests.xctemplate/TemplateIcon.png -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Unit Tests.xctemplate/TemplateIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strzempa/CleanSwiftWithSwiftUI/00172764602354895dcca1350121b3b0312ed18e/XcodeTemplates/CleanSwift + SwiftUI/Unit Tests.xctemplate/TemplateIcon@2x.png -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Unit Tests.xctemplate/TemplateInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DefaultCompletionName 6 | MyScene 7 | Description 8 | This generates unit tests for your scene using Uncle Bob's clean architecture. It consists of three test files for view controller, interactor, and presenter. 9 | Kind 10 | Xcode.IDEKit.TextSubstitutionFileTemplateKind 11 | Options 12 | 13 | 14 | Description 15 | The name of the scene to create tests for 16 | Identifier 17 | sceneName 18 | Name 19 | Scene Name: 20 | NotPersisted 21 | 22 | Required 23 | 24 | Type 25 | text 26 | 27 | 28 | Default 29 | ___VARIABLE_sceneName:identifier___Scene 30 | Identifier 31 | productName 32 | Type 33 | static 34 | 35 | 36 | Default 37 | ___VARIABLE_sceneName:identifier___SceneViewControllerTests 38 | Description 39 | The view controller tests name 40 | Identifier 41 | viewControllerTestsName 42 | Name 43 | View Controller Tests Name: 44 | Required 45 | 46 | Type 47 | static 48 | 49 | 50 | Default 51 | ___VARIABLE_sceneName:identifier___SceneInteractorTests 52 | Description 53 | The interactor tests name 54 | Identifier 55 | interactorTestsName 56 | Name 57 | Interactor Tests Name: 58 | Required 59 | 60 | Type 61 | static 62 | 63 | 64 | Default 65 | ___VARIABLE_sceneName:identifier___ScenePresenterTests 66 | Description 67 | The presenter tests name 68 | Identifier 69 | presenterTestsName 70 | Name 71 | Presenter Tests Name: 72 | Required 73 | 74 | Type 75 | static 76 | 77 | 78 | Default 79 | ___VARIABLE_sceneName:identifier___SceneConfiguratorTests 80 | Description 81 | The configurator tests name 82 | Identifier 83 | configuratorTestsName 84 | Name 85 | Configurator Tests Name: 86 | Required 87 | 88 | Type 89 | static 90 | 91 | 92 | Default 93 | ___VARIABLE_sceneName:identifier___SceneViewModelTests 94 | Description 95 | The view tests name 96 | Identifier 97 | viewModelTestsName 98 | Name 99 | View Model Tests Name: 100 | Required 101 | 102 | Type 103 | static 104 | 105 | 106 | Default 107 | ___VARIABLE_sceneName:identifier___SceneRouterTests 108 | Description 109 | The router tests name 110 | Identifier 111 | routerTestsName 112 | Name 113 | Router Tests Name: 114 | Required 115 | 116 | Type 117 | static 118 | 119 | 120 | Platforms 121 | 122 | com.apple.platform.iphoneos 123 | 124 | SortOrder 125 | 7 126 | Summary 127 | This generates unit tests for your scene using Uncle Bob's clean architecture. 128 | 129 | 130 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Unit Tests.xctemplate/___FILEBASENAME___ConfiguratorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | @testable import ___PROJECTNAMEASIDENTIFIER___ 7 | import XCTest 8 | 9 | // swiftlint:disable force_cast 10 | final class ___VARIABLE_sceneName___SceneConfiguratorTests: XCTestCase { 11 | 12 | private var sut: ___VARIABLE_sceneName___SceneConfigurator! 13 | private var viewModel: ___VARIABLE_sceneName___SceneViewModelMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewModel = ___VARIABLE_sceneName___SceneViewModelMock() 21 | sut = Default___VARIABLE_sceneName___SceneConfigurator() 22 | } 23 | 24 | override func tearDown() { 25 | sut = nil 26 | viewModel = nil 27 | 28 | super.tearDown() 29 | } 30 | 31 | func test_configurator_configure_allDependenciesProperlyConfigured() { 32 | let configured = sut.configured(with: viewModel) 33 | 34 | XCTAssertTrue(configured.router is ___VARIABLE_sceneName___SceneRouter) 35 | XCTAssertTrue( 36 | (configured.router as! ___VARIABLE_sceneName___SceneRouter) 37 | .source is ___VARIABLE_sceneName___SceneViewController 38 | ) 39 | XCTAssertTrue( 40 | configured.interactor is ___VARIABLE_sceneName___SceneInteractor 41 | ) 42 | XCTAssertTrue( 43 | (configured.interactor as! ___VARIABLE_sceneName___SceneInteractor) 44 | .presenter is ___VARIABLE_sceneName___ScenePresenter 45 | ) 46 | XCTAssertTrue( 47 | configured.interactor is ___VARIABLE_sceneName___SceneInteractor 48 | ) 49 | XCTAssertNotNil(configured.viewModel) 50 | XCTAssertTrue( 51 | viewModel.delegate is ___VARIABLE_sceneName___SceneViewController 52 | ) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Unit Tests.xctemplate/___FILEBASENAME___InteractorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | @testable import ___PROJECTNAMEASIDENTIFIER___ 7 | import XCTest 8 | 9 | final class ___VARIABLE_sceneName___SceneInteractorTests: XCTestCase { 10 | 11 | private var sut: ___VARIABLE_sceneName___SceneInteractor! 12 | private var presenter: ___VARIABLE_sceneName___ScenePresenterMock! 13 | 14 | override func setUp() { 15 | super.setUp() 16 | 17 | UIView.setAnimationsEnabled(false) 18 | 19 | presenter = ___VARIABLE_sceneName___ScenePresenterMock() 20 | sut = ___VARIABLE_sceneName___SceneInteractor() 21 | sut.presenter = presenter 22 | } 23 | 24 | override func tearDown() { 25 | sut = nil 26 | presenter = nil 27 | 28 | super.tearDown() 29 | } 30 | } 31 | 32 | // swiftlint:disable colon 33 | final class ___VARIABLE_sceneName___ScenePresenterMock: 34 | ___VARIABLE_sceneName___ScenePresenterInput {} 35 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Unit Tests.xctemplate/___FILEBASENAME___PresenterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | @testable import ___PROJECTNAMEASIDENTIFIER___ 7 | import XCTest 8 | 9 | final class ___VARIABLE_sceneName___ScenePresenterTests: XCTestCase { 10 | 11 | private var sut: ___VARIABLE_sceneName___ScenePresenter! 12 | private var viewController: ___VARIABLE_sceneName___SceneViewControllerMock! 13 | private var router: ___VARIABLE_sceneName___SceneRouterMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewController = ___VARIABLE_sceneName___SceneViewControllerMock() 21 | router = ___VARIABLE_sceneName___SceneRouterMock() 22 | viewController.router = router 23 | sut = ___VARIABLE_sceneName___ScenePresenter() 24 | sut.viewController = viewController 25 | } 26 | 27 | override func tearDown() { 28 | sut = nil 29 | viewController = nil 30 | router = nil 31 | 32 | super.tearDown() 33 | } 34 | } 35 | 36 | // swiftlint:disable colon 37 | final class ___VARIABLE_sceneName___SceneViewControllerMock: 38 | ___VARIABLE_sceneName___SceneViewControllerInput { 39 | var viewModel: ___VARIABLE_sceneName___SceneViewModel? 40 | var router: ___VARIABLE_sceneName___SceneRoutingLogic? 41 | } 42 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Unit Tests.xctemplate/___FILEBASENAME___RouterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | @testable import ___PROJECTNAMEASIDENTIFIER___ 7 | import XCTest 8 | 9 | final class ___VARIABLE_sceneName___SceneRouterTests: XCTestCase { 10 | 11 | private var sut: ___VARIABLE_sceneName___SceneRouter! 12 | private var viewController: UIViewControllerMock! 13 | private var navigationController: UINavigationControllerMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewController = UIViewControllerMock() 21 | navigationController 22 | = UINavigationControllerMock(rootViewController: UIViewController()) 23 | navigationController.pushViewController(viewController, animated: false) 24 | sut = ___VARIABLE_sceneName___SceneRouter() 25 | sut.source = viewController 26 | } 27 | 28 | override func tearDown() { 29 | sut = nil 30 | viewController = nil 31 | navigationController = nil 32 | 33 | super.tearDown() 34 | } 35 | } 36 | 37 | private final class UIViewControllerMock: UIViewController {} 38 | 39 | private final class UINavigationControllerMock: UINavigationController {} 40 | -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Unit Tests.xctemplate/___FILEBASENAME___ViewControllerTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | @testable import ___PROJECTNAMEASIDENTIFIER___ 7 | import XCTest 8 | 9 | final class ___VARIABLE_sceneName___SceneViewControllerTests: XCTestCase { 10 | 11 | private var sut: ___VARIABLE_sceneName___SceneViewController! 12 | private var interactor: ___VARIABLE_sceneName___SceneInteractorMock! 13 | private var router: ___VARIABLE_sceneName___SceneRouterMock! 14 | private var viewModel: ___VARIABLE_sceneName___SceneViewModelMock! 15 | 16 | override func setUp() { 17 | super.setUp() 18 | 19 | UIView.setAnimationsEnabled(false) 20 | 21 | interactor = ___VARIABLE_sceneName___SceneInteractorMock() 22 | router = ___VARIABLE_sceneName___SceneRouterMock() 23 | viewModel = ___VARIABLE_sceneName___SceneViewModelMock() 24 | sut = ___VARIABLE_sceneName___SceneViewController( 25 | rootView: ___VARIABLE_sceneName___SceneView(viewModel: viewModel) 26 | ) 27 | sut.interactor = interactor 28 | sut.router = router 29 | } 30 | 31 | override func tearDown() { 32 | sut = nil 33 | interactor = nil 34 | router = nil 35 | viewModel = nil 36 | 37 | super.tearDown() 38 | } 39 | } 40 | 41 | // swiftlint:disable colon 42 | final class ___VARIABLE_sceneName___SceneInteractorMock: 43 | ___VARIABLE_sceneName___SceneInteractorInput {} 44 | 45 | // swiftlint:disable colon 46 | final class ___VARIABLE_sceneName___SceneRouterMock: 47 | ___VARIABLE_sceneName___SceneRoutingLogic {} 48 | 49 | final class ___VARIABLE_sceneName___SceneViewModelMock: ___VARIABLE_sceneName___SceneViewModel { 50 | var delegate: ___VARIABLE_sceneName___SceneViewDelegate? 51 | } -------------------------------------------------------------------------------- /XcodeTemplates/CleanSwift + SwiftUI/Unit Tests.xctemplate/___FILEBASENAME___ViewModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ___FILENAME___ 3 | // ___PROJECTNAME___ 4 | // 5 | 6 | @testable import ___PROJECTNAMEASIDENTIFIER___ 7 | import XCTest 8 | 9 | // swiftlint:disable force_cast 10 | final class ___VARIABLE_sceneName___SceneViewModelTests: XCTestCase { 11 | 12 | private var sut: ___VARIABLE_sceneName___SceneViewModel! 13 | private var viewController: UIViewControllerMock! 14 | 15 | override func setUp() { 16 | super.setUp() 17 | 18 | UIView.setAnimationsEnabled(false) 19 | 20 | viewController = UIViewControllerMock() 21 | sut = Default___VARIABLE_sceneName___SceneViewModel() 22 | sut.delegate = viewController 23 | } 24 | 25 | override func tearDown() { 26 | sut = nil 27 | viewController = nil 28 | 29 | super.tearDown() 30 | } 31 | } 32 | 33 | // swiftlint:disable colon 34 | private final class UIViewControllerMock: 35 | UIViewController, ___VARIABLE_sceneName___SceneViewDelegate {} 36 | --------------------------------------------------------------------------------