├── README.md ├── SplashCardTest.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── jmb.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── SplashCardTest ├── AppDelegate.swift ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ └── LaunchScreen.storyboard ├── ContentView.swift ├── Info.plist ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── SceneDelegate.swift ├── SplashExample01.swift ├── SplashExample02.swift ├── SplashShape.swift └── SplashView.swift ├── SplashCardTestTests ├── Info.plist └── SplashCardTestTests.swift └── images └── SplashView_Example.gif /README.md: -------------------------------------------------------------------------------- 1 | # SplashView 2 | ### Color Changing Animations using SwiftUI 3 | 4 | ![Stars](https://img.shields.io/github/stars/jboullianne/SplashView?style=social) ![Followers](https://img.shields.io/github/followers/jboullianne?style=social) ![Forks](https://img.shields.io/github/forks/jboullianne/SplashView?style=social) 5 | 6 | Written for [TrailingClosure.com](https://trailingclosure.com/swiftui-animating-color-changes/). 7 | 8 | > Time to spice up your color changes! SplashView uses `Path`s and `AnimatableData` to animate color changes on SwiftUI Views! 9 | 10 | 11 | ![Example](images/SplashView_Example.gif?v=4&s=200) 12 | 13 | ## Usage 14 | 15 | ```swift 16 | 17 | // The SplashView's take in a SplashAnimation enum to decide which animation they perform on color change. 18 | SplashView(animationType: .angle(Angle(degrees: 45)), color: self.colors[self.index]) 19 | 20 | SplashView(animationType: .angle(Angle(degrees: 135)), color: self.colors[self.index]) 21 | 22 | SplashView(animationType: .angle(Angle(degrees: 315)), color: self.colors[self.index]) 23 | 24 | SplashView(animationType: .angle(Angle(degrees: 225)), color: self.colors[self.index]) 25 | 26 | SplashView(animationType: .topToBottom, color: self.colors[self.index]) 27 | 28 | SplashView(animationType: .circle, color: self.colors[self.index]) 29 | 30 | // Change the `index` counter on user interaction to see the View's change color. 31 | 32 | ``` 33 | -------------------------------------------------------------------------------- /SplashCardTest.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8C1DEE772453B2710013344D /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1DEE762453B2710013344D /* AppDelegate.swift */; }; 11 | 8C1DEE792453B2710013344D /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1DEE782453B2710013344D /* SceneDelegate.swift */; }; 12 | 8C1DEE7B2453B2710013344D /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1DEE7A2453B2710013344D /* ContentView.swift */; }; 13 | 8C1DEE7D2453B2730013344D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8C1DEE7C2453B2730013344D /* Assets.xcassets */; }; 14 | 8C1DEE802453B2730013344D /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8C1DEE7F2453B2730013344D /* Preview Assets.xcassets */; }; 15 | 8C1DEE832453B2730013344D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 8C1DEE812453B2730013344D /* LaunchScreen.storyboard */; }; 16 | 8C1DEE8E2453B2730013344D /* SplashCardTestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1DEE8D2453B2730013344D /* SplashCardTestTests.swift */; }; 17 | 8C1DEE992453B27F0013344D /* SplashView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1DEE982453B27F0013344D /* SplashView.swift */; }; 18 | 8C1DEE9B2453CC2A0013344D /* SplashShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1DEE9A2453CC2A0013344D /* SplashShape.swift */; }; 19 | 8C1DEE9D2454E66C0013344D /* SplashExample01.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1DEE9C2454E66C0013344D /* SplashExample01.swift */; }; 20 | 8C1DEE9F2454EF890013344D /* SplashExample02.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C1DEE9E2454EF890013344D /* SplashExample02.swift */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXContainerItemProxy section */ 24 | 8C1DEE8A2453B2730013344D /* PBXContainerItemProxy */ = { 25 | isa = PBXContainerItemProxy; 26 | containerPortal = 8C1DEE6B2453B2710013344D /* Project object */; 27 | proxyType = 1; 28 | remoteGlobalIDString = 8C1DEE722453B2710013344D; 29 | remoteInfo = SplashCardTest; 30 | }; 31 | /* End PBXContainerItemProxy section */ 32 | 33 | /* Begin PBXFileReference section */ 34 | 8C1DEE732453B2710013344D /* SplashCardTest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SplashCardTest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 8C1DEE762453B2710013344D /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 36 | 8C1DEE782453B2710013344D /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 37 | 8C1DEE7A2453B2710013344D /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 38 | 8C1DEE7C2453B2730013344D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 39 | 8C1DEE7F2453B2730013344D /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 40 | 8C1DEE822453B2730013344D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 41 | 8C1DEE842453B2730013344D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 42 | 8C1DEE892453B2730013344D /* SplashCardTestTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SplashCardTestTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | 8C1DEE8D2453B2730013344D /* SplashCardTestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashCardTestTests.swift; sourceTree = ""; }; 44 | 8C1DEE8F2453B2730013344D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 45 | 8C1DEE982453B27F0013344D /* SplashView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashView.swift; sourceTree = ""; }; 46 | 8C1DEE9A2453CC2A0013344D /* SplashShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashShape.swift; sourceTree = ""; }; 47 | 8C1DEE9C2454E66C0013344D /* SplashExample01.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashExample01.swift; sourceTree = ""; }; 48 | 8C1DEE9E2454EF890013344D /* SplashExample02.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplashExample02.swift; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | 8C1DEE702453B2710013344D /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | 8C1DEE862453B2730013344D /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | 8C1DEE6A2453B2710013344D = { 70 | isa = PBXGroup; 71 | children = ( 72 | 8C1DEE752453B2710013344D /* SplashCardTest */, 73 | 8C1DEE8C2453B2730013344D /* SplashCardTestTests */, 74 | 8C1DEE742453B2710013344D /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | 8C1DEE742453B2710013344D /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 8C1DEE732453B2710013344D /* SplashCardTest.app */, 82 | 8C1DEE892453B2730013344D /* SplashCardTestTests.xctest */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | 8C1DEE752453B2710013344D /* SplashCardTest */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 8C1DEE762453B2710013344D /* AppDelegate.swift */, 91 | 8C1DEE782453B2710013344D /* SceneDelegate.swift */, 92 | 8C1DEE7A2453B2710013344D /* ContentView.swift */, 93 | 8C1DEE7C2453B2730013344D /* Assets.xcassets */, 94 | 8C1DEE812453B2730013344D /* LaunchScreen.storyboard */, 95 | 8C1DEE842453B2730013344D /* Info.plist */, 96 | 8C1DEE7E2453B2730013344D /* Preview Content */, 97 | 8C1DEE982453B27F0013344D /* SplashView.swift */, 98 | 8C1DEE9A2453CC2A0013344D /* SplashShape.swift */, 99 | 8C1DEE9C2454E66C0013344D /* SplashExample01.swift */, 100 | 8C1DEE9E2454EF890013344D /* SplashExample02.swift */, 101 | ); 102 | path = SplashCardTest; 103 | sourceTree = ""; 104 | }; 105 | 8C1DEE7E2453B2730013344D /* Preview Content */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 8C1DEE7F2453B2730013344D /* Preview Assets.xcassets */, 109 | ); 110 | path = "Preview Content"; 111 | sourceTree = ""; 112 | }; 113 | 8C1DEE8C2453B2730013344D /* SplashCardTestTests */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 8C1DEE8D2453B2730013344D /* SplashCardTestTests.swift */, 117 | 8C1DEE8F2453B2730013344D /* Info.plist */, 118 | ); 119 | path = SplashCardTestTests; 120 | sourceTree = ""; 121 | }; 122 | /* End PBXGroup section */ 123 | 124 | /* Begin PBXNativeTarget section */ 125 | 8C1DEE722453B2710013344D /* SplashCardTest */ = { 126 | isa = PBXNativeTarget; 127 | buildConfigurationList = 8C1DEE922453B2730013344D /* Build configuration list for PBXNativeTarget "SplashCardTest" */; 128 | buildPhases = ( 129 | 8C1DEE6F2453B2710013344D /* Sources */, 130 | 8C1DEE702453B2710013344D /* Frameworks */, 131 | 8C1DEE712453B2710013344D /* Resources */, 132 | ); 133 | buildRules = ( 134 | ); 135 | dependencies = ( 136 | ); 137 | name = SplashCardTest; 138 | productName = SplashCardTest; 139 | productReference = 8C1DEE732453B2710013344D /* SplashCardTest.app */; 140 | productType = "com.apple.product-type.application"; 141 | }; 142 | 8C1DEE882453B2730013344D /* SplashCardTestTests */ = { 143 | isa = PBXNativeTarget; 144 | buildConfigurationList = 8C1DEE952453B2730013344D /* Build configuration list for PBXNativeTarget "SplashCardTestTests" */; 145 | buildPhases = ( 146 | 8C1DEE852453B2730013344D /* Sources */, 147 | 8C1DEE862453B2730013344D /* Frameworks */, 148 | 8C1DEE872453B2730013344D /* Resources */, 149 | ); 150 | buildRules = ( 151 | ); 152 | dependencies = ( 153 | 8C1DEE8B2453B2730013344D /* PBXTargetDependency */, 154 | ); 155 | name = SplashCardTestTests; 156 | productName = SplashCardTestTests; 157 | productReference = 8C1DEE892453B2730013344D /* SplashCardTestTests.xctest */; 158 | productType = "com.apple.product-type.bundle.unit-test"; 159 | }; 160 | /* End PBXNativeTarget section */ 161 | 162 | /* Begin PBXProject section */ 163 | 8C1DEE6B2453B2710013344D /* Project object */ = { 164 | isa = PBXProject; 165 | attributes = { 166 | LastSwiftUpdateCheck = 1120; 167 | LastUpgradeCheck = 1120; 168 | ORGANIZATIONNAME = TrailingClosure; 169 | TargetAttributes = { 170 | 8C1DEE722453B2710013344D = { 171 | CreatedOnToolsVersion = 11.2; 172 | }; 173 | 8C1DEE882453B2730013344D = { 174 | CreatedOnToolsVersion = 11.2; 175 | TestTargetID = 8C1DEE722453B2710013344D; 176 | }; 177 | }; 178 | }; 179 | buildConfigurationList = 8C1DEE6E2453B2710013344D /* Build configuration list for PBXProject "SplashCardTest" */; 180 | compatibilityVersion = "Xcode 9.3"; 181 | developmentRegion = en; 182 | hasScannedForEncodings = 0; 183 | knownRegions = ( 184 | en, 185 | Base, 186 | ); 187 | mainGroup = 8C1DEE6A2453B2710013344D; 188 | productRefGroup = 8C1DEE742453B2710013344D /* Products */; 189 | projectDirPath = ""; 190 | projectRoot = ""; 191 | targets = ( 192 | 8C1DEE722453B2710013344D /* SplashCardTest */, 193 | 8C1DEE882453B2730013344D /* SplashCardTestTests */, 194 | ); 195 | }; 196 | /* End PBXProject section */ 197 | 198 | /* Begin PBXResourcesBuildPhase section */ 199 | 8C1DEE712453B2710013344D /* Resources */ = { 200 | isa = PBXResourcesBuildPhase; 201 | buildActionMask = 2147483647; 202 | files = ( 203 | 8C1DEE832453B2730013344D /* LaunchScreen.storyboard in Resources */, 204 | 8C1DEE802453B2730013344D /* Preview Assets.xcassets in Resources */, 205 | 8C1DEE7D2453B2730013344D /* Assets.xcassets in Resources */, 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | }; 209 | 8C1DEE872453B2730013344D /* Resources */ = { 210 | isa = PBXResourcesBuildPhase; 211 | buildActionMask = 2147483647; 212 | files = ( 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | }; 216 | /* End PBXResourcesBuildPhase section */ 217 | 218 | /* Begin PBXSourcesBuildPhase section */ 219 | 8C1DEE6F2453B2710013344D /* Sources */ = { 220 | isa = PBXSourcesBuildPhase; 221 | buildActionMask = 2147483647; 222 | files = ( 223 | 8C1DEE9D2454E66C0013344D /* SplashExample01.swift in Sources */, 224 | 8C1DEE9B2453CC2A0013344D /* SplashShape.swift in Sources */, 225 | 8C1DEE992453B27F0013344D /* SplashView.swift in Sources */, 226 | 8C1DEE9F2454EF890013344D /* SplashExample02.swift in Sources */, 227 | 8C1DEE772453B2710013344D /* AppDelegate.swift in Sources */, 228 | 8C1DEE792453B2710013344D /* SceneDelegate.swift in Sources */, 229 | 8C1DEE7B2453B2710013344D /* ContentView.swift in Sources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | 8C1DEE852453B2730013344D /* Sources */ = { 234 | isa = PBXSourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | 8C1DEE8E2453B2730013344D /* SplashCardTestTests.swift in Sources */, 238 | ); 239 | runOnlyForDeploymentPostprocessing = 0; 240 | }; 241 | /* End PBXSourcesBuildPhase section */ 242 | 243 | /* Begin PBXTargetDependency section */ 244 | 8C1DEE8B2453B2730013344D /* PBXTargetDependency */ = { 245 | isa = PBXTargetDependency; 246 | target = 8C1DEE722453B2710013344D /* SplashCardTest */; 247 | targetProxy = 8C1DEE8A2453B2730013344D /* PBXContainerItemProxy */; 248 | }; 249 | /* End PBXTargetDependency section */ 250 | 251 | /* Begin PBXVariantGroup section */ 252 | 8C1DEE812453B2730013344D /* LaunchScreen.storyboard */ = { 253 | isa = PBXVariantGroup; 254 | children = ( 255 | 8C1DEE822453B2730013344D /* Base */, 256 | ); 257 | name = LaunchScreen.storyboard; 258 | sourceTree = ""; 259 | }; 260 | /* End PBXVariantGroup section */ 261 | 262 | /* Begin XCBuildConfiguration section */ 263 | 8C1DEE902453B2730013344D /* Debug */ = { 264 | isa = XCBuildConfiguration; 265 | buildSettings = { 266 | ALWAYS_SEARCH_USER_PATHS = NO; 267 | CLANG_ANALYZER_NONNULL = YES; 268 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 269 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 270 | CLANG_CXX_LIBRARY = "libc++"; 271 | CLANG_ENABLE_MODULES = YES; 272 | CLANG_ENABLE_OBJC_ARC = YES; 273 | CLANG_ENABLE_OBJC_WEAK = YES; 274 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 275 | CLANG_WARN_BOOL_CONVERSION = YES; 276 | CLANG_WARN_COMMA = YES; 277 | CLANG_WARN_CONSTANT_CONVERSION = YES; 278 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 279 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 280 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 281 | CLANG_WARN_EMPTY_BODY = YES; 282 | CLANG_WARN_ENUM_CONVERSION = YES; 283 | CLANG_WARN_INFINITE_RECURSION = YES; 284 | CLANG_WARN_INT_CONVERSION = YES; 285 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 286 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 287 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 288 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 289 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 290 | CLANG_WARN_STRICT_PROTOTYPES = YES; 291 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 292 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 293 | CLANG_WARN_UNREACHABLE_CODE = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | COPY_PHASE_STRIP = NO; 296 | DEBUG_INFORMATION_FORMAT = dwarf; 297 | ENABLE_STRICT_OBJC_MSGSEND = YES; 298 | ENABLE_TESTABILITY = YES; 299 | GCC_C_LANGUAGE_STANDARD = gnu11; 300 | GCC_DYNAMIC_NO_PIC = NO; 301 | GCC_NO_COMMON_BLOCKS = YES; 302 | GCC_OPTIMIZATION_LEVEL = 0; 303 | GCC_PREPROCESSOR_DEFINITIONS = ( 304 | "DEBUG=1", 305 | "$(inherited)", 306 | ); 307 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 308 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 309 | GCC_WARN_UNDECLARED_SELECTOR = YES; 310 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 311 | GCC_WARN_UNUSED_FUNCTION = YES; 312 | GCC_WARN_UNUSED_VARIABLE = YES; 313 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 314 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 315 | MTL_FAST_MATH = YES; 316 | ONLY_ACTIVE_ARCH = YES; 317 | SDKROOT = iphoneos; 318 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 319 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 320 | }; 321 | name = Debug; 322 | }; 323 | 8C1DEE912453B2730013344D /* Release */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ALWAYS_SEARCH_USER_PATHS = NO; 327 | CLANG_ANALYZER_NONNULL = YES; 328 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 329 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 330 | CLANG_CXX_LIBRARY = "libc++"; 331 | CLANG_ENABLE_MODULES = YES; 332 | CLANG_ENABLE_OBJC_ARC = YES; 333 | CLANG_ENABLE_OBJC_WEAK = YES; 334 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 335 | CLANG_WARN_BOOL_CONVERSION = YES; 336 | CLANG_WARN_COMMA = YES; 337 | CLANG_WARN_CONSTANT_CONVERSION = YES; 338 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 339 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 340 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 341 | CLANG_WARN_EMPTY_BODY = YES; 342 | CLANG_WARN_ENUM_CONVERSION = YES; 343 | CLANG_WARN_INFINITE_RECURSION = YES; 344 | CLANG_WARN_INT_CONVERSION = YES; 345 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 346 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 347 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 348 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 349 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 350 | CLANG_WARN_STRICT_PROTOTYPES = YES; 351 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 352 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 353 | CLANG_WARN_UNREACHABLE_CODE = YES; 354 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 355 | COPY_PHASE_STRIP = NO; 356 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 357 | ENABLE_NS_ASSERTIONS = NO; 358 | ENABLE_STRICT_OBJC_MSGSEND = YES; 359 | GCC_C_LANGUAGE_STANDARD = gnu11; 360 | GCC_NO_COMMON_BLOCKS = YES; 361 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 362 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 363 | GCC_WARN_UNDECLARED_SELECTOR = YES; 364 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 365 | GCC_WARN_UNUSED_FUNCTION = YES; 366 | GCC_WARN_UNUSED_VARIABLE = YES; 367 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 368 | MTL_ENABLE_DEBUG_INFO = NO; 369 | MTL_FAST_MATH = YES; 370 | SDKROOT = iphoneos; 371 | SWIFT_COMPILATION_MODE = wholemodule; 372 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 373 | VALIDATE_PRODUCT = YES; 374 | }; 375 | name = Release; 376 | }; 377 | 8C1DEE932453B2730013344D /* Debug */ = { 378 | isa = XCBuildConfiguration; 379 | buildSettings = { 380 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 381 | CODE_SIGN_STYLE = Automatic; 382 | DEVELOPMENT_ASSET_PATHS = "\"SplashCardTest/Preview Content\""; 383 | DEVELOPMENT_TEAM = W3U8G84792; 384 | ENABLE_PREVIEWS = YES; 385 | INFOPLIST_FILE = SplashCardTest/Info.plist; 386 | LD_RUNPATH_SEARCH_PATHS = ( 387 | "$(inherited)", 388 | "@executable_path/Frameworks", 389 | ); 390 | PRODUCT_BUNDLE_IDENTIFIER = com.trailingclosure.SplashCardTest; 391 | PRODUCT_NAME = "$(TARGET_NAME)"; 392 | SWIFT_VERSION = 5.0; 393 | TARGETED_DEVICE_FAMILY = "1,2"; 394 | }; 395 | name = Debug; 396 | }; 397 | 8C1DEE942453B2730013344D /* Release */ = { 398 | isa = XCBuildConfiguration; 399 | buildSettings = { 400 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 401 | CODE_SIGN_STYLE = Automatic; 402 | DEVELOPMENT_ASSET_PATHS = "\"SplashCardTest/Preview Content\""; 403 | DEVELOPMENT_TEAM = W3U8G84792; 404 | ENABLE_PREVIEWS = YES; 405 | INFOPLIST_FILE = SplashCardTest/Info.plist; 406 | LD_RUNPATH_SEARCH_PATHS = ( 407 | "$(inherited)", 408 | "@executable_path/Frameworks", 409 | ); 410 | PRODUCT_BUNDLE_IDENTIFIER = com.trailingclosure.SplashCardTest; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | SWIFT_VERSION = 5.0; 413 | TARGETED_DEVICE_FAMILY = "1,2"; 414 | }; 415 | name = Release; 416 | }; 417 | 8C1DEE962453B2730013344D /* Debug */ = { 418 | isa = XCBuildConfiguration; 419 | buildSettings = { 420 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 421 | BUNDLE_LOADER = "$(TEST_HOST)"; 422 | CODE_SIGN_STYLE = Automatic; 423 | DEVELOPMENT_TEAM = W3U8G84792; 424 | INFOPLIST_FILE = SplashCardTestTests/Info.plist; 425 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 426 | LD_RUNPATH_SEARCH_PATHS = ( 427 | "$(inherited)", 428 | "@executable_path/Frameworks", 429 | "@loader_path/Frameworks", 430 | ); 431 | PRODUCT_BUNDLE_IDENTIFIER = com.trailingclosure.SplashCardTestTests; 432 | PRODUCT_NAME = "$(TARGET_NAME)"; 433 | SWIFT_VERSION = 5.0; 434 | TARGETED_DEVICE_FAMILY = "1,2"; 435 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SplashCardTest.app/SplashCardTest"; 436 | }; 437 | name = Debug; 438 | }; 439 | 8C1DEE972453B2730013344D /* Release */ = { 440 | isa = XCBuildConfiguration; 441 | buildSettings = { 442 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 443 | BUNDLE_LOADER = "$(TEST_HOST)"; 444 | CODE_SIGN_STYLE = Automatic; 445 | DEVELOPMENT_TEAM = W3U8G84792; 446 | INFOPLIST_FILE = SplashCardTestTests/Info.plist; 447 | IPHONEOS_DEPLOYMENT_TARGET = 13.2; 448 | LD_RUNPATH_SEARCH_PATHS = ( 449 | "$(inherited)", 450 | "@executable_path/Frameworks", 451 | "@loader_path/Frameworks", 452 | ); 453 | PRODUCT_BUNDLE_IDENTIFIER = com.trailingclosure.SplashCardTestTests; 454 | PRODUCT_NAME = "$(TARGET_NAME)"; 455 | SWIFT_VERSION = 5.0; 456 | TARGETED_DEVICE_FAMILY = "1,2"; 457 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SplashCardTest.app/SplashCardTest"; 458 | }; 459 | name = Release; 460 | }; 461 | /* End XCBuildConfiguration section */ 462 | 463 | /* Begin XCConfigurationList section */ 464 | 8C1DEE6E2453B2710013344D /* Build configuration list for PBXProject "SplashCardTest" */ = { 465 | isa = XCConfigurationList; 466 | buildConfigurations = ( 467 | 8C1DEE902453B2730013344D /* Debug */, 468 | 8C1DEE912453B2730013344D /* Release */, 469 | ); 470 | defaultConfigurationIsVisible = 0; 471 | defaultConfigurationName = Release; 472 | }; 473 | 8C1DEE922453B2730013344D /* Build configuration list for PBXNativeTarget "SplashCardTest" */ = { 474 | isa = XCConfigurationList; 475 | buildConfigurations = ( 476 | 8C1DEE932453B2730013344D /* Debug */, 477 | 8C1DEE942453B2730013344D /* Release */, 478 | ); 479 | defaultConfigurationIsVisible = 0; 480 | defaultConfigurationName = Release; 481 | }; 482 | 8C1DEE952453B2730013344D /* Build configuration list for PBXNativeTarget "SplashCardTestTests" */ = { 483 | isa = XCConfigurationList; 484 | buildConfigurations = ( 485 | 8C1DEE962453B2730013344D /* Debug */, 486 | 8C1DEE972453B2730013344D /* Release */, 487 | ); 488 | defaultConfigurationIsVisible = 0; 489 | defaultConfigurationName = Release; 490 | }; 491 | /* End XCConfigurationList section */ 492 | }; 493 | rootObject = 8C1DEE6B2453B2710013344D /* Project object */; 494 | } 495 | -------------------------------------------------------------------------------- /SplashCardTest.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SplashCardTest.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SplashCardTest.xcodeproj/xcuserdata/jmb.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | SplashCardTest.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /SplashCardTest/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // SplashCardTest 4 | // 5 | // Created by Jean-Marc Boullianne on 4/24/20. 6 | // Copyright © 2020 TrailingClosure. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | 15 | 16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 17 | // Override point for customization after application launch. 18 | return true 19 | } 20 | 21 | // MARK: UISceneSession Lifecycle 22 | 23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 24 | // Called when a new scene session is being created. 25 | // Use this method to select a configuration to create the new scene with. 26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 27 | } 28 | 29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 30 | // Called when the user discards a scene session. 31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 33 | } 34 | 35 | 36 | } 37 | 38 | -------------------------------------------------------------------------------- /SplashCardTest/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /SplashCardTest/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SplashCardTest/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 | -------------------------------------------------------------------------------- /SplashCardTest/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // SplashCardTest 4 | // 5 | // Created by Jean-Marc Boullianne on 4/24/20. 6 | // Copyright © 2020 TrailingClosure. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct ContentView: View { 12 | var colors: [Color] = [.blue, .red, .green, .orange] 13 | @State var index: Int = 0 14 | 15 | @State var progress: CGFloat = 0 16 | var body: some View { 17 | VStack { 18 | HStack { 19 | SplashView(animationType: .angle(Angle(degrees: 45)), color: self.colors[self.index]) 20 | .frame(width: 100, height: 100, alignment: .center) 21 | .cornerRadius(10) 22 | .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 4) 23 | SplashView(animationType: .angle(Angle(degrees: 135)), color: self.colors[self.index]) 24 | .frame(width: 100, height: 100, alignment: .center) 25 | .cornerRadius(10) 26 | .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 4) 27 | } 28 | HStack { 29 | SplashView(animationType: .angle(Angle(degrees: 315)), color: self.colors[self.index]) 30 | .frame(width: 100, height: 100, alignment: .center) 31 | .cornerRadius(10) 32 | .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 4) 33 | SplashView(animationType: .angle(Angle(degrees: 225)), color: self.colors[self.index]) 34 | .frame(width: 100, height: 100, alignment: .center) 35 | .cornerRadius(10) 36 | .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 4) 37 | } 38 | HStack { 39 | SplashView(animationType: .topToBottom, color: self.colors[self.index]) 40 | .frame(width: 100, height: 100, alignment: .center) 41 | .cornerRadius(10) 42 | .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 4) 43 | SplashView(animationType: .circle, color: self.colors[self.index]) 44 | .frame(width: 100, height: 100, alignment: .center) 45 | .cornerRadius(10) 46 | .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 4) 47 | } 48 | 49 | Button(action: { 50 | self.index = (self.index + 1) % self.colors.count 51 | }) { 52 | Text("Change Color") 53 | } 54 | .padding(.top, 200) 55 | } 56 | .padding(.all, 200) 57 | .background(Color(red: 38/255, green: 50/255, blue: 56/255)) 58 | 59 | } 60 | } 61 | 62 | struct ContentView_Previews: PreviewProvider { 63 | static var previews: some View { 64 | ContentView() 65 | } 66 | } 67 | 68 | 69 | -------------------------------------------------------------------------------- /SplashCardTest/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 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UISupportedInterfaceOrientations~ipad 53 | 54 | UIInterfaceOrientationPortrait 55 | UIInterfaceOrientationPortraitUpsideDown 56 | UIInterfaceOrientationLandscapeLeft 57 | UIInterfaceOrientationLandscapeRight 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /SplashCardTest/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /SplashCardTest/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // SplashCardTest 4 | // 5 | // Created by Jean-Marc Boullianne on 4/24/20. 6 | // Copyright © 2020 TrailingClosure. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import SwiftUI 11 | 12 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 18 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. 19 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene. 20 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead). 21 | 22 | // Create the SwiftUI view that provides the window contents. 23 | let contentView = ContentView() 24 | 25 | // Use a UIHostingController as window root view controller. 26 | if let windowScene = scene as? UIWindowScene { 27 | let window = UIWindow(windowScene: windowScene) 28 | window.rootViewController = UIHostingController(rootView: contentView) 29 | self.window = window 30 | window.makeKeyAndVisible() 31 | } 32 | } 33 | 34 | func sceneDidDisconnect(_ scene: UIScene) { 35 | // Called as the scene is being released by the system. 36 | // This occurs shortly after the scene enters the background, or when its session is discarded. 37 | // Release any resources associated with this scene that can be re-created the next time the scene connects. 38 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead). 39 | } 40 | 41 | func sceneDidBecomeActive(_ scene: UIScene) { 42 | // Called when the scene has moved from an inactive state to an active state. 43 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive. 44 | } 45 | 46 | func sceneWillResignActive(_ scene: UIScene) { 47 | // Called when the scene will move from an active state to an inactive state. 48 | // This may occur due to temporary interruptions (ex. an incoming phone call). 49 | } 50 | 51 | func sceneWillEnterForeground(_ scene: UIScene) { 52 | // Called as the scene transitions from the background to the foreground. 53 | // Use this method to undo the changes made on entering the background. 54 | } 55 | 56 | func sceneDidEnterBackground(_ scene: UIScene) { 57 | // Called as the scene transitions from the foreground to the background. 58 | // Use this method to save data, release shared resources, and store enough scene-specific state information 59 | // to restore the scene back to its current state. 60 | } 61 | 62 | 63 | } 64 | 65 | -------------------------------------------------------------------------------- /SplashCardTest/SplashExample01.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashExample01.swift 3 | // SplashCardTest 4 | // 5 | // Created by Jean-Marc Boullianne on 4/25/20. 6 | // Copyright © 2020 TrailingClosure. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SplashExample01: View { 12 | var colors: [Color] = [.blue, .red, .green, .orange] 13 | var backgroundColors: [Color] = [.green, .orange, .blue, .red] 14 | @State var index: Int = 0 15 | 16 | var body: some View { 17 | ZStack { 18 | SplashView(animationType: .angle(Angle(degrees: 30)), color: self.backgroundColors[self.index]) 19 | VStack { 20 | GeometryReader { outsideProxy in 21 | ScrollView(.horizontal, showsIndicators: false) { 22 | HStack(alignment: .center, spacing: 40) { 23 | GeometryReader { insideProxy in 24 | Color.clear 25 | .preference(key: ScrollOffsetPreferenceKey.self, value: [self.calculateContentOffset(fromOutsideProxy: outsideProxy, insideProxy: insideProxy)]) 26 | // Send value to the parent 27 | } 28 | ForEach(self.colors, id: \.self) { color in 29 | Rectangle() 30 | .frame(width: 250, height: 175, alignment: .center) 31 | .foregroundColor(color) 32 | .cornerRadius(10) 33 | .shadow(color: Color.black.opacity(0.2), radius: 4, x: 0, y: 0) 34 | } 35 | } 36 | .padding() 37 | } 38 | }.onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in 39 | 40 | let newIndex = Int((value[0] + 125) / (270)) 41 | if self.index != newIndex { self.index = newIndex } 42 | } 43 | SplashView(animationType: .circle, color: self.colors[self.index]) 44 | .frame(width: 300, height: 400, alignment: .center) 45 | .cornerRadius(10) 46 | .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 0) 47 | .padding(.bottom, 50) 48 | } 49 | } 50 | .edgesIgnoringSafeArea(.vertical) 51 | 52 | 53 | } 54 | 55 | private func calculateContentOffset(fromOutsideProxy outsideProxy: GeometryProxy, insideProxy: GeometryProxy) -> CGFloat { 56 | return outsideProxy.frame(in: .global).minX - insideProxy.frame(in: .global).minX 57 | } 58 | } 59 | 60 | struct SplashExample01_Previews: PreviewProvider { 61 | static var previews: some View { 62 | SplashExample01() 63 | } 64 | } 65 | 66 | struct ScrollOffsetPreferenceKey: PreferenceKey { 67 | typealias Value = [CGFloat] 68 | 69 | static var defaultValue: [CGFloat] = [0] 70 | 71 | static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) { 72 | value.append(contentsOf: nextValue()) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /SplashCardTest/SplashExample02.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashExample02.swift 3 | // SplashCardTest 4 | // 5 | // Created by Jean-Marc Boullianne on 4/25/20. 6 | // Copyright © 2020 TrailingClosure. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SplashExample02: View { 12 | var colors: [Color] = [.blue, .red, .green, .orange] 13 | var backgroundColors: [Color] = [.green, .orange, .blue, .red] 14 | @State var index: Int = 0 15 | 16 | var body: some View { 17 | ZStack { 18 | SplashView(animationType: .angle(Angle(degrees: 30)), color: self.backgroundColors[self.index]) 19 | VStack { 20 | 21 | 22 | 23 | 24 | SplashView(animationType: .circle, color: self.colors[self.index]) 25 | .frame(width: 300, height: 375, alignment: .center) 26 | .cornerRadius(10) 27 | .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 0) 28 | 29 | Button(action: { 30 | self.index = (self.index + 1) % self.colors.count 31 | }) { 32 | Text("Change Colors") 33 | .font(Font.system(size: 18, weight: .bold, design: .default)) 34 | .foregroundColor(Color.white) 35 | .padding(.horizontal, 20) 36 | .padding(.vertical, 10) 37 | .background(Color(red: 51/255, green: 51/255, blue: 51/255)) 38 | .cornerRadius(5) 39 | .shadow(color: Color.black.opacity(0.2), radius: 10, x: 0, y: 0) 40 | 41 | }.padding(.top, 50) 42 | 43 | } 44 | } 45 | .edgesIgnoringSafeArea(.vertical) 46 | 47 | 48 | } 49 | } 50 | 51 | struct SplashExample02_Previews: PreviewProvider { 52 | static var previews: some View { 53 | SplashExample02() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /SplashCardTest/SplashShape.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashShape.swift 3 | // SplashCardTest 4 | // 5 | // Created by Jean-Marc Boullianne on 4/24/20. 6 | // Copyright © 2020 TrailingClosure. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SplashShape: Shape { 12 | 13 | enum SplashAnimation { 14 | case leftToRight 15 | case rightToLeft 16 | case topToBottom 17 | case bottomToTop 18 | case angle(Angle) 19 | case circle 20 | } 21 | 22 | var progress: CGFloat 23 | var animationType: SplashAnimation 24 | 25 | var animatableData: CGFloat { 26 | get { return progress } 27 | set { self.progress = newValue} 28 | } 29 | 30 | func path(in rect: CGRect) -> Path { 31 | 32 | switch self.animationType { 33 | case .leftToRight: 34 | return leftToRight(rect: rect) 35 | case .rightToLeft: 36 | return rightToLeft(rect: rect) 37 | case .topToBottom: 38 | return topToBottom(rect: rect) 39 | case .bottomToTop: 40 | return bottomToTop(rect: rect) 41 | case .angle(let splashAngle): 42 | return angle(rect: rect, angle: splashAngle) 43 | case .circle: 44 | return circle(rect: rect) 45 | } 46 | 47 | } 48 | 49 | func leftToRight(rect: CGRect) -> Path { 50 | var path = Path() 51 | path.move(to: CGPoint(x: 0, y: 0)) 52 | path.addLine(to: CGPoint(x: rect.width * progress, y: 0)) 53 | path.addLine(to: CGPoint(x: rect.width * progress, y: rect.height)) 54 | path.addLine(to: CGPoint(x: 0, y: rect.height)) 55 | path.closeSubpath() 56 | return path 57 | } 58 | 59 | func rightToLeft(rect: CGRect) -> Path { 60 | var path = Path() 61 | path.move(to: CGPoint(x: rect.width, y: 0)) 62 | path.addLine(to: CGPoint(x: rect.width - (rect.width * progress), y: 0)) 63 | path.addLine(to: CGPoint(x: rect.width - (rect.width * progress), y: rect.height)) 64 | path.addLine(to: CGPoint(x: rect.width, y: rect.height)) 65 | path.closeSubpath() 66 | return path 67 | } 68 | 69 | func topToBottom(rect: CGRect) -> Path { 70 | var path = Path() 71 | path.move(to: CGPoint(x: 0, y: 0)) 72 | path.addLine(to: CGPoint(x: rect.width, y: 0)) 73 | path.addLine(to: CGPoint(x: rect.width, y: rect.height * progress)) 74 | path.addLine(to: CGPoint(x: 0, y: rect.height * progress)) 75 | path.closeSubpath() 76 | return path 77 | } 78 | 79 | func bottomToTop(rect: CGRect) -> Path { 80 | var path = Path() 81 | path.move(to: CGPoint(x: 0, y: rect.height)) 82 | path.addLine(to: CGPoint(x: rect.width, y: rect.height)) 83 | path.addLine(to: CGPoint(x: rect.width, y: rect.height - (rect.height * progress))) 84 | path.addLine(to: CGPoint(x: 0, y: rect.height - (rect.height * progress))) 85 | path.closeSubpath() 86 | return path 87 | } 88 | 89 | func angle(rect: CGRect, angle: Angle) -> Path { 90 | 91 | var cAngle = Angle(degrees: angle.degrees.truncatingRemainder(dividingBy: 90)) 92 | 93 | // Return Path Using Other Animations (topToBottom, leftToRight, etc) if angle is 0, 90, 180, 270 94 | if angle.degrees == 0 || cAngle.degrees == 0 { return leftToRight(rect: rect)} 95 | else if angle.degrees == 90 || cAngle.degrees == 90 { return topToBottom(rect: rect)} 96 | else if angle.degrees == 180 || cAngle.degrees == 180 { return rightToLeft(rect: rect)} 97 | else if angle.degrees == 270 || cAngle.degrees == 270 { return bottomToTop(rect: rect)} 98 | 99 | 100 | // Calculate Slope of Line and inverse slope 101 | let m = CGFloat(tan(cAngle.radians)) 102 | let m_1 = pow(m, -1) * -1 103 | let h = rect.height 104 | let w = rect.width 105 | 106 | // tan (angle) = slope of line 107 | // y = mx + b ---> b = y - mx ~ 'b' = y intercept 108 | let b = h - (m_1 * w) // b = y - (m * x) 109 | 110 | // X and Y coordinate calculation 111 | var x = b * m * progress 112 | var y = b * progress 113 | 114 | // Triangle Offset Calculation 115 | let xOffset = (angle.degrees > 90 && angle.degrees < 270) ? rect.width : 0 116 | let yOffset = (angle.degrees > 180 && angle.degrees < 360) ? rect.height : 0 117 | 118 | // Modify which side the triangle is drawn from depending on the angle 119 | if angle.degrees > 90 && angle.degrees < 180 { x *= -1 } 120 | else if angle.degrees > 180 && angle.degrees < 270 { x *= -1; y *= -1 } 121 | else if angle.degrees > 270 && angle.degrees < 360 { y *= -1 } 122 | 123 | // Build Triangle Path 124 | var path = Path() 125 | path.move(to: CGPoint(x: xOffset, y: yOffset)) 126 | path.addLine(to: CGPoint(x: xOffset + x, y: yOffset)) 127 | path.addLine(to: CGPoint(x: xOffset, y: yOffset + y)) 128 | path.closeSubpath() 129 | return path 130 | 131 | } 132 | 133 | func circle(rect: CGRect) -> Path { 134 | 135 | let a: CGFloat = rect.height / 2.0 136 | let b: CGFloat = rect.width / 2.0 137 | 138 | let c = pow(pow(a, 2) + pow(b, 2), 0.5) // a^2 + b^2 = c^2 --> Solved for 'c' 139 | // c = radius of final circle 140 | 141 | let radius = c * progress 142 | // Build Circle Path 143 | var path = Path() 144 | path.addArc(center: CGPoint(x: rect.midX, y: rect.midY), radius: radius, startAngle: Angle(degrees: 0), endAngle: Angle(degrees: 360), clockwise: true) 145 | return path 146 | 147 | } 148 | 149 | } 150 | 151 | struct SplashShape_Previews: PreviewProvider { 152 | static var previews: some View { 153 | SplashShape(progress: 1, animationType: .rightToLeft) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /SplashCardTest/SplashView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashCard.swift 3 | // SplashCardTest 4 | // 5 | // Created by Jean-Marc Boullianne on 4/24/20. 6 | // Copyright © 2020 TrailingClosure. All rights reserved. 7 | // 8 | 9 | import SwiftUI 10 | 11 | struct SplashView: View { 12 | 13 | 14 | var animationType: SplashShape.SplashAnimation 15 | 16 | 17 | @State private var prevColor: Color 18 | @ObservedObject var colorStore: ColorStore 19 | 20 | @State var layers: [(Color,CGFloat)] = [] 21 | 22 | 23 | init(animationType: SplashShape.SplashAnimation, color: Color) { 24 | self.animationType = animationType 25 | self._prevColor = State(initialValue: color) 26 | self.colorStore = ColorStore(color: color) 27 | } 28 | 29 | var body: some View { 30 | Rectangle() 31 | .foregroundColor(self.prevColor) 32 | .overlay( 33 | ZStack { 34 | ForEach(layers.indices, id: \.self) { x in 35 | SplashShape(progress: self.layers[x].1, animationType: self.animationType) 36 | .foregroundColor(self.layers[x].0) 37 | } 38 | 39 | } 40 | 41 | , alignment: .leading) 42 | .onReceive(self.colorStore.$color) { color in 43 | self.layers.append((color, 0)) 44 | 45 | withAnimation(.easeInOut(duration: 0.5)) { 46 | self.layers[self.layers.count-1].1 = 1.0 47 | /*DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { 48 | self.prevColor = self.layers[0].0 49 | self.layers.remove(at: 0) 50 | }*/ 51 | } 52 | } 53 | } 54 | 55 | } 56 | 57 | struct SplashCard_Previews: PreviewProvider { 58 | 59 | @State static var color: Color = Color.blue 60 | 61 | static var previews: some View { 62 | SplashView(animationType: .rightToLeft, color: color) 63 | } 64 | } 65 | 66 | class ColorStore: ObservableObject { 67 | @Published var color: Color 68 | 69 | init(color: Color) { 70 | self.color = color 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SplashCardTestTests/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 | -------------------------------------------------------------------------------- /SplashCardTestTests/SplashCardTestTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SplashCardTestTests.swift 3 | // SplashCardTestTests 4 | // 5 | // Created by Jean-Marc Boullianne on 4/24/20. 6 | // Copyright © 2020 TrailingClosure. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import SplashCardTest 11 | 12 | class SplashCardTestTests: XCTestCase { 13 | 14 | override func setUp() { 15 | // Put setup code here. This method is called before the invocation of each test method in the class. 16 | } 17 | 18 | override func tearDown() { 19 | // Put teardown code here. This method is called after the invocation of each test method in the class. 20 | } 21 | 22 | func testExample() { 23 | // This is an example of a functional test case. 24 | // Use XCTAssert and related functions to verify your tests produce the correct results. 25 | } 26 | 27 | func testPerformanceExample() { 28 | // This is an example of a performance test case. 29 | self.measure { 30 | // Put the code you want to measure the time of here. 31 | } 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /images/SplashView_Example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jboullianne/SplashView/2e1cfb451842e80fba9dea2e226abc77ea419b03/images/SplashView_Example.gif --------------------------------------------------------------------------------