├── .gitignore ├── README.md ├── TargetArchitecture.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── TargetArchitecture.xcscheme ├── TargetArchitecture ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── Fakes │ └── FakeViewModel.swift ├── Info.plist ├── Models │ ├── Clock │ │ ├── Clock.swift │ │ ├── ClockError.swift │ │ └── ClockProtocol.swift │ └── Counter │ │ ├── Counter.swift │ │ └── CounterProtocol.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── SceneDelegate.swift ├── ViewModels │ ├── ViewModel.swift │ └── ViewModelProtocol.swift └── Views │ └── ContentView.swift ├── TargetArchitectureTests ├── ClockTests.swift ├── CounterTests.swift ├── Fakes │ ├── FakeClock.swift │ └── FakeCounter.swift ├── Info.plist └── ViewModelTests.swift └── TargetArchitectureUITests ├── Info.plist └── PocViewModelUITests.swift /.gitignore: -------------------------------------------------------------------------------- 1 | ##### 2 | # OS X temporary files that should never be committed 3 | .DS_Store 4 | *.swp 5 | profile 6 | 7 | #### 8 | # Xcode temporary files that should never be committed 9 | *~.nib 10 | 11 | #### 12 | # Objective-C/Swift specific 13 | *.hmap 14 | *.ipa 15 | 16 | #### 17 | # Xcode build files 18 | DerivedData/ 19 | build/ 20 | Builds/ 21 | 22 | ##### 23 | # Xcode private settings (window sizes, bookmarks, breakpoints, custom executables, smart groups) 24 | *.pbxuser 25 | *.mode1v3 26 | *.mode2v3 27 | *.perspectivev3 28 | !default.pbxuser 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.perspectivev3 32 | 33 | #### 34 | # Xcode 4 35 | xcuserdata 36 | !xcschemes 37 | # Xcode 4 38 | *.moved-aside 39 | 40 | #### 41 | # XCode 4 workspaces - more detailed 42 | !xcshareddata 43 | !xcworkspace 44 | *.xcworkspacedata 45 | 46 | 47 | #### 48 | # Xcode 5 49 | *.xccheckout 50 | *.xcuserstate 51 | 52 | #### 53 | # Xcode 7 54 | *.xcscmblueprint 55 | 56 | #### 57 | # AppCode 58 | .idea/ 59 | 60 | #### 61 | # Other Xcode files 62 | profile 63 | *.hmap 64 | *.ipa 65 | 66 | #### 67 | # CocoaPods 68 | Pods/ 69 | 70 | #### 71 | # Carthage 72 | Cartfile.resolved 73 | Carthage 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # swiftui-target-architecture by MobileFactory (SBB) 2 | This demo app showcases our implementation of the MVVM pattern for SwiftUI/Combine apps. It enables: 3 | * Separation of concerns, so multiple developers know and understand what they are working on. 4 | * Having SwiftUI Previews with mock data to efficiently develop and review UI, sometimes directly with designers and product owners. 5 | * Having an architecture which is suitable for Unit Testing and UI Testing so we can have a high Code Coverage of our app and thus ensure correctness of our software. 6 | Further reading: check out our medium blog post on this topic (https://medium.com/swlh/swiftui-app-architecture-124b0199d52c?source=friends_link&sk=a491a0b06d64c3c2f93e8eacfa7f13b6) 7 | 8 | ## Requirements 9 | 10 | * iOS 13.0 as we are using SwiftUI. 11 | 12 | ## Authors 13 | 14 | * 15 | 16 | ## License 17 | 18 | * SBB CFF FFS AG 19 | -------------------------------------------------------------------------------- /TargetArchitecture.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | A7DC2373250FA7000012EA25 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC2372250FA7000012EA25 /* AppDelegate.swift */; }; 11 | A7DC2375250FA7000012EA25 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC2374250FA7000012EA25 /* SceneDelegate.swift */; }; 12 | A7DC2377250FA7000012EA25 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC2376250FA7000012EA25 /* ContentView.swift */; }; 13 | A7DC2379250FA7020012EA25 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A7DC2378250FA7020012EA25 /* Assets.xcassets */; }; 14 | A7DC237C250FA7020012EA25 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A7DC237B250FA7020012EA25 /* Preview Assets.xcassets */; }; 15 | A7DC237F250FA7020012EA25 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = A7DC237D250FA7020012EA25 /* LaunchScreen.storyboard */; }; 16 | A7DC2395250FA7020012EA25 /* PocViewModelUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC2394250FA7020012EA25 /* PocViewModelUITests.swift */; }; 17 | A7DC23A7250FA7CC0012EA25 /* Clock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23A6250FA7CC0012EA25 /* Clock.swift */; }; 18 | A7DC23A9250FA7D70012EA25 /* ClockProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23A8250FA7D70012EA25 /* ClockProtocol.swift */; }; 19 | A7DC23AB250FA8CC0012EA25 /* ClockTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23AA250FA8CC0012EA25 /* ClockTests.swift */; }; 20 | A7DC23AD250FB81E0012EA25 /* Counter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23AC250FB81E0012EA25 /* Counter.swift */; }; 21 | A7DC23AF250FB82C0012EA25 /* CounterProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23AE250FB82C0012EA25 /* CounterProtocol.swift */; }; 22 | A7DC23B1250FB9C60012EA25 /* CounterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23B0250FB9C60012EA25 /* CounterTests.swift */; }; 23 | A7DC23B4250FBA440012EA25 /* FakeClock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23B3250FBA440012EA25 /* FakeClock.swift */; }; 24 | A7DC23B6250FC1070012EA25 /* ViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23B5250FC1070012EA25 /* ViewModel.swift */; }; 25 | A7DC23B8250FC4F40012EA25 /* ViewModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23B7250FC4F40012EA25 /* ViewModelProtocol.swift */; }; 26 | A7DC23BB2510801D0012EA25 /* FakeViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23BA2510801D0012EA25 /* FakeViewModel.swift */; }; 27 | A7DC23BD251082A40012EA25 /* FakeCounter.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23BC251082A40012EA25 /* FakeCounter.swift */; }; 28 | A7DC23BF2510832A0012EA25 /* ViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DC23BE2510832A0012EA25 /* ViewModelTests.swift */; }; 29 | A7DEB8582580D43A000A94EA /* ClockError.swift in Sources */ = {isa = PBXBuildFile; fileRef = A7DEB8572580D43A000A94EA /* ClockError.swift */; }; 30 | /* End PBXBuildFile section */ 31 | 32 | /* Begin PBXContainerItemProxy section */ 33 | A7DC2386250FA7020012EA25 /* PBXContainerItemProxy */ = { 34 | isa = PBXContainerItemProxy; 35 | containerPortal = A7DC2367250FA7000012EA25 /* Project object */; 36 | proxyType = 1; 37 | remoteGlobalIDString = A7DC236E250FA7000012EA25; 38 | remoteInfo = PocViewModel; 39 | }; 40 | A7DC2391250FA7020012EA25 /* PBXContainerItemProxy */ = { 41 | isa = PBXContainerItemProxy; 42 | containerPortal = A7DC2367250FA7000012EA25 /* Project object */; 43 | proxyType = 1; 44 | remoteGlobalIDString = A7DC236E250FA7000012EA25; 45 | remoteInfo = PocViewModel; 46 | }; 47 | /* End PBXContainerItemProxy section */ 48 | 49 | /* Begin PBXFileReference section */ 50 | A7DC236F250FA7000012EA25 /* TargetArchitecture.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = TargetArchitecture.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | A7DC2372250FA7000012EA25 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 52 | A7DC2374250FA7000012EA25 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 53 | A7DC2376250FA7000012EA25 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 54 | A7DC2378250FA7020012EA25 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 55 | A7DC237B250FA7020012EA25 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 56 | A7DC237E250FA7020012EA25 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 57 | A7DC2380250FA7020012EA25 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | A7DC2385250FA7020012EA25 /* TargetArchitectureTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TargetArchitectureTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | A7DC238B250FA7020012EA25 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 60 | A7DC2390250FA7020012EA25 /* TargetArchitectureUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = TargetArchitectureUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 61 | A7DC2394250FA7020012EA25 /* PocViewModelUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PocViewModelUITests.swift; sourceTree = ""; }; 62 | A7DC2396250FA7020012EA25 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 63 | A7DC23A6250FA7CC0012EA25 /* Clock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Clock.swift; sourceTree = ""; }; 64 | A7DC23A8250FA7D70012EA25 /* ClockProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockProtocol.swift; sourceTree = ""; }; 65 | A7DC23AA250FA8CC0012EA25 /* ClockTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockTests.swift; sourceTree = ""; }; 66 | A7DC23AC250FB81E0012EA25 /* Counter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Counter.swift; sourceTree = ""; }; 67 | A7DC23AE250FB82C0012EA25 /* CounterProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterProtocol.swift; sourceTree = ""; }; 68 | A7DC23B0250FB9C60012EA25 /* CounterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CounterTests.swift; sourceTree = ""; }; 69 | A7DC23B3250FBA440012EA25 /* FakeClock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeClock.swift; sourceTree = ""; }; 70 | A7DC23B5250FC1070012EA25 /* ViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModel.swift; sourceTree = ""; }; 71 | A7DC23B7250FC4F40012EA25 /* ViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelProtocol.swift; sourceTree = ""; }; 72 | A7DC23BA2510801D0012EA25 /* FakeViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeViewModel.swift; sourceTree = ""; }; 73 | A7DC23BC251082A40012EA25 /* FakeCounter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakeCounter.swift; sourceTree = ""; }; 74 | A7DC23BE2510832A0012EA25 /* ViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewModelTests.swift; sourceTree = ""; }; 75 | A7DEB8572580D43A000A94EA /* ClockError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClockError.swift; sourceTree = ""; }; 76 | /* End PBXFileReference section */ 77 | 78 | /* Begin PBXFrameworksBuildPhase section */ 79 | A7DC236C250FA7000012EA25 /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | ); 84 | runOnlyForDeploymentPostprocessing = 0; 85 | }; 86 | A7DC2382250FA7020012EA25 /* Frameworks */ = { 87 | isa = PBXFrameworksBuildPhase; 88 | buildActionMask = 2147483647; 89 | files = ( 90 | ); 91 | runOnlyForDeploymentPostprocessing = 0; 92 | }; 93 | A7DC238D250FA7020012EA25 /* Frameworks */ = { 94 | isa = PBXFrameworksBuildPhase; 95 | buildActionMask = 2147483647; 96 | files = ( 97 | ); 98 | runOnlyForDeploymentPostprocessing = 0; 99 | }; 100 | /* End PBXFrameworksBuildPhase section */ 101 | 102 | /* Begin PBXGroup section */ 103 | A7A0FD3B2590D40300647954 /* Clock */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | A7DC23A6250FA7CC0012EA25 /* Clock.swift */, 107 | A7DEB8572580D43A000A94EA /* ClockError.swift */, 108 | A7DC23A8250FA7D70012EA25 /* ClockProtocol.swift */, 109 | ); 110 | path = Clock; 111 | sourceTree = ""; 112 | }; 113 | A7A0FD3C2590D40C00647954 /* Counter */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | A7DC23AC250FB81E0012EA25 /* Counter.swift */, 117 | A7DC23AE250FB82C0012EA25 /* CounterProtocol.swift */, 118 | ); 119 | path = Counter; 120 | sourceTree = ""; 121 | }; 122 | A7DC2366250FA7000012EA25 = { 123 | isa = PBXGroup; 124 | children = ( 125 | A7DC2371250FA7000012EA25 /* TargetArchitecture */, 126 | A7DC2388250FA7020012EA25 /* TargetArchitectureTests */, 127 | A7DC2393250FA7020012EA25 /* TargetArchitectureUITests */, 128 | A7DC2370250FA7000012EA25 /* Products */, 129 | ); 130 | sourceTree = ""; 131 | }; 132 | A7DC2370250FA7000012EA25 /* Products */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | A7DC236F250FA7000012EA25 /* TargetArchitecture.app */, 136 | A7DC2385250FA7020012EA25 /* TargetArchitectureTests.xctest */, 137 | A7DC2390250FA7020012EA25 /* TargetArchitectureUITests.xctest */, 138 | ); 139 | name = Products; 140 | sourceTree = ""; 141 | }; 142 | A7DC2371250FA7000012EA25 /* TargetArchitecture */ = { 143 | isa = PBXGroup; 144 | children = ( 145 | A7DC2372250FA7000012EA25 /* AppDelegate.swift */, 146 | A7DC2374250FA7000012EA25 /* SceneDelegate.swift */, 147 | A7DC23A2250FA73F0012EA25 /* Views */, 148 | A7DC23A3250FA74B0012EA25 /* ViewModels */, 149 | A7DC23A4250FA7530012EA25 /* Models */, 150 | A7DC23B9251080070012EA25 /* Fakes */, 151 | A7DC2378250FA7020012EA25 /* Assets.xcassets */, 152 | A7DC237D250FA7020012EA25 /* LaunchScreen.storyboard */, 153 | A7DC2380250FA7020012EA25 /* Info.plist */, 154 | A7DC237A250FA7020012EA25 /* Preview Content */, 155 | ); 156 | path = TargetArchitecture; 157 | sourceTree = ""; 158 | }; 159 | A7DC237A250FA7020012EA25 /* Preview Content */ = { 160 | isa = PBXGroup; 161 | children = ( 162 | A7DC237B250FA7020012EA25 /* Preview Assets.xcassets */, 163 | ); 164 | path = "Preview Content"; 165 | sourceTree = ""; 166 | }; 167 | A7DC2388250FA7020012EA25 /* TargetArchitectureTests */ = { 168 | isa = PBXGroup; 169 | children = ( 170 | A7DC238B250FA7020012EA25 /* Info.plist */, 171 | A7DC23AA250FA8CC0012EA25 /* ClockTests.swift */, 172 | A7DC23B0250FB9C60012EA25 /* CounterTests.swift */, 173 | A7DC23BE2510832A0012EA25 /* ViewModelTests.swift */, 174 | A7DC23B2250FBA330012EA25 /* Fakes */, 175 | ); 176 | path = TargetArchitectureTests; 177 | sourceTree = ""; 178 | }; 179 | A7DC2393250FA7020012EA25 /* TargetArchitectureUITests */ = { 180 | isa = PBXGroup; 181 | children = ( 182 | A7DC2394250FA7020012EA25 /* PocViewModelUITests.swift */, 183 | A7DC2396250FA7020012EA25 /* Info.plist */, 184 | ); 185 | path = TargetArchitectureUITests; 186 | sourceTree = ""; 187 | }; 188 | A7DC23A2250FA73F0012EA25 /* Views */ = { 189 | isa = PBXGroup; 190 | children = ( 191 | A7DC2376250FA7000012EA25 /* ContentView.swift */, 192 | ); 193 | path = Views; 194 | sourceTree = ""; 195 | }; 196 | A7DC23A3250FA74B0012EA25 /* ViewModels */ = { 197 | isa = PBXGroup; 198 | children = ( 199 | A7DC23B5250FC1070012EA25 /* ViewModel.swift */, 200 | A7DC23B7250FC4F40012EA25 /* ViewModelProtocol.swift */, 201 | ); 202 | path = ViewModels; 203 | sourceTree = ""; 204 | }; 205 | A7DC23A4250FA7530012EA25 /* Models */ = { 206 | isa = PBXGroup; 207 | children = ( 208 | A7A0FD3B2590D40300647954 /* Clock */, 209 | A7A0FD3C2590D40C00647954 /* Counter */, 210 | ); 211 | path = Models; 212 | sourceTree = ""; 213 | }; 214 | A7DC23B2250FBA330012EA25 /* Fakes */ = { 215 | isa = PBXGroup; 216 | children = ( 217 | A7DC23B3250FBA440012EA25 /* FakeClock.swift */, 218 | A7DC23BC251082A40012EA25 /* FakeCounter.swift */, 219 | ); 220 | path = Fakes; 221 | sourceTree = ""; 222 | }; 223 | A7DC23B9251080070012EA25 /* Fakes */ = { 224 | isa = PBXGroup; 225 | children = ( 226 | A7DC23BA2510801D0012EA25 /* FakeViewModel.swift */, 227 | ); 228 | path = Fakes; 229 | sourceTree = ""; 230 | }; 231 | /* End PBXGroup section */ 232 | 233 | /* Begin PBXNativeTarget section */ 234 | A7DC236E250FA7000012EA25 /* TargetArchitecture */ = { 235 | isa = PBXNativeTarget; 236 | buildConfigurationList = A7DC2399250FA7020012EA25 /* Build configuration list for PBXNativeTarget "TargetArchitecture" */; 237 | buildPhases = ( 238 | A7DC236B250FA7000012EA25 /* Sources */, 239 | A7DC236C250FA7000012EA25 /* Frameworks */, 240 | A7DC236D250FA7000012EA25 /* Resources */, 241 | ); 242 | buildRules = ( 243 | ); 244 | dependencies = ( 245 | ); 246 | name = TargetArchitecture; 247 | productName = PocViewModel; 248 | productReference = A7DC236F250FA7000012EA25 /* TargetArchitecture.app */; 249 | productType = "com.apple.product-type.application"; 250 | }; 251 | A7DC2384250FA7020012EA25 /* TargetArchitectureTests */ = { 252 | isa = PBXNativeTarget; 253 | buildConfigurationList = A7DC239C250FA7020012EA25 /* Build configuration list for PBXNativeTarget "TargetArchitectureTests" */; 254 | buildPhases = ( 255 | A7DC2381250FA7020012EA25 /* Sources */, 256 | A7DC2382250FA7020012EA25 /* Frameworks */, 257 | A7DC2383250FA7020012EA25 /* Resources */, 258 | ); 259 | buildRules = ( 260 | ); 261 | dependencies = ( 262 | A7DC2387250FA7020012EA25 /* PBXTargetDependency */, 263 | ); 264 | name = TargetArchitectureTests; 265 | productName = PocViewModelTests; 266 | productReference = A7DC2385250FA7020012EA25 /* TargetArchitectureTests.xctest */; 267 | productType = "com.apple.product-type.bundle.unit-test"; 268 | }; 269 | A7DC238F250FA7020012EA25 /* TargetArchitectureUITests */ = { 270 | isa = PBXNativeTarget; 271 | buildConfigurationList = A7DC239F250FA7020012EA25 /* Build configuration list for PBXNativeTarget "TargetArchitectureUITests" */; 272 | buildPhases = ( 273 | A7DC238C250FA7020012EA25 /* Sources */, 274 | A7DC238D250FA7020012EA25 /* Frameworks */, 275 | A7DC238E250FA7020012EA25 /* Resources */, 276 | ); 277 | buildRules = ( 278 | ); 279 | dependencies = ( 280 | A7DC2392250FA7020012EA25 /* PBXTargetDependency */, 281 | ); 282 | name = TargetArchitectureUITests; 283 | productName = PocViewModelUITests; 284 | productReference = A7DC2390250FA7020012EA25 /* TargetArchitectureUITests.xctest */; 285 | productType = "com.apple.product-type.bundle.ui-testing"; 286 | }; 287 | /* End PBXNativeTarget section */ 288 | 289 | /* Begin PBXProject section */ 290 | A7DC2367250FA7000012EA25 /* Project object */ = { 291 | isa = PBXProject; 292 | attributes = { 293 | LastSwiftUpdateCheck = 1170; 294 | LastUpgradeCheck = 1220; 295 | ORGANIZATIONNAME = "Schweizerische Bundesbahnen SBB"; 296 | TargetAttributes = { 297 | A7DC236E250FA7000012EA25 = { 298 | CreatedOnToolsVersion = 11.7; 299 | }; 300 | A7DC2384250FA7020012EA25 = { 301 | CreatedOnToolsVersion = 11.7; 302 | TestTargetID = A7DC236E250FA7000012EA25; 303 | }; 304 | A7DC238F250FA7020012EA25 = { 305 | CreatedOnToolsVersion = 11.7; 306 | TestTargetID = A7DC236E250FA7000012EA25; 307 | }; 308 | }; 309 | }; 310 | buildConfigurationList = A7DC236A250FA7000012EA25 /* Build configuration list for PBXProject "TargetArchitecture" */; 311 | compatibilityVersion = "Xcode 9.3"; 312 | developmentRegion = en; 313 | hasScannedForEncodings = 0; 314 | knownRegions = ( 315 | en, 316 | Base, 317 | ); 318 | mainGroup = A7DC2366250FA7000012EA25; 319 | productRefGroup = A7DC2370250FA7000012EA25 /* Products */; 320 | projectDirPath = ""; 321 | projectRoot = ""; 322 | targets = ( 323 | A7DC236E250FA7000012EA25 /* TargetArchitecture */, 324 | A7DC2384250FA7020012EA25 /* TargetArchitectureTests */, 325 | A7DC238F250FA7020012EA25 /* TargetArchitectureUITests */, 326 | ); 327 | }; 328 | /* End PBXProject section */ 329 | 330 | /* Begin PBXResourcesBuildPhase section */ 331 | A7DC236D250FA7000012EA25 /* Resources */ = { 332 | isa = PBXResourcesBuildPhase; 333 | buildActionMask = 2147483647; 334 | files = ( 335 | A7DC237F250FA7020012EA25 /* LaunchScreen.storyboard in Resources */, 336 | A7DC237C250FA7020012EA25 /* Preview Assets.xcassets in Resources */, 337 | A7DC2379250FA7020012EA25 /* Assets.xcassets in Resources */, 338 | ); 339 | runOnlyForDeploymentPostprocessing = 0; 340 | }; 341 | A7DC2383250FA7020012EA25 /* Resources */ = { 342 | isa = PBXResourcesBuildPhase; 343 | buildActionMask = 2147483647; 344 | files = ( 345 | ); 346 | runOnlyForDeploymentPostprocessing = 0; 347 | }; 348 | A7DC238E250FA7020012EA25 /* Resources */ = { 349 | isa = PBXResourcesBuildPhase; 350 | buildActionMask = 2147483647; 351 | files = ( 352 | ); 353 | runOnlyForDeploymentPostprocessing = 0; 354 | }; 355 | /* End PBXResourcesBuildPhase section */ 356 | 357 | /* Begin PBXSourcesBuildPhase section */ 358 | A7DC236B250FA7000012EA25 /* Sources */ = { 359 | isa = PBXSourcesBuildPhase; 360 | buildActionMask = 2147483647; 361 | files = ( 362 | A7DC2373250FA7000012EA25 /* AppDelegate.swift in Sources */, 363 | A7DC23BB2510801D0012EA25 /* FakeViewModel.swift in Sources */, 364 | A7DC23AD250FB81E0012EA25 /* Counter.swift in Sources */, 365 | A7DC23B8250FC4F40012EA25 /* ViewModelProtocol.swift in Sources */, 366 | A7DC23A7250FA7CC0012EA25 /* Clock.swift in Sources */, 367 | A7DC2375250FA7000012EA25 /* SceneDelegate.swift in Sources */, 368 | A7DC2377250FA7000012EA25 /* ContentView.swift in Sources */, 369 | A7DEB8582580D43A000A94EA /* ClockError.swift in Sources */, 370 | A7DC23AF250FB82C0012EA25 /* CounterProtocol.swift in Sources */, 371 | A7DC23B6250FC1070012EA25 /* ViewModel.swift in Sources */, 372 | A7DC23A9250FA7D70012EA25 /* ClockProtocol.swift in Sources */, 373 | ); 374 | runOnlyForDeploymentPostprocessing = 0; 375 | }; 376 | A7DC2381250FA7020012EA25 /* Sources */ = { 377 | isa = PBXSourcesBuildPhase; 378 | buildActionMask = 2147483647; 379 | files = ( 380 | A7DC23AB250FA8CC0012EA25 /* ClockTests.swift in Sources */, 381 | A7DC23B1250FB9C60012EA25 /* CounterTests.swift in Sources */, 382 | A7DC23BD251082A40012EA25 /* FakeCounter.swift in Sources */, 383 | A7DC23BF2510832A0012EA25 /* ViewModelTests.swift in Sources */, 384 | A7DC23B4250FBA440012EA25 /* FakeClock.swift in Sources */, 385 | ); 386 | runOnlyForDeploymentPostprocessing = 0; 387 | }; 388 | A7DC238C250FA7020012EA25 /* Sources */ = { 389 | isa = PBXSourcesBuildPhase; 390 | buildActionMask = 2147483647; 391 | files = ( 392 | A7DC2395250FA7020012EA25 /* PocViewModelUITests.swift in Sources */, 393 | ); 394 | runOnlyForDeploymentPostprocessing = 0; 395 | }; 396 | /* End PBXSourcesBuildPhase section */ 397 | 398 | /* Begin PBXTargetDependency section */ 399 | A7DC2387250FA7020012EA25 /* PBXTargetDependency */ = { 400 | isa = PBXTargetDependency; 401 | target = A7DC236E250FA7000012EA25 /* TargetArchitecture */; 402 | targetProxy = A7DC2386250FA7020012EA25 /* PBXContainerItemProxy */; 403 | }; 404 | A7DC2392250FA7020012EA25 /* PBXTargetDependency */ = { 405 | isa = PBXTargetDependency; 406 | target = A7DC236E250FA7000012EA25 /* TargetArchitecture */; 407 | targetProxy = A7DC2391250FA7020012EA25 /* PBXContainerItemProxy */; 408 | }; 409 | /* End PBXTargetDependency section */ 410 | 411 | /* Begin PBXVariantGroup section */ 412 | A7DC237D250FA7020012EA25 /* LaunchScreen.storyboard */ = { 413 | isa = PBXVariantGroup; 414 | children = ( 415 | A7DC237E250FA7020012EA25 /* Base */, 416 | ); 417 | name = LaunchScreen.storyboard; 418 | sourceTree = ""; 419 | }; 420 | /* End PBXVariantGroup section */ 421 | 422 | /* Begin XCBuildConfiguration section */ 423 | A7DC2397250FA7020012EA25 /* Debug */ = { 424 | isa = XCBuildConfiguration; 425 | buildSettings = { 426 | ALWAYS_SEARCH_USER_PATHS = NO; 427 | CLANG_ANALYZER_NONNULL = YES; 428 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 429 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 430 | CLANG_CXX_LIBRARY = "libc++"; 431 | CLANG_ENABLE_MODULES = YES; 432 | CLANG_ENABLE_OBJC_ARC = YES; 433 | CLANG_ENABLE_OBJC_WEAK = YES; 434 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 435 | CLANG_WARN_BOOL_CONVERSION = YES; 436 | CLANG_WARN_COMMA = YES; 437 | CLANG_WARN_CONSTANT_CONVERSION = YES; 438 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 439 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 440 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 441 | CLANG_WARN_EMPTY_BODY = YES; 442 | CLANG_WARN_ENUM_CONVERSION = YES; 443 | CLANG_WARN_INFINITE_RECURSION = YES; 444 | CLANG_WARN_INT_CONVERSION = YES; 445 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 446 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 447 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 448 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 449 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 450 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 451 | CLANG_WARN_STRICT_PROTOTYPES = YES; 452 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 453 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 454 | CLANG_WARN_UNREACHABLE_CODE = YES; 455 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 456 | COPY_PHASE_STRIP = NO; 457 | DEBUG_INFORMATION_FORMAT = dwarf; 458 | ENABLE_STRICT_OBJC_MSGSEND = YES; 459 | ENABLE_TESTABILITY = YES; 460 | GCC_C_LANGUAGE_STANDARD = gnu11; 461 | GCC_DYNAMIC_NO_PIC = NO; 462 | GCC_NO_COMMON_BLOCKS = YES; 463 | GCC_OPTIMIZATION_LEVEL = 0; 464 | GCC_PREPROCESSOR_DEFINITIONS = ( 465 | "DEBUG=1", 466 | "$(inherited)", 467 | ); 468 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 469 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 470 | GCC_WARN_UNDECLARED_SELECTOR = YES; 471 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 472 | GCC_WARN_UNUSED_FUNCTION = YES; 473 | GCC_WARN_UNUSED_VARIABLE = YES; 474 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 475 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 476 | MTL_FAST_MATH = YES; 477 | ONLY_ACTIVE_ARCH = YES; 478 | SDKROOT = iphoneos; 479 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 480 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 481 | }; 482 | name = Debug; 483 | }; 484 | A7DC2398250FA7020012EA25 /* Release */ = { 485 | isa = XCBuildConfiguration; 486 | buildSettings = { 487 | ALWAYS_SEARCH_USER_PATHS = NO; 488 | CLANG_ANALYZER_NONNULL = YES; 489 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 490 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 491 | CLANG_CXX_LIBRARY = "libc++"; 492 | CLANG_ENABLE_MODULES = YES; 493 | CLANG_ENABLE_OBJC_ARC = YES; 494 | CLANG_ENABLE_OBJC_WEAK = YES; 495 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 496 | CLANG_WARN_BOOL_CONVERSION = YES; 497 | CLANG_WARN_COMMA = YES; 498 | CLANG_WARN_CONSTANT_CONVERSION = YES; 499 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 500 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 501 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 502 | CLANG_WARN_EMPTY_BODY = YES; 503 | CLANG_WARN_ENUM_CONVERSION = YES; 504 | CLANG_WARN_INFINITE_RECURSION = YES; 505 | CLANG_WARN_INT_CONVERSION = YES; 506 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 507 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 508 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 509 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 510 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 511 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 512 | CLANG_WARN_STRICT_PROTOTYPES = YES; 513 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 514 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 515 | CLANG_WARN_UNREACHABLE_CODE = YES; 516 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 517 | COPY_PHASE_STRIP = NO; 518 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 519 | ENABLE_NS_ASSERTIONS = NO; 520 | ENABLE_STRICT_OBJC_MSGSEND = YES; 521 | GCC_C_LANGUAGE_STANDARD = gnu11; 522 | GCC_NO_COMMON_BLOCKS = YES; 523 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 524 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 525 | GCC_WARN_UNDECLARED_SELECTOR = YES; 526 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 527 | GCC_WARN_UNUSED_FUNCTION = YES; 528 | GCC_WARN_UNUSED_VARIABLE = YES; 529 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 530 | MTL_ENABLE_DEBUG_INFO = NO; 531 | MTL_FAST_MATH = YES; 532 | SDKROOT = iphoneos; 533 | SWIFT_COMPILATION_MODE = wholemodule; 534 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 535 | VALIDATE_PRODUCT = YES; 536 | }; 537 | name = Release; 538 | }; 539 | A7DC239A250FA7020012EA25 /* Debug */ = { 540 | isa = XCBuildConfiguration; 541 | buildSettings = { 542 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 543 | CODE_SIGN_STYLE = Automatic; 544 | DEVELOPMENT_ASSET_PATHS = "\"TargetArchitecture/Preview Content\""; 545 | DEVELOPMENT_TEAM = P2M8L992PE; 546 | ENABLE_PREVIEWS = YES; 547 | INFOPLIST_FILE = TargetArchitecture/Info.plist; 548 | LD_RUNPATH_SEARCH_PATHS = ( 549 | "$(inherited)", 550 | "@executable_path/Frameworks", 551 | ); 552 | PRODUCT_BUNDLE_IDENTIFIER = ch.sbb.TargetArchitecture; 553 | PRODUCT_NAME = "$(TARGET_NAME)"; 554 | SWIFT_VERSION = 5.0; 555 | TARGETED_DEVICE_FAMILY = 1; 556 | }; 557 | name = Debug; 558 | }; 559 | A7DC239B250FA7020012EA25 /* Release */ = { 560 | isa = XCBuildConfiguration; 561 | buildSettings = { 562 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 563 | CODE_SIGN_STYLE = Automatic; 564 | DEVELOPMENT_ASSET_PATHS = "\"TargetArchitecture/Preview Content\""; 565 | DEVELOPMENT_TEAM = P2M8L992PE; 566 | ENABLE_PREVIEWS = YES; 567 | INFOPLIST_FILE = TargetArchitecture/Info.plist; 568 | LD_RUNPATH_SEARCH_PATHS = ( 569 | "$(inherited)", 570 | "@executable_path/Frameworks", 571 | ); 572 | PRODUCT_BUNDLE_IDENTIFIER = ch.sbb.TargetArchitecture; 573 | PRODUCT_NAME = "$(TARGET_NAME)"; 574 | SWIFT_VERSION = 5.0; 575 | TARGETED_DEVICE_FAMILY = 1; 576 | }; 577 | name = Release; 578 | }; 579 | A7DC239D250FA7020012EA25 /* Debug */ = { 580 | isa = XCBuildConfiguration; 581 | buildSettings = { 582 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 583 | BUNDLE_LOADER = "$(TEST_HOST)"; 584 | CODE_SIGN_STYLE = Automatic; 585 | DEVELOPMENT_TEAM = P2M8L992PE; 586 | INFOPLIST_FILE = TargetArchitectureTests/Info.plist; 587 | IPHONEOS_DEPLOYMENT_TARGET = 13.7; 588 | LD_RUNPATH_SEARCH_PATHS = ( 589 | "$(inherited)", 590 | "@executable_path/Frameworks", 591 | "@loader_path/Frameworks", 592 | ); 593 | PRODUCT_BUNDLE_IDENTIFIER = ch.sbb.TargetArchitectureTests; 594 | PRODUCT_NAME = "$(TARGET_NAME)"; 595 | SWIFT_VERSION = 5.0; 596 | TARGETED_DEVICE_FAMILY = "1,2"; 597 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TargetArchitecture.app/TargetArchitecture"; 598 | }; 599 | name = Debug; 600 | }; 601 | A7DC239E250FA7020012EA25 /* Release */ = { 602 | isa = XCBuildConfiguration; 603 | buildSettings = { 604 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 605 | BUNDLE_LOADER = "$(TEST_HOST)"; 606 | CODE_SIGN_STYLE = Automatic; 607 | DEVELOPMENT_TEAM = P2M8L992PE; 608 | INFOPLIST_FILE = TargetArchitectureTests/Info.plist; 609 | IPHONEOS_DEPLOYMENT_TARGET = 13.7; 610 | LD_RUNPATH_SEARCH_PATHS = ( 611 | "$(inherited)", 612 | "@executable_path/Frameworks", 613 | "@loader_path/Frameworks", 614 | ); 615 | PRODUCT_BUNDLE_IDENTIFIER = ch.sbb.TargetArchitectureTests; 616 | PRODUCT_NAME = "$(TARGET_NAME)"; 617 | SWIFT_VERSION = 5.0; 618 | TARGETED_DEVICE_FAMILY = "1,2"; 619 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TargetArchitecture.app/TargetArchitecture"; 620 | }; 621 | name = Release; 622 | }; 623 | A7DC23A0250FA7020012EA25 /* Debug */ = { 624 | isa = XCBuildConfiguration; 625 | buildSettings = { 626 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 627 | CODE_SIGN_STYLE = Automatic; 628 | DEVELOPMENT_TEAM = P2M8L992PE; 629 | INFOPLIST_FILE = TargetArchitectureUITests/Info.plist; 630 | LD_RUNPATH_SEARCH_PATHS = ( 631 | "$(inherited)", 632 | "@executable_path/Frameworks", 633 | "@loader_path/Frameworks", 634 | ); 635 | PRODUCT_BUNDLE_IDENTIFIER = ch.sbb.TargetArchitectureUITests; 636 | PRODUCT_NAME = "$(TARGET_NAME)"; 637 | SWIFT_VERSION = 5.0; 638 | TARGETED_DEVICE_FAMILY = "1,2"; 639 | TEST_TARGET_NAME = TargetArchitecture; 640 | }; 641 | name = Debug; 642 | }; 643 | A7DC23A1250FA7020012EA25 /* Release */ = { 644 | isa = XCBuildConfiguration; 645 | buildSettings = { 646 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 647 | CODE_SIGN_STYLE = Automatic; 648 | DEVELOPMENT_TEAM = P2M8L992PE; 649 | INFOPLIST_FILE = TargetArchitectureUITests/Info.plist; 650 | LD_RUNPATH_SEARCH_PATHS = ( 651 | "$(inherited)", 652 | "@executable_path/Frameworks", 653 | "@loader_path/Frameworks", 654 | ); 655 | PRODUCT_BUNDLE_IDENTIFIER = ch.sbb.TargetArchitectureUITests; 656 | PRODUCT_NAME = "$(TARGET_NAME)"; 657 | SWIFT_VERSION = 5.0; 658 | TARGETED_DEVICE_FAMILY = "1,2"; 659 | TEST_TARGET_NAME = TargetArchitecture; 660 | }; 661 | name = Release; 662 | }; 663 | A7DC23C02510B1AD0012EA25 /* Tests */ = { 664 | isa = XCBuildConfiguration; 665 | buildSettings = { 666 | ALWAYS_SEARCH_USER_PATHS = NO; 667 | CLANG_ANALYZER_NONNULL = YES; 668 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 669 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 670 | CLANG_CXX_LIBRARY = "libc++"; 671 | CLANG_ENABLE_MODULES = YES; 672 | CLANG_ENABLE_OBJC_ARC = YES; 673 | CLANG_ENABLE_OBJC_WEAK = YES; 674 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 675 | CLANG_WARN_BOOL_CONVERSION = YES; 676 | CLANG_WARN_COMMA = YES; 677 | CLANG_WARN_CONSTANT_CONVERSION = YES; 678 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 679 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 680 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 681 | CLANG_WARN_EMPTY_BODY = YES; 682 | CLANG_WARN_ENUM_CONVERSION = YES; 683 | CLANG_WARN_INFINITE_RECURSION = YES; 684 | CLANG_WARN_INT_CONVERSION = YES; 685 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 686 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 687 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 688 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 689 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 690 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 691 | CLANG_WARN_STRICT_PROTOTYPES = YES; 692 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 693 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 694 | CLANG_WARN_UNREACHABLE_CODE = YES; 695 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 696 | COPY_PHASE_STRIP = NO; 697 | DEBUG_INFORMATION_FORMAT = dwarf; 698 | ENABLE_STRICT_OBJC_MSGSEND = YES; 699 | ENABLE_TESTABILITY = YES; 700 | GCC_C_LANGUAGE_STANDARD = gnu11; 701 | GCC_DYNAMIC_NO_PIC = NO; 702 | GCC_NO_COMMON_BLOCKS = YES; 703 | GCC_OPTIMIZATION_LEVEL = 0; 704 | GCC_PREPROCESSOR_DEFINITIONS = ( 705 | "DEBUG=1", 706 | "$(inherited)", 707 | ); 708 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 709 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 710 | GCC_WARN_UNDECLARED_SELECTOR = YES; 711 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 712 | GCC_WARN_UNUSED_FUNCTION = YES; 713 | GCC_WARN_UNUSED_VARIABLE = YES; 714 | IPHONEOS_DEPLOYMENT_TARGET = 13.0; 715 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 716 | MTL_FAST_MATH = YES; 717 | ONLY_ACTIVE_ARCH = YES; 718 | SDKROOT = iphoneos; 719 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG UITESTS"; 720 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 721 | }; 722 | name = Tests; 723 | }; 724 | A7DC23C12510B1AD0012EA25 /* Tests */ = { 725 | isa = XCBuildConfiguration; 726 | buildSettings = { 727 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 728 | CODE_SIGN_STYLE = Automatic; 729 | DEVELOPMENT_ASSET_PATHS = "\"TargetArchitecture/Preview Content\""; 730 | DEVELOPMENT_TEAM = P2M8L992PE; 731 | ENABLE_PREVIEWS = YES; 732 | INFOPLIST_FILE = TargetArchitecture/Info.plist; 733 | LD_RUNPATH_SEARCH_PATHS = ( 734 | "$(inherited)", 735 | "@executable_path/Frameworks", 736 | ); 737 | PRODUCT_BUNDLE_IDENTIFIER = ch.sbb.TargetArchitecture; 738 | PRODUCT_NAME = "$(TARGET_NAME)"; 739 | SWIFT_VERSION = 5.0; 740 | TARGETED_DEVICE_FAMILY = 1; 741 | }; 742 | name = Tests; 743 | }; 744 | A7DC23C22510B1AD0012EA25 /* Tests */ = { 745 | isa = XCBuildConfiguration; 746 | buildSettings = { 747 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 748 | BUNDLE_LOADER = "$(TEST_HOST)"; 749 | CODE_SIGN_STYLE = Automatic; 750 | DEVELOPMENT_TEAM = P2M8L992PE; 751 | INFOPLIST_FILE = TargetArchitectureTests/Info.plist; 752 | IPHONEOS_DEPLOYMENT_TARGET = 13.7; 753 | LD_RUNPATH_SEARCH_PATHS = ( 754 | "$(inherited)", 755 | "@executable_path/Frameworks", 756 | "@loader_path/Frameworks", 757 | ); 758 | PRODUCT_BUNDLE_IDENTIFIER = ch.sbb.TargetArchitectureTests; 759 | PRODUCT_NAME = "$(TARGET_NAME)"; 760 | SWIFT_VERSION = 5.0; 761 | TARGETED_DEVICE_FAMILY = "1,2"; 762 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TargetArchitecture.app/TargetArchitecture"; 763 | }; 764 | name = Tests; 765 | }; 766 | A7DC23C32510B1AD0012EA25 /* Tests */ = { 767 | isa = XCBuildConfiguration; 768 | buildSettings = { 769 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 770 | CODE_SIGN_STYLE = Automatic; 771 | DEVELOPMENT_TEAM = P2M8L992PE; 772 | INFOPLIST_FILE = TargetArchitectureUITests/Info.plist; 773 | LD_RUNPATH_SEARCH_PATHS = ( 774 | "$(inherited)", 775 | "@executable_path/Frameworks", 776 | "@loader_path/Frameworks", 777 | ); 778 | PRODUCT_BUNDLE_IDENTIFIER = ch.sbb.TargetArchitectureUITests; 779 | PRODUCT_NAME = "$(TARGET_NAME)"; 780 | SWIFT_VERSION = 5.0; 781 | TARGETED_DEVICE_FAMILY = "1,2"; 782 | TEST_TARGET_NAME = TargetArchitecture; 783 | }; 784 | name = Tests; 785 | }; 786 | /* End XCBuildConfiguration section */ 787 | 788 | /* Begin XCConfigurationList section */ 789 | A7DC236A250FA7000012EA25 /* Build configuration list for PBXProject "TargetArchitecture" */ = { 790 | isa = XCConfigurationList; 791 | buildConfigurations = ( 792 | A7DC2397250FA7020012EA25 /* Debug */, 793 | A7DC23C02510B1AD0012EA25 /* Tests */, 794 | A7DC2398250FA7020012EA25 /* Release */, 795 | ); 796 | defaultConfigurationIsVisible = 0; 797 | defaultConfigurationName = Release; 798 | }; 799 | A7DC2399250FA7020012EA25 /* Build configuration list for PBXNativeTarget "TargetArchitecture" */ = { 800 | isa = XCConfigurationList; 801 | buildConfigurations = ( 802 | A7DC239A250FA7020012EA25 /* Debug */, 803 | A7DC23C12510B1AD0012EA25 /* Tests */, 804 | A7DC239B250FA7020012EA25 /* Release */, 805 | ); 806 | defaultConfigurationIsVisible = 0; 807 | defaultConfigurationName = Release; 808 | }; 809 | A7DC239C250FA7020012EA25 /* Build configuration list for PBXNativeTarget "TargetArchitectureTests" */ = { 810 | isa = XCConfigurationList; 811 | buildConfigurations = ( 812 | A7DC239D250FA7020012EA25 /* Debug */, 813 | A7DC23C22510B1AD0012EA25 /* Tests */, 814 | A7DC239E250FA7020012EA25 /* Release */, 815 | ); 816 | defaultConfigurationIsVisible = 0; 817 | defaultConfigurationName = Release; 818 | }; 819 | A7DC239F250FA7020012EA25 /* Build configuration list for PBXNativeTarget "TargetArchitectureUITests" */ = { 820 | isa = XCConfigurationList; 821 | buildConfigurations = ( 822 | A7DC23A0250FA7020012EA25 /* Debug */, 823 | A7DC23C32510B1AD0012EA25 /* Tests */, 824 | A7DC23A1250FA7020012EA25 /* Release */, 825 | ); 826 | defaultConfigurationIsVisible = 0; 827 | defaultConfigurationName = Release; 828 | }; 829 | /* End XCConfigurationList section */ 830 | }; 831 | rootObject = A7DC2367250FA7000012EA25 /* Project object */; 832 | } 833 | -------------------------------------------------------------------------------- /TargetArchitecture.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /TargetArchitecture.xcodeproj/xcshareddata/xcschemes/TargetArchitecture.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 39 | 40 | 41 | 42 | 45 | 51 | 52 | 53 | 56 | 62 | 63 | 64 | 65 | 66 | 76 | 78 | 84 | 85 | 86 | 87 | 93 | 95 | 101 | 102 | 103 | 104 | 106 | 107 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /TargetArchitecture/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import UIKit 6 | 7 | @UIApplicationMain 8 | class AppDelegate: UIResponder, UIApplicationDelegate { 9 | 10 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 11 | // Override point for customization after application launch. 12 | return true 13 | } 14 | 15 | // MARK: UISceneSession Lifecycle 16 | 17 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 18 | // Called when a new scene session is being created. 19 | // Use this method to select a configuration to create the new scene with. 20 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 21 | } 22 | 23 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 24 | // Called when the user discards a scene session. 25 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 26 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /TargetArchitecture/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 | -------------------------------------------------------------------------------- /TargetArchitecture/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TargetArchitecture/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 | -------------------------------------------------------------------------------- /TargetArchitecture/Fakes/FakeViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import Foundation 6 | import Combine 7 | 8 | class FakeViewModel: ViewModelProtocol { 9 | 10 | @Published var value: Int 11 | @Published var isCounterStarted: Bool 12 | 13 | init(value: Int, isCounterStarted: Bool) { 14 | self.value = value 15 | self.isCounterStarted = isCounterStarted 16 | } 17 | 18 | func reset() { 19 | self.value = -1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /TargetArchitecture/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UIApplicationSceneManifest 24 | 25 | UIApplicationSupportsMultipleScenes 26 | 27 | UISceneConfigurations 28 | 29 | UIWindowSceneSessionRoleApplication 30 | 31 | 32 | UISceneConfigurationName 33 | Default Configuration 34 | UISceneDelegateClassName 35 | $(PRODUCT_MODULE_NAME).SceneDelegate 36 | 37 | 38 | 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | 50 | UISupportedInterfaceOrientations~ipad 51 | 52 | UIInterfaceOrientationPortrait 53 | UIInterfaceOrientationPortraitUpsideDown 54 | UIInterfaceOrientationLandscapeLeft 55 | UIInterfaceOrientationLandscapeRight 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /TargetArchitecture/Models/Clock/Clock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import Foundation 6 | import Combine 7 | 8 | class Clock: ClockProtocol { 9 | 10 | var clock: AnyPublisher { 11 | clockMulticaster.eraseToAnyPublisher() 12 | } 13 | private let clockMulticaster = PassthroughSubject() 14 | private var timerSubscription: Cancellable? 15 | 16 | func startClock() { 17 | timerSubscription = Timer.publish(every: 1.0, on: .main, in: .common) 18 | .autoconnect() 19 | .setFailureType(to: ClockError.self) 20 | .multicast(subject: clockMulticaster) 21 | .connect() 22 | } 23 | 24 | func stopClock() { 25 | timerSubscription?.cancel() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /TargetArchitecture/Models/Clock/ClockError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import Foundation 6 | 7 | enum ClockError: Error { 8 | 9 | case invalid 10 | } 11 | -------------------------------------------------------------------------------- /TargetArchitecture/Models/Clock/ClockProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import Foundation 6 | import Combine 7 | 8 | protocol ClockProtocol { 9 | 10 | var clock: AnyPublisher { get } 11 | 12 | func startClock() 13 | func stopClock() 14 | } 15 | -------------------------------------------------------------------------------- /TargetArchitecture/Models/Counter/Counter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import Foundation 6 | import Combine 7 | 8 | class Counter: CounterProtocol { 9 | 10 | var counter: AnyPublisher { 11 | counterValue.eraseToAnyPublisher() 12 | } 13 | var isCounterStarted = false { 14 | didSet { 15 | if isCounterStarted { 16 | service.startClock() 17 | } else { 18 | service.stopClock() 19 | } 20 | } 21 | } 22 | 23 | private let service: ClockProtocol 24 | private var counterValue = CurrentValueSubject(0) 25 | private var clockSubscription: Cancellable! 26 | 27 | init(service: ClockProtocol = Clock()) { 28 | self.service = service 29 | clockSubscription = service.clock.sink { completion in 30 | print("Clock completion: \(completion)") 31 | self.counterValue.value = 0 32 | } receiveValue: { _ in 33 | self.counterValue.value += 1 34 | } 35 | } 36 | 37 | func resetCounter() { 38 | counterValue.value = 0 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /TargetArchitecture/Models/Counter/CounterProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import Foundation 6 | import Combine 7 | 8 | protocol CounterProtocol { 9 | 10 | var counter: AnyPublisher { get } 11 | var isCounterStarted: Bool { get set } 12 | 13 | func resetCounter() 14 | } 15 | -------------------------------------------------------------------------------- /TargetArchitecture/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /TargetArchitecture/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import UIKit 6 | import SwiftUI 7 | 8 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 9 | 10 | var window: UIWindow? 11 | 12 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 13 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 14 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 15 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 16 | 17 | #if UITESTS 18 | let contentView: AnyView 19 | let scenarioName = ProcessInfo.processInfo.environment["UI_TEST_SCENARIO"] 20 | switch scenarioName { 21 | case "scenario1": 22 | contentView = AnyView(ContentView().environmentObject(FakeViewModel(value: 123, isCounterStarted: true))) 23 | case "scenario2": 24 | contentView = AnyView(ContentView().environmentObject(FakeViewModel(value: 456, isCounterStarted: false))) 25 | default: 26 | return 27 | } 28 | #else 29 | let contentView = ContentView().environmentObject(ViewModel()) 30 | #endif 31 | 32 | // Use a UIHostingController as window root view controller. 33 | if let windowScene = scene as? UIWindowScene { 34 | let window = UIWindow(windowScene: windowScene) 35 | window.rootViewController = UIHostingController(rootView: contentView) 36 | self.window = window 37 | window.makeKeyAndVisible() 38 | } 39 | } 40 | 41 | func sceneDidDisconnect(_ scene: UIScene) { 42 | // Called as the scene is being released by the system. 43 | // This occurs shortly after the scene enters the background, or when its session is discarded. 44 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 45 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 46 | } 47 | 48 | func sceneDidBecomeActive(_ scene: UIScene) { 49 | // Called when the scene has moved from an inactive state to an active state. 50 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 51 | } 52 | 53 | func sceneWillResignActive(_ scene: UIScene) { 54 | // Called when the scene will move from an active state to an inactive state. 55 | // This may occur due to temporary interruptions (ex. an incoming phone call). 56 | } 57 | 58 | func sceneWillEnterForeground(_ scene: UIScene) { 59 | // Called as the scene transitions from the background to the foreground. 60 | // Use this method to undo the changes made on entering the background. 61 | } 62 | 63 | func sceneDidEnterBackground(_ scene: UIScene) { 64 | // Called as the scene transitions from the foreground to the background. 65 | // Use this method to save data, release shared resources, and store enough scene-specific state information 66 | // to restore the scene back to its current state. 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /TargetArchitecture/ViewModels/ViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import Foundation 6 | import Combine 7 | 8 | class ViewModel: ViewModelProtocol { 9 | 10 | @Published var value: Int = 0 11 | @Published var isCounterStarted: Bool = false 12 | 13 | private var model: CounterProtocol 14 | private var counterValueSubscription: Cancellable! 15 | private var counterStartedSubscription: Cancellable! 16 | 17 | init(model: CounterProtocol = Counter()) { 18 | self.model = model 19 | counterValueSubscription = model.counter.receive(on: DispatchQueue.main).assign(to: \.value, on: self) 20 | counterStartedSubscription = $isCounterStarted.sink(receiveValue: { self.model.isCounterStarted = $0 }) 21 | } 22 | 23 | func reset() { 24 | model.resetCounter() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TargetArchitecture/ViewModels/ViewModelProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import Foundation 6 | 7 | protocol ViewModelProtocol: ObservableObject { 8 | 9 | var value: Int { get set } 10 | var isCounterStarted: Bool { get set } 11 | 12 | func reset() 13 | } 14 | -------------------------------------------------------------------------------- /TargetArchitecture/Views/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import SwiftUI 6 | 7 | struct ContentView: View { 8 | 9 | @EnvironmentObject var model: Model 10 | 11 | var body: some View { 12 | VStack(spacing: 8) { 13 | Toggle(isOn: $model.isCounterStarted) { 14 | Text("Start timer") 15 | } 16 | Text("Value: \(model.value)").accessibility(identifier: "counter_value") 17 | Button(action: model.reset) { 18 | Text("Reset") 19 | } 20 | } 21 | .padding(8) 22 | } 23 | } 24 | 25 | struct ContentView_Previews: PreviewProvider { 26 | static var previews: some View { 27 | Group { 28 | ContentView().environmentObject(FakeViewModel(value: 123, isCounterStarted: true)) 29 | .previewDisplayName("ON 123") 30 | ContentView().environmentObject(FakeViewModel(value: 456, isCounterStarted: false)) 31 | .previewDisplayName("OFF 456") 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /TargetArchitectureTests/ClockTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import XCTest 6 | import Combine 7 | @testable import TargetArchitecture 8 | 9 | class ClockTests: XCTestCase { 10 | 11 | private var clock: Clock! 12 | 13 | override func setUp() { 14 | clock = Clock() 15 | } 16 | 17 | func testClockDoesNothingAfterInit() { 18 | let expectation = self.expectation(description: "wait for service timer") 19 | 20 | let sub = clock.clock.sink(receiveCompletion: { _ in 21 | XCTFail() 22 | }, receiveValue: { value in 23 | XCTFail() 24 | }) 25 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 26 | expectation.fulfill() 27 | } 28 | 29 | waitForExpectations(timeout: 5.0) { _ in 30 | sub.cancel() 31 | } 32 | } 33 | 34 | func testClockSendEverySecondsWhenStarted() { 35 | let expectation = self.expectation(description: "wait for service timer") 36 | 37 | var i = 0 38 | let start = Date() 39 | let sub = clock.clock.sink(receiveCompletion: { _ in 40 | XCTFail() 41 | }, receiveValue: { value in 42 | i += 1 43 | switch i { 44 | case 1: 45 | XCTAssertEqual(start.timeIntervalSinceNow, -1.0, accuracy: 0.5) 46 | case 2: 47 | XCTAssertEqual(start.timeIntervalSinceNow, -2.0, accuracy: 0.5) 48 | case 3: 49 | XCTAssertEqual(start.timeIntervalSinceNow, -3.0, accuracy: 0.5) 50 | expectation.fulfill() 51 | default: 52 | XCTFail() 53 | } 54 | }) 55 | clock.startClock() 56 | 57 | waitForExpectations(timeout: 5.0) { _ in 58 | sub.cancel() 59 | } 60 | } 61 | 62 | func testClockStopsSendingWhenStopped() { 63 | let expectation = self.expectation(description: "wait for service timer") 64 | 65 | var i = 0 66 | let start = Date() 67 | let sub = clock.clock.sink(receiveCompletion: { _ in 68 | XCTFail() 69 | }, receiveValue: { value in 70 | i += 1 71 | switch i { 72 | case 1: 73 | XCTAssertEqual(start.timeIntervalSinceNow, -1.0, accuracy: 0.5) 74 | self.clock.stopClock() 75 | default: 76 | XCTFail() 77 | } 78 | }) 79 | clock.startClock() 80 | DispatchQueue.main.asyncAfter(deadline: .now() + 3) { 81 | expectation.fulfill() 82 | } 83 | 84 | waitForExpectations(timeout: 5.0) { _ in 85 | sub.cancel() 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /TargetArchitectureTests/CounterTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import XCTest 6 | import Combine 7 | @testable import TargetArchitecture 8 | 9 | class CounterTests: XCTestCase { 10 | 11 | private var counter: Counter! 12 | private var fakeClock: FakeClock! 13 | 14 | override func setUp() { 15 | fakeClock = FakeClock() 16 | counter = Counter(service: fakeClock) 17 | } 18 | 19 | func testCounterIsInitiallyStopped() { 20 | XCTAssertFalse(counter.isCounterStarted) 21 | } 22 | 23 | func testCounterIsInitiallySetToZero() { 24 | let expectation = self.expectation(description: "wait for counter") 25 | 26 | var i = 0 27 | let sub = counter.counter.sink { value in 28 | i += 1 29 | switch i { 30 | case 1: 31 | XCTAssertEqual(value, 0) 32 | expectation.fulfill() 33 | default: 34 | XCTFail() 35 | } 36 | } 37 | 38 | waitForExpectations(timeout: 5.0) { _ in 39 | sub.cancel() 40 | } 41 | } 42 | 43 | func testStartingCounterStartsClockService() { 44 | counter.isCounterStarted = true 45 | 46 | XCTAssertTrue(fakeClock.clockWasStarted) 47 | } 48 | 49 | func testReceivingAClockEventIncrementsCounter() { 50 | let expectation = self.expectation(description: "wait for counter") 51 | 52 | var i = 0 53 | let sub = counter.counter.sink { value in 54 | i += 1 55 | switch i { 56 | case 1: 57 | XCTAssertEqual(value, 0) 58 | self.fakeClock.clockSubject.send(Date()) 59 | case 2: 60 | XCTAssertEqual(value, 1) 61 | expectation.fulfill() 62 | default: 63 | XCTFail() 64 | } 65 | } 66 | 67 | waitForExpectations(timeout: 5.0) { _ in 68 | sub.cancel() 69 | } 70 | } 71 | 72 | func testStoppingCounterStopsClockService() { 73 | counter.isCounterStarted = false 74 | 75 | XCTAssertTrue(fakeClock.clockWasStopped) 76 | } 77 | 78 | func testResetingCounterSetValueToZero() { 79 | let expectation = self.expectation(description: "wait for counter") 80 | 81 | var i = 0 82 | let sub = counter.counter.sink { value in 83 | i += 1 84 | switch i { 85 | case 1: 86 | XCTAssertEqual(value, 0) 87 | self.counter.resetCounter() 88 | case 2: 89 | XCTAssertEqual(value, 0) 90 | expectation.fulfill() 91 | default: 92 | XCTFail() 93 | } 94 | } 95 | 96 | waitForExpectations(timeout: 5.0) { _ in 97 | sub.cancel() 98 | } 99 | } 100 | 101 | func testReceivingAClockErrorSetCounterToZero() { 102 | let expectation = self.expectation(description: "wait for counter") 103 | 104 | var i = 0 105 | let sub = counter.counter.sink { value in 106 | i += 1 107 | switch i { 108 | case 1: 109 | XCTAssertEqual(value, 0) 110 | self.fakeClock.clockSubject.send(Date()) 111 | case 2: 112 | XCTAssertEqual(value, 1) 113 | self.fakeClock.clockSubject.send(completion: .failure(.invalid)) 114 | case 3: 115 | XCTAssertEqual(value, 0) 116 | expectation.fulfill() 117 | default: 118 | XCTFail() 119 | } 120 | } 121 | 122 | waitForExpectations(timeout: 5.0) { _ in 123 | sub.cancel() 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /TargetArchitectureTests/Fakes/FakeClock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import Foundation 6 | import Combine 7 | @testable import TargetArchitecture 8 | 9 | class FakeClock: ClockProtocol { 10 | 11 | let clockSubject = PassthroughSubject() 12 | var clock: AnyPublisher { 13 | return clockSubject.eraseToAnyPublisher() 14 | } 15 | 16 | var clockWasStarted = false 17 | func startClock() { 18 | clockWasStarted = true 19 | } 20 | 21 | var clockWasStopped = false 22 | func stopClock() { 23 | clockWasStopped = true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /TargetArchitectureTests/Fakes/FakeCounter.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import Foundation 6 | import Combine 7 | @testable import TargetArchitecture 8 | 9 | class FakeCounter: CounterProtocol { 10 | 11 | let counterSubject = PassthroughSubject() 12 | var counter: AnyPublisher { 13 | counterSubject.eraseToAnyPublisher() 14 | } 15 | 16 | var isCounterStarted: Bool 17 | var resetWasCalled = false 18 | 19 | init(isCounterStarted: Bool) { 20 | self.isCounterStarted = isCounterStarted 21 | } 22 | 23 | func resetCounter() { 24 | resetWasCalled = true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /TargetArchitectureTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TargetArchitectureTests/ViewModelTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import XCTest 6 | import Combine 7 | @testable import TargetArchitecture 8 | 9 | class ViewModelTests: XCTestCase { 10 | 11 | private var viewModel: ViewModel! 12 | private var fakeCounter: FakeCounter! 13 | 14 | override func setUp() { 15 | fakeCounter = FakeCounter(isCounterStarted: false) 16 | viewModel = ViewModel(model: fakeCounter) 17 | } 18 | 19 | func testViewModelInit() { 20 | XCTAssertEqual(viewModel.value, 0) 21 | XCTAssertFalse(viewModel.isCounterStarted) 22 | } 23 | 24 | func testCounterModelUpdatesValue() { 25 | let expectation = self.expectation(description: "wait for value update") 26 | 27 | var i = 0 28 | let sub = viewModel.$value.sink { value in 29 | i += 1 30 | switch i { 31 | case 1: 32 | XCTAssertEqual(value, 0) 33 | self.fakeCounter.counterSubject.send(5) 34 | case 2: 35 | XCTAssertEqual(value, 5) 36 | expectation.fulfill() 37 | default: 38 | XCTFail() 39 | } 40 | } 41 | 42 | waitForExpectations(timeout: 5.0) { _ in 43 | sub.cancel() 44 | } 45 | } 46 | 47 | func testIsCounterStartedForwardsToCounterModel() { 48 | viewModel.isCounterStarted = true 49 | 50 | XCTAssertTrue(fakeCounter.isCounterStarted) 51 | viewModel.isCounterStarted = false 52 | 53 | XCTAssertFalse(fakeCounter.isCounterStarted) 54 | } 55 | 56 | func testResetForwardsToCounterModel() { 57 | viewModel.reset() 58 | 59 | XCTAssertTrue(fakeCounter.resetWasCalled) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /TargetArchitectureUITests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /TargetArchitectureUITests/PocViewModelUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Schweizerische Bundesbahnen SBB, 2020. 3 | // 4 | 5 | import XCTest 6 | 7 | class PocViewModelUITests: XCTestCase { 8 | 9 | override func setUpWithError() throws { 10 | // Put setup code here. This method is called before the invocation of each test method in the class. 11 | 12 | // In UI tests it is usually best to stop immediately when a failure occurs. 13 | continueAfterFailure = false 14 | 15 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 16 | } 17 | 18 | func testScenarioOne() throws { 19 | let app = XCUIApplication() 20 | app.launchEnvironment.updateValue("scenario1", forKey: "UI_TEST_SCENARIO") 21 | app.launch() 22 | 23 | XCTAssertEqual(app.switches["Start timer"].value as? String, "1") 24 | app.switches["Start timer"].tap() 25 | XCTAssertEqual(app.switches["Start timer"].value as? String, "0") 26 | 27 | XCTAssertEqual(app.staticTexts["counter_value"].label, "Value: 123") 28 | app.buttons["Reset"].tap() 29 | XCTAssertEqual(app.staticTexts["counter_value"].label, "Value: -1") 30 | } 31 | 32 | func testScenarioTwo() throws { 33 | let app = XCUIApplication() 34 | app.launchEnvironment.updateValue("scenario2", forKey: "UI_TEST_SCENARIO") 35 | app.launch() 36 | 37 | XCTAssertEqual(app.switches["Start timer"].value as? String, "0") 38 | app.switches["Start timer"].tap() 39 | XCTAssertEqual(app.switches["Start timer"].value as? String, "1") 40 | 41 | XCTAssertEqual(app.staticTexts["counter_value"].label, "Value: 456") 42 | app.buttons["Reset"].tap() 43 | XCTAssertEqual(app.staticTexts["counter_value"].label, "Value: -1") 44 | } 45 | } 46 | --------------------------------------------------------------------------------