├── README.md ├── StickyHeader.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── lebonbbauma.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── StickyHeader ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ ├── Contents.json │ ├── artwork.imageset │ │ ├── Contents.json │ │ └── artwork.jpeg │ ├── background.colorset │ │ └── Contents.json │ └── spotifyGreen.colorset │ │ └── Contents.json ├── ContentView.swift ├── Model │ └── Album.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── StickyHeaderApp.swift └── View │ └── Home.swift ├── StickyHeaderTests └── StickyHeaderTests.swift └── StickyHeaderUITests ├── StickyHeaderUITests.swift └── StickyHeaderUITestsLaunchTests.swift /README.md: -------------------------------------------------------------------------------- 1 | # StickyHeader 2 | Here is a simple animated Sticky Header built in SwiftUI 4 in XCode 14 3 | Enjoy 😎 4 | 5 | ![StickyHeader](https://user-images.githubusercontent.com/15221546/205421428-78a8a2d7-2a8e-4ae4-921e-27c601208466.gif) 6 | -------------------------------------------------------------------------------- /StickyHeader.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8F3F4388293AD07B00B76F99 /* StickyHeaderApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3F4387293AD07B00B76F99 /* StickyHeaderApp.swift */; }; 11 | 8F3F438A293AD07B00B76F99 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3F4389293AD07B00B76F99 /* ContentView.swift */; }; 12 | 8F3F438C293AD07E00B76F99 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8F3F438B293AD07E00B76F99 /* Assets.xcassets */; }; 13 | 8F3F438F293AD07E00B76F99 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 8F3F438E293AD07E00B76F99 /* Preview Assets.xcassets */; }; 14 | 8F3F4399293AD07E00B76F99 /* StickyHeaderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3F4398293AD07E00B76F99 /* StickyHeaderTests.swift */; }; 15 | 8F3F43A3293AD07F00B76F99 /* StickyHeaderUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3F43A2293AD07F00B76F99 /* StickyHeaderUITests.swift */; }; 16 | 8F3F43A5293AD07F00B76F99 /* StickyHeaderUITestsLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3F43A4293AD07F00B76F99 /* StickyHeaderUITestsLaunchTests.swift */; }; 17 | 8F3F43B3293AD4DA00B76F99 /* Album.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3F43B2293AD4DA00B76F99 /* Album.swift */; }; 18 | 8F3F43B6293ADAD100B76F99 /* Home.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8F3F43B5293ADAD100B76F99 /* Home.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 8F3F4395293AD07E00B76F99 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 8F3F437C293AD07B00B76F99 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 8F3F4383293AD07B00B76F99; 27 | remoteInfo = StickyHeader; 28 | }; 29 | 8F3F439F293AD07F00B76F99 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 8F3F437C293AD07B00B76F99 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 8F3F4383293AD07B00B76F99; 34 | remoteInfo = StickyHeader; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 8F3F4384293AD07B00B76F99 /* StickyHeader.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StickyHeader.app; sourceTree = BUILT_PRODUCTS_DIR; }; 40 | 8F3F4387293AD07B00B76F99 /* StickyHeaderApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyHeaderApp.swift; sourceTree = ""; }; 41 | 8F3F4389293AD07B00B76F99 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 42 | 8F3F438B293AD07E00B76F99 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 43 | 8F3F438E293AD07E00B76F99 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 44 | 8F3F4394293AD07E00B76F99 /* StickyHeaderTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StickyHeaderTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 45 | 8F3F4398293AD07E00B76F99 /* StickyHeaderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyHeaderTests.swift; sourceTree = ""; }; 46 | 8F3F439E293AD07F00B76F99 /* StickyHeaderUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = StickyHeaderUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 8F3F43A2293AD07F00B76F99 /* StickyHeaderUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyHeaderUITests.swift; sourceTree = ""; }; 48 | 8F3F43A4293AD07F00B76F99 /* StickyHeaderUITestsLaunchTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyHeaderUITestsLaunchTests.swift; sourceTree = ""; }; 49 | 8F3F43B2293AD4DA00B76F99 /* Album.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Album.swift; sourceTree = ""; }; 50 | 8F3F43B5293ADAD100B76F99 /* Home.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Home.swift; sourceTree = ""; }; 51 | /* End PBXFileReference section */ 52 | 53 | /* Begin PBXFrameworksBuildPhase section */ 54 | 8F3F4381293AD07B00B76F99 /* Frameworks */ = { 55 | isa = PBXFrameworksBuildPhase; 56 | buildActionMask = 2147483647; 57 | files = ( 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | 8F3F4391293AD07E00B76F99 /* Frameworks */ = { 62 | isa = PBXFrameworksBuildPhase; 63 | buildActionMask = 2147483647; 64 | files = ( 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | 8F3F439B293AD07F00B76F99 /* Frameworks */ = { 69 | isa = PBXFrameworksBuildPhase; 70 | buildActionMask = 2147483647; 71 | files = ( 72 | ); 73 | runOnlyForDeploymentPostprocessing = 0; 74 | }; 75 | /* End PBXFrameworksBuildPhase section */ 76 | 77 | /* Begin PBXGroup section */ 78 | 8F3F437B293AD07B00B76F99 = { 79 | isa = PBXGroup; 80 | children = ( 81 | 8F3F4386293AD07B00B76F99 /* StickyHeader */, 82 | 8F3F4397293AD07E00B76F99 /* StickyHeaderTests */, 83 | 8F3F43A1293AD07F00B76F99 /* StickyHeaderUITests */, 84 | 8F3F4385293AD07B00B76F99 /* Products */, 85 | ); 86 | sourceTree = ""; 87 | }; 88 | 8F3F4385293AD07B00B76F99 /* Products */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 8F3F4384293AD07B00B76F99 /* StickyHeader.app */, 92 | 8F3F4394293AD07E00B76F99 /* StickyHeaderTests.xctest */, 93 | 8F3F439E293AD07F00B76F99 /* StickyHeaderUITests.xctest */, 94 | ); 95 | name = Products; 96 | sourceTree = ""; 97 | }; 98 | 8F3F4386293AD07B00B76F99 /* StickyHeader */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 8F3F43B4293ADABE00B76F99 /* View */, 102 | 8F3F43B1293AD4B700B76F99 /* Model */, 103 | 8F3F4387293AD07B00B76F99 /* StickyHeaderApp.swift */, 104 | 8F3F4389293AD07B00B76F99 /* ContentView.swift */, 105 | 8F3F438B293AD07E00B76F99 /* Assets.xcassets */, 106 | 8F3F438D293AD07E00B76F99 /* Preview Content */, 107 | ); 108 | path = StickyHeader; 109 | sourceTree = ""; 110 | }; 111 | 8F3F438D293AD07E00B76F99 /* Preview Content */ = { 112 | isa = PBXGroup; 113 | children = ( 114 | 8F3F438E293AD07E00B76F99 /* Preview Assets.xcassets */, 115 | ); 116 | path = "Preview Content"; 117 | sourceTree = ""; 118 | }; 119 | 8F3F4397293AD07E00B76F99 /* StickyHeaderTests */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 8F3F4398293AD07E00B76F99 /* StickyHeaderTests.swift */, 123 | ); 124 | path = StickyHeaderTests; 125 | sourceTree = ""; 126 | }; 127 | 8F3F43A1293AD07F00B76F99 /* StickyHeaderUITests */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 8F3F43A2293AD07F00B76F99 /* StickyHeaderUITests.swift */, 131 | 8F3F43A4293AD07F00B76F99 /* StickyHeaderUITestsLaunchTests.swift */, 132 | ); 133 | path = StickyHeaderUITests; 134 | sourceTree = ""; 135 | }; 136 | 8F3F43B1293AD4B700B76F99 /* Model */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 8F3F43B2293AD4DA00B76F99 /* Album.swift */, 140 | ); 141 | path = Model; 142 | sourceTree = ""; 143 | }; 144 | 8F3F43B4293ADABE00B76F99 /* View */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 8F3F43B5293ADAD100B76F99 /* Home.swift */, 148 | ); 149 | path = View; 150 | sourceTree = ""; 151 | }; 152 | /* End PBXGroup section */ 153 | 154 | /* Begin PBXNativeTarget section */ 155 | 8F3F4383293AD07B00B76F99 /* StickyHeader */ = { 156 | isa = PBXNativeTarget; 157 | buildConfigurationList = 8F3F43A8293AD07F00B76F99 /* Build configuration list for PBXNativeTarget "StickyHeader" */; 158 | buildPhases = ( 159 | 8F3F4380293AD07B00B76F99 /* Sources */, 160 | 8F3F4381293AD07B00B76F99 /* Frameworks */, 161 | 8F3F4382293AD07B00B76F99 /* Resources */, 162 | ); 163 | buildRules = ( 164 | ); 165 | dependencies = ( 166 | ); 167 | name = StickyHeader; 168 | productName = StickyHeader; 169 | productReference = 8F3F4384293AD07B00B76F99 /* StickyHeader.app */; 170 | productType = "com.apple.product-type.application"; 171 | }; 172 | 8F3F4393293AD07E00B76F99 /* StickyHeaderTests */ = { 173 | isa = PBXNativeTarget; 174 | buildConfigurationList = 8F3F43AB293AD07F00B76F99 /* Build configuration list for PBXNativeTarget "StickyHeaderTests" */; 175 | buildPhases = ( 176 | 8F3F4390293AD07E00B76F99 /* Sources */, 177 | 8F3F4391293AD07E00B76F99 /* Frameworks */, 178 | 8F3F4392293AD07E00B76F99 /* Resources */, 179 | ); 180 | buildRules = ( 181 | ); 182 | dependencies = ( 183 | 8F3F4396293AD07E00B76F99 /* PBXTargetDependency */, 184 | ); 185 | name = StickyHeaderTests; 186 | productName = StickyHeaderTests; 187 | productReference = 8F3F4394293AD07E00B76F99 /* StickyHeaderTests.xctest */; 188 | productType = "com.apple.product-type.bundle.unit-test"; 189 | }; 190 | 8F3F439D293AD07F00B76F99 /* StickyHeaderUITests */ = { 191 | isa = PBXNativeTarget; 192 | buildConfigurationList = 8F3F43AE293AD07F00B76F99 /* Build configuration list for PBXNativeTarget "StickyHeaderUITests" */; 193 | buildPhases = ( 194 | 8F3F439A293AD07F00B76F99 /* Sources */, 195 | 8F3F439B293AD07F00B76F99 /* Frameworks */, 196 | 8F3F439C293AD07F00B76F99 /* Resources */, 197 | ); 198 | buildRules = ( 199 | ); 200 | dependencies = ( 201 | 8F3F43A0293AD07F00B76F99 /* PBXTargetDependency */, 202 | ); 203 | name = StickyHeaderUITests; 204 | productName = StickyHeaderUITests; 205 | productReference = 8F3F439E293AD07F00B76F99 /* StickyHeaderUITests.xctest */; 206 | productType = "com.apple.product-type.bundle.ui-testing"; 207 | }; 208 | /* End PBXNativeTarget section */ 209 | 210 | /* Begin PBXProject section */ 211 | 8F3F437C293AD07B00B76F99 /* Project object */ = { 212 | isa = PBXProject; 213 | attributes = { 214 | BuildIndependentTargetsInParallel = 1; 215 | LastSwiftUpdateCheck = 1410; 216 | LastUpgradeCheck = 1410; 217 | TargetAttributes = { 218 | 8F3F4383293AD07B00B76F99 = { 219 | CreatedOnToolsVersion = 14.1; 220 | }; 221 | 8F3F4393293AD07E00B76F99 = { 222 | CreatedOnToolsVersion = 14.1; 223 | TestTargetID = 8F3F4383293AD07B00B76F99; 224 | }; 225 | 8F3F439D293AD07F00B76F99 = { 226 | CreatedOnToolsVersion = 14.1; 227 | TestTargetID = 8F3F4383293AD07B00B76F99; 228 | }; 229 | }; 230 | }; 231 | buildConfigurationList = 8F3F437F293AD07B00B76F99 /* Build configuration list for PBXProject "StickyHeader" */; 232 | compatibilityVersion = "Xcode 14.0"; 233 | developmentRegion = en; 234 | hasScannedForEncodings = 0; 235 | knownRegions = ( 236 | en, 237 | Base, 238 | ); 239 | mainGroup = 8F3F437B293AD07B00B76F99; 240 | productRefGroup = 8F3F4385293AD07B00B76F99 /* Products */; 241 | projectDirPath = ""; 242 | projectRoot = ""; 243 | targets = ( 244 | 8F3F4383293AD07B00B76F99 /* StickyHeader */, 245 | 8F3F4393293AD07E00B76F99 /* StickyHeaderTests */, 246 | 8F3F439D293AD07F00B76F99 /* StickyHeaderUITests */, 247 | ); 248 | }; 249 | /* End PBXProject section */ 250 | 251 | /* Begin PBXResourcesBuildPhase section */ 252 | 8F3F4382293AD07B00B76F99 /* Resources */ = { 253 | isa = PBXResourcesBuildPhase; 254 | buildActionMask = 2147483647; 255 | files = ( 256 | 8F3F438F293AD07E00B76F99 /* Preview Assets.xcassets in Resources */, 257 | 8F3F438C293AD07E00B76F99 /* Assets.xcassets in Resources */, 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | }; 261 | 8F3F4392293AD07E00B76F99 /* Resources */ = { 262 | isa = PBXResourcesBuildPhase; 263 | buildActionMask = 2147483647; 264 | files = ( 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | }; 268 | 8F3F439C293AD07F00B76F99 /* Resources */ = { 269 | isa = PBXResourcesBuildPhase; 270 | buildActionMask = 2147483647; 271 | files = ( 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | }; 275 | /* End PBXResourcesBuildPhase section */ 276 | 277 | /* Begin PBXSourcesBuildPhase section */ 278 | 8F3F4380293AD07B00B76F99 /* Sources */ = { 279 | isa = PBXSourcesBuildPhase; 280 | buildActionMask = 2147483647; 281 | files = ( 282 | 8F3F438A293AD07B00B76F99 /* ContentView.swift in Sources */, 283 | 8F3F4388293AD07B00B76F99 /* StickyHeaderApp.swift in Sources */, 284 | 8F3F43B6293ADAD100B76F99 /* Home.swift in Sources */, 285 | 8F3F43B3293AD4DA00B76F99 /* Album.swift in Sources */, 286 | ); 287 | runOnlyForDeploymentPostprocessing = 0; 288 | }; 289 | 8F3F4390293AD07E00B76F99 /* Sources */ = { 290 | isa = PBXSourcesBuildPhase; 291 | buildActionMask = 2147483647; 292 | files = ( 293 | 8F3F4399293AD07E00B76F99 /* StickyHeaderTests.swift in Sources */, 294 | ); 295 | runOnlyForDeploymentPostprocessing = 0; 296 | }; 297 | 8F3F439A293AD07F00B76F99 /* Sources */ = { 298 | isa = PBXSourcesBuildPhase; 299 | buildActionMask = 2147483647; 300 | files = ( 301 | 8F3F43A5293AD07F00B76F99 /* StickyHeaderUITestsLaunchTests.swift in Sources */, 302 | 8F3F43A3293AD07F00B76F99 /* StickyHeaderUITests.swift in Sources */, 303 | ); 304 | runOnlyForDeploymentPostprocessing = 0; 305 | }; 306 | /* End PBXSourcesBuildPhase section */ 307 | 308 | /* Begin PBXTargetDependency section */ 309 | 8F3F4396293AD07E00B76F99 /* PBXTargetDependency */ = { 310 | isa = PBXTargetDependency; 311 | target = 8F3F4383293AD07B00B76F99 /* StickyHeader */; 312 | targetProxy = 8F3F4395293AD07E00B76F99 /* PBXContainerItemProxy */; 313 | }; 314 | 8F3F43A0293AD07F00B76F99 /* PBXTargetDependency */ = { 315 | isa = PBXTargetDependency; 316 | target = 8F3F4383293AD07B00B76F99 /* StickyHeader */; 317 | targetProxy = 8F3F439F293AD07F00B76F99 /* PBXContainerItemProxy */; 318 | }; 319 | /* End PBXTargetDependency section */ 320 | 321 | /* Begin XCBuildConfiguration section */ 322 | 8F3F43A6293AD07F00B76F99 /* Debug */ = { 323 | isa = XCBuildConfiguration; 324 | buildSettings = { 325 | ALWAYS_SEARCH_USER_PATHS = NO; 326 | CLANG_ANALYZER_NONNULL = YES; 327 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 328 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 329 | CLANG_ENABLE_MODULES = YES; 330 | CLANG_ENABLE_OBJC_ARC = YES; 331 | CLANG_ENABLE_OBJC_WEAK = YES; 332 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 333 | CLANG_WARN_BOOL_CONVERSION = YES; 334 | CLANG_WARN_COMMA = YES; 335 | CLANG_WARN_CONSTANT_CONVERSION = YES; 336 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 337 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 338 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 339 | CLANG_WARN_EMPTY_BODY = YES; 340 | CLANG_WARN_ENUM_CONVERSION = YES; 341 | CLANG_WARN_INFINITE_RECURSION = YES; 342 | CLANG_WARN_INT_CONVERSION = YES; 343 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 344 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 345 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 346 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 347 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 348 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 349 | CLANG_WARN_STRICT_PROTOTYPES = YES; 350 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 351 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 352 | CLANG_WARN_UNREACHABLE_CODE = YES; 353 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 354 | COPY_PHASE_STRIP = NO; 355 | DEBUG_INFORMATION_FORMAT = dwarf; 356 | ENABLE_STRICT_OBJC_MSGSEND = YES; 357 | ENABLE_TESTABILITY = YES; 358 | GCC_C_LANGUAGE_STANDARD = gnu11; 359 | GCC_DYNAMIC_NO_PIC = NO; 360 | GCC_NO_COMMON_BLOCKS = YES; 361 | GCC_OPTIMIZATION_LEVEL = 0; 362 | GCC_PREPROCESSOR_DEFINITIONS = ( 363 | "DEBUG=1", 364 | "$(inherited)", 365 | ); 366 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 367 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 368 | GCC_WARN_UNDECLARED_SELECTOR = YES; 369 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 370 | GCC_WARN_UNUSED_FUNCTION = YES; 371 | GCC_WARN_UNUSED_VARIABLE = YES; 372 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 373 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 374 | MTL_FAST_MATH = YES; 375 | ONLY_ACTIVE_ARCH = YES; 376 | SDKROOT = iphoneos; 377 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 378 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 379 | }; 380 | name = Debug; 381 | }; 382 | 8F3F43A7293AD07F00B76F99 /* Release */ = { 383 | isa = XCBuildConfiguration; 384 | buildSettings = { 385 | ALWAYS_SEARCH_USER_PATHS = NO; 386 | CLANG_ANALYZER_NONNULL = YES; 387 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 388 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 389 | CLANG_ENABLE_MODULES = YES; 390 | CLANG_ENABLE_OBJC_ARC = YES; 391 | CLANG_ENABLE_OBJC_WEAK = YES; 392 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 393 | CLANG_WARN_BOOL_CONVERSION = YES; 394 | CLANG_WARN_COMMA = YES; 395 | CLANG_WARN_CONSTANT_CONVERSION = YES; 396 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 397 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 398 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 399 | CLANG_WARN_EMPTY_BODY = YES; 400 | CLANG_WARN_ENUM_CONVERSION = YES; 401 | CLANG_WARN_INFINITE_RECURSION = YES; 402 | CLANG_WARN_INT_CONVERSION = YES; 403 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 404 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 405 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 406 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 407 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 408 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 409 | CLANG_WARN_STRICT_PROTOTYPES = YES; 410 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 411 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 412 | CLANG_WARN_UNREACHABLE_CODE = YES; 413 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 414 | COPY_PHASE_STRIP = NO; 415 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 416 | ENABLE_NS_ASSERTIONS = NO; 417 | ENABLE_STRICT_OBJC_MSGSEND = YES; 418 | GCC_C_LANGUAGE_STANDARD = gnu11; 419 | GCC_NO_COMMON_BLOCKS = YES; 420 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 421 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 422 | GCC_WARN_UNDECLARED_SELECTOR = YES; 423 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 424 | GCC_WARN_UNUSED_FUNCTION = YES; 425 | GCC_WARN_UNUSED_VARIABLE = YES; 426 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 427 | MTL_ENABLE_DEBUG_INFO = NO; 428 | MTL_FAST_MATH = YES; 429 | SDKROOT = iphoneos; 430 | SWIFT_COMPILATION_MODE = wholemodule; 431 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 432 | VALIDATE_PRODUCT = YES; 433 | }; 434 | name = Release; 435 | }; 436 | 8F3F43A9293AD07F00B76F99 /* Debug */ = { 437 | isa = XCBuildConfiguration; 438 | buildSettings = { 439 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 440 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 441 | CODE_SIGN_STYLE = Automatic; 442 | CURRENT_PROJECT_VERSION = 1; 443 | DEVELOPMENT_ASSET_PATHS = "\"StickyHeader/Preview Content\""; 444 | ENABLE_PREVIEWS = YES; 445 | GENERATE_INFOPLIST_FILE = YES; 446 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 447 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 448 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 449 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 450 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 451 | LD_RUNPATH_SEARCH_PATHS = ( 452 | "$(inherited)", 453 | "@executable_path/Frameworks", 454 | ); 455 | MARKETING_VERSION = 1.0; 456 | PRODUCT_BUNDLE_IDENTIFIER = com.lebonbauma.dev.StickyHeader; 457 | PRODUCT_NAME = "$(TARGET_NAME)"; 458 | SWIFT_EMIT_LOC_STRINGS = YES; 459 | SWIFT_VERSION = 5.0; 460 | TARGETED_DEVICE_FAMILY = "1,2"; 461 | }; 462 | name = Debug; 463 | }; 464 | 8F3F43AA293AD07F00B76F99 /* Release */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 468 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 469 | CODE_SIGN_STYLE = Automatic; 470 | CURRENT_PROJECT_VERSION = 1; 471 | DEVELOPMENT_ASSET_PATHS = "\"StickyHeader/Preview Content\""; 472 | ENABLE_PREVIEWS = YES; 473 | GENERATE_INFOPLIST_FILE = YES; 474 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 475 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 476 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 477 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 478 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 479 | LD_RUNPATH_SEARCH_PATHS = ( 480 | "$(inherited)", 481 | "@executable_path/Frameworks", 482 | ); 483 | MARKETING_VERSION = 1.0; 484 | PRODUCT_BUNDLE_IDENTIFIER = com.lebonbauma.dev.StickyHeader; 485 | PRODUCT_NAME = "$(TARGET_NAME)"; 486 | SWIFT_EMIT_LOC_STRINGS = YES; 487 | SWIFT_VERSION = 5.0; 488 | TARGETED_DEVICE_FAMILY = "1,2"; 489 | }; 490 | name = Release; 491 | }; 492 | 8F3F43AC293AD07F00B76F99 /* Debug */ = { 493 | isa = XCBuildConfiguration; 494 | buildSettings = { 495 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 496 | BUNDLE_LOADER = "$(TEST_HOST)"; 497 | CODE_SIGN_STYLE = Automatic; 498 | CURRENT_PROJECT_VERSION = 1; 499 | GENERATE_INFOPLIST_FILE = YES; 500 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 501 | MARKETING_VERSION = 1.0; 502 | PRODUCT_BUNDLE_IDENTIFIER = com.lebonbauma.dev.StickyHeaderTests; 503 | PRODUCT_NAME = "$(TARGET_NAME)"; 504 | SWIFT_EMIT_LOC_STRINGS = NO; 505 | SWIFT_VERSION = 5.0; 506 | TARGETED_DEVICE_FAMILY = "1,2"; 507 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StickyHeader.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/StickyHeader"; 508 | }; 509 | name = Debug; 510 | }; 511 | 8F3F43AD293AD07F00B76F99 /* Release */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 515 | BUNDLE_LOADER = "$(TEST_HOST)"; 516 | CODE_SIGN_STYLE = Automatic; 517 | CURRENT_PROJECT_VERSION = 1; 518 | GENERATE_INFOPLIST_FILE = YES; 519 | IPHONEOS_DEPLOYMENT_TARGET = 16.1; 520 | MARKETING_VERSION = 1.0; 521 | PRODUCT_BUNDLE_IDENTIFIER = com.lebonbauma.dev.StickyHeaderTests; 522 | PRODUCT_NAME = "$(TARGET_NAME)"; 523 | SWIFT_EMIT_LOC_STRINGS = NO; 524 | SWIFT_VERSION = 5.0; 525 | TARGETED_DEVICE_FAMILY = "1,2"; 526 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/StickyHeader.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/StickyHeader"; 527 | }; 528 | name = Release; 529 | }; 530 | 8F3F43AF293AD07F00B76F99 /* Debug */ = { 531 | isa = XCBuildConfiguration; 532 | buildSettings = { 533 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 534 | CODE_SIGN_STYLE = Automatic; 535 | CURRENT_PROJECT_VERSION = 1; 536 | GENERATE_INFOPLIST_FILE = YES; 537 | MARKETING_VERSION = 1.0; 538 | PRODUCT_BUNDLE_IDENTIFIER = com.lebonbauma.dev.StickyHeaderUITests; 539 | PRODUCT_NAME = "$(TARGET_NAME)"; 540 | SWIFT_EMIT_LOC_STRINGS = NO; 541 | SWIFT_VERSION = 5.0; 542 | TARGETED_DEVICE_FAMILY = "1,2"; 543 | TEST_TARGET_NAME = StickyHeader; 544 | }; 545 | name = Debug; 546 | }; 547 | 8F3F43B0293AD07F00B76F99 /* Release */ = { 548 | isa = XCBuildConfiguration; 549 | buildSettings = { 550 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 551 | CODE_SIGN_STYLE = Automatic; 552 | CURRENT_PROJECT_VERSION = 1; 553 | GENERATE_INFOPLIST_FILE = YES; 554 | MARKETING_VERSION = 1.0; 555 | PRODUCT_BUNDLE_IDENTIFIER = com.lebonbauma.dev.StickyHeaderUITests; 556 | PRODUCT_NAME = "$(TARGET_NAME)"; 557 | SWIFT_EMIT_LOC_STRINGS = NO; 558 | SWIFT_VERSION = 5.0; 559 | TARGETED_DEVICE_FAMILY = "1,2"; 560 | TEST_TARGET_NAME = StickyHeader; 561 | }; 562 | name = Release; 563 | }; 564 | /* End XCBuildConfiguration section */ 565 | 566 | /* Begin XCConfigurationList section */ 567 | 8F3F437F293AD07B00B76F99 /* Build configuration list for PBXProject "StickyHeader" */ = { 568 | isa = XCConfigurationList; 569 | buildConfigurations = ( 570 | 8F3F43A6293AD07F00B76F99 /* Debug */, 571 | 8F3F43A7293AD07F00B76F99 /* Release */, 572 | ); 573 | defaultConfigurationIsVisible = 0; 574 | defaultConfigurationName = Release; 575 | }; 576 | 8F3F43A8293AD07F00B76F99 /* Build configuration list for PBXNativeTarget "StickyHeader" */ = { 577 | isa = XCConfigurationList; 578 | buildConfigurations = ( 579 | 8F3F43A9293AD07F00B76F99 /* Debug */, 580 | 8F3F43AA293AD07F00B76F99 /* Release */, 581 | ); 582 | defaultConfigurationIsVisible = 0; 583 | defaultConfigurationName = Release; 584 | }; 585 | 8F3F43AB293AD07F00B76F99 /* Build configuration list for PBXNativeTarget "StickyHeaderTests" */ = { 586 | isa = XCConfigurationList; 587 | buildConfigurations = ( 588 | 8F3F43AC293AD07F00B76F99 /* Debug */, 589 | 8F3F43AD293AD07F00B76F99 /* Release */, 590 | ); 591 | defaultConfigurationIsVisible = 0; 592 | defaultConfigurationName = Release; 593 | }; 594 | 8F3F43AE293AD07F00B76F99 /* Build configuration list for PBXNativeTarget "StickyHeaderUITests" */ = { 595 | isa = XCConfigurationList; 596 | buildConfigurations = ( 597 | 8F3F43AF293AD07F00B76F99 /* Debug */, 598 | 8F3F43B0293AD07F00B76F99 /* Release */, 599 | ); 600 | defaultConfigurationIsVisible = 0; 601 | defaultConfigurationName = Release; 602 | }; 603 | /* End XCConfigurationList section */ 604 | }; 605 | rootObject = 8F3F437C293AD07B00B76F99 /* Project object */; 606 | } 607 | -------------------------------------------------------------------------------- /StickyHeader.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /StickyHeader.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /StickyHeader.xcodeproj/xcuserdata/lebonbbauma.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | StickyHeader.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /StickyHeader/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /StickyHeader/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /StickyHeader/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /StickyHeader/Assets.xcassets/artwork.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "artwork.jpeg", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /StickyHeader/Assets.xcassets/artwork.imageset/artwork.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Learnin-IOS/StickyHeader/a7d08989394d8eda47d57ed162041359a2faf338/StickyHeader/Assets.xcassets/artwork.imageset/artwork.jpeg -------------------------------------------------------------------------------- /StickyHeader/Assets.xcassets/background.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "18", 9 | "green" : "18", 10 | "red" : "18" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /StickyHeader/Assets.xcassets/spotifyGreen.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "color" : { 5 | "color-space" : "srgb", 6 | "components" : { 7 | "alpha" : "1.000", 8 | "blue" : "96", 9 | "green" : "215", 10 | "red" : "27" 11 | } 12 | }, 13 | "idiom" : "universal" 14 | } 15 | ], 16 | "info" : { 17 | "author" : "xcode", 18 | "version" : 1 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /StickyHeader/ContentView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ContentView.swift 3 | // StickyHeader 4 | // 5 | // Created by Le Bon B' Bauma on 03/12/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct ContentView: View { 11 | var body: some View { 12 | GeometryReader { 13 | let safeArea = $0.safeAreaInsets 14 | let size = $0.size 15 | Home(safeArea: safeArea, size: size) 16 | .ignoresSafeArea(.container, edges: .top) 17 | } 18 | .preferredColorScheme(.dark ) 19 | } 20 | } 21 | 22 | struct ContentView_Previews: PreviewProvider { 23 | static var previews: some View { 24 | ContentView() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /StickyHeader/Model/Album.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Album.swift 3 | // StickyHeader 4 | // 5 | // Created by Le Bon B' Bauma on 03/12/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | 11 | // MARK: - Album Model and Smaple Data 12 | 13 | struct Album: Identifiable{ 14 | var id = UUID().uuidString 15 | var albumName: String 16 | 17 | } 18 | 19 | 20 | var albums: [Album] = [ 21 | 22 | 23 | Album(albumName: "Arsenal des belles mélodies"), 24 | Album(albumName: "Bloqué"), 25 | Album(albumName: "Se Yo"), 26 | Album(albumName: "Droit Chemin"), 27 | Album(albumName: "Destin"), 28 | Album(albumName: "Tokooos II"), 29 | Album(albumName: "Tokooos II Gold"), 30 | Album(albumName: "Science - Fiction"), 31 | Album(albumName: "Strandje Aan De Maas"), 32 | Album(albumName: "Inama"), 33 | Album(albumName: "Par Terre - A COLOR SHOW"), 34 | Album(albumName: "QALF infinity"), 35 | Album(albumName: "Berna Reloaded"), 36 | Album(albumName: "Flavour of Africa"), 37 | Album(albumName: "Control"), 38 | Album(albumName: "Gentleman 2.0"), 39 | Album(albumName: "Power 'Kosa Leka' : Vol 1"), 40 | Album(albumName: "Historia"), 41 | Album(albumName: "Tokooos"), 42 | Album(albumName: "Fleur Froide - Second état : la cristalisation"), 43 | 44 | ] 45 | -------------------------------------------------------------------------------- /StickyHeader/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /StickyHeader/StickyHeaderApp.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StickyHeaderApp.swift 3 | // StickyHeader 4 | // 5 | // Created by Le Bon B' Bauma on 03/12/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | @main 11 | struct StickyHeaderApp: App { 12 | var body: some Scene { 13 | WindowGroup { 14 | ContentView() 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /StickyHeader/View/Home.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Home.swift 3 | // StickyHeader 4 | // 5 | // Created by Le Bon B' Bauma on 03/12/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct Home: View { 11 | 12 | // MARK: - Properties 13 | 14 | var safeArea: EdgeInsets 15 | var size: CGSize 16 | var body: some View { 17 | ScrollView(.vertical, showsIndicators: false) { 18 | VStack{ 19 | // MARK: - Artwork 20 | Artwork() 21 | 22 | // Since We ignored Top Edge 23 | GeometryReader{ proxy in 24 | let minY = proxy.frame(in: .named("SCROLL")).minY - safeArea.top 25 | Button { 26 | 27 | } label: { 28 | Text("SHUFFLE PLAY") 29 | .font(.callout) 30 | .fontWeight(.bold) 31 | .foregroundColor(.white) 32 | .padding(.horizontal, 45) 33 | .padding(.vertical, 12) 34 | .background( 35 | Capsule() 36 | .fill(Color("spotifyGreen").gradient) 37 | ) 38 | } 39 | .frame(maxWidth: .infinity, maxHeight: .infinity) 40 | .offset(y: minY < 50 ? -(minY - 50) : 0) 41 | } 42 | .frame(height: 50) 43 | .padding(.top, -34) 44 | .zIndex(1) 45 | 46 | VStack{ 47 | Text("Popular") 48 | .fontWeight(.heavy) 49 | 50 | // MARK: - Album View 51 | AlbumView() 52 | } 53 | .padding(.top, 10) 54 | .zIndex(0) 55 | 56 | } 57 | .overlay(alignment: .top) { 58 | HeaderView() 59 | } 60 | 61 | } 62 | .coordinateSpace(name: "SCROLL") 63 | } 64 | 65 | @ViewBuilder 66 | func Artwork() -> some View { 67 | let height = size.height * 0.45 68 | GeometryReader{ proxy in 69 | 70 | let size = proxy.size 71 | let minY = proxy.frame(in: .named("SCROLL")).minY 72 | let progress = minY / (height * (minY > 0 ? 0.5 : 0.8)) 73 | 74 | Image("artwork") 75 | .resizable() 76 | .aspectRatio(contentMode: .fill) 77 | .frame(width: size.width, height: size.height + (minY > 0 ? minY : 0 )) 78 | .clipped() 79 | .overlay(content: { 80 | ZStack(alignment: .bottom) { 81 | 82 | // MARK: - Gradient Overlay 83 | Rectangle() 84 | .fill( 85 | .linearGradient(colors: [ 86 | .black.opacity(0 - progress), 87 | .black.opacity(0.1 - progress), 88 | .black.opacity(0.3 - progress), 89 | .black.opacity(0.5 - progress), 90 | .black.opacity(0.8 - progress), 91 | .black.opacity(1), 92 | ], startPoint: .top, endPoint: .bottom) 93 | ) 94 | VStack(spacing: 0) { 95 | Text("Fally\nIpupa") 96 | .font(.system(size: 45)) 97 | .fontWeight(.bold) 98 | .multilineTextAlignment(.center) 99 | 100 | Text("710,329 monthly listeners".uppercased()) 101 | .font(.caption) 102 | .fontWeight(.bold) 103 | .foregroundColor(.gray) 104 | .padding(.top, 15) 105 | } 106 | .opacity(1 + (progress > 0 ? -progress : progress)) 107 | .padding(.bottom, 55) 108 | 109 | // Moving with Scroll View 110 | 111 | .offset(y: minY < 0 ? minY : 0 ) 112 | } 113 | }) 114 | .offset(y: -minY) 115 | 116 | 117 | } 118 | .frame(height: height + safeArea.top ) 119 | } 120 | 121 | @ViewBuilder 122 | func AlbumView() -> some View { 123 | VStack(spacing: 25) { 124 | ForEach(albums.indices, id: \.self) { index in 125 | HStack(spacing: 25) { 126 | Text("\(index + 1)") 127 | .font(.callout) 128 | .fontWeight(.semibold) 129 | .foregroundColor(.gray) 130 | 131 | VStack(alignment: .leading, spacing: 6){ 132 | Text(albums[index].albumName) 133 | .fontWeight(.semibold) 134 | .foregroundColor(.white) 135 | Text("2,282,938") 136 | .font(.caption) 137 | .foregroundColor(.gray) 138 | } 139 | .frame(maxWidth: .infinity, alignment: .leading) 140 | 141 | Image(systemName: "ellipsis") 142 | .foregroundColor(.gray) 143 | 144 | } 145 | 146 | } 147 | } 148 | .padding(15) 149 | } 150 | 151 | 152 | // MARK: - Header View 153 | @ViewBuilder 154 | func HeaderView() -> some View { 155 | GeometryReader{ proxy in 156 | let minY = proxy.frame(in: .named("SCROLL")).minY 157 | let height = size.height * 0.45 158 | let progress = minY / (height * (minY > 0 ? 0.5 : 0.8)) 159 | let titleProgress = minY / height 160 | 161 | HStack(spacing: 15) { 162 | Button { 163 | 164 | } label: { 165 | Image(systemName: "chevron.left") 166 | .font(.title3) 167 | .foregroundColor(.white) 168 | } 169 | Spacer(minLength: 0) 170 | 171 | 172 | Button { 173 | 174 | } label: { 175 | Text("FOLLOWING") 176 | .font(.caption) 177 | .fontWeight(.semibold) 178 | .foregroundColor(.white) 179 | .padding(.horizontal, 10) 180 | .padding(.vertical, 6) 181 | .border(.white, width: 1.5) 182 | } 183 | .opacity(1 + progress) 184 | 185 | Button { 186 | 187 | } label: { 188 | Image(systemName: "ellipsis") 189 | .font(.title3) 190 | .foregroundColor(.white) 191 | } 192 | } 193 | .overlay(content: { 194 | Text("Fally Ipupa") 195 | .fontWeight(.semibold) 196 | .offset(y: -titleProgress > 0.75 ? 0 : 45) 197 | .clipped() 198 | .animation(.easeOut(duration: 0.25), value: -titleProgress > 0.75) 199 | }) 200 | .padding(.top, safeArea.top + 10) 201 | .padding([.horizontal,.bottom], 15) 202 | .background( 203 | Color.black 204 | .opacity(-progress > 1 ? 1 : 0) 205 | ) 206 | .offset(y: -minY) 207 | 208 | 209 | 210 | } 211 | .frame(height: 35) 212 | } 213 | } 214 | 215 | struct Home_Previews: PreviewProvider { 216 | static var previews: some View { 217 | ContentView() 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /StickyHeaderTests/StickyHeaderTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StickyHeaderTests.swift 3 | // StickyHeaderTests 4 | // 5 | // Created by Le Bon B' Bauma on 03/12/2022. 6 | // 7 | 8 | import XCTest 9 | @testable import StickyHeader 10 | 11 | final class StickyHeaderTests: XCTestCase { 12 | 13 | override func setUpWithError() throws { 14 | // Put setup code here. This method is called before the invocation of each test method in the class. 15 | } 16 | 17 | override func tearDownWithError() throws { 18 | // Put teardown code here. This method is called after the invocation of each test method in the class. 19 | } 20 | 21 | func testExample() throws { 22 | // This is an example of a functional test case. 23 | // Use XCTAssert and related functions to verify your tests produce the correct results. 24 | // Any test you write for XCTest can be annotated as throws and async. 25 | // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. 26 | // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. 27 | } 28 | 29 | func testPerformanceExample() throws { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /StickyHeaderUITests/StickyHeaderUITests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StickyHeaderUITests.swift 3 | // StickyHeaderUITests 4 | // 5 | // Created by Le Bon B' Bauma on 03/12/2022. 6 | // 7 | 8 | import XCTest 9 | 10 | final class StickyHeaderUITests: XCTestCase { 11 | 12 | override func setUpWithError() throws { 13 | // Put setup code here. This method is called before the invocation of each test method in the class. 14 | 15 | // In UI tests it is usually best to stop immediately when a failure occurs. 16 | continueAfterFailure = false 17 | 18 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this. 19 | } 20 | 21 | override func tearDownWithError() throws { 22 | // Put teardown code here. This method is called after the invocation of each test method in the class. 23 | } 24 | 25 | func testExample() throws { 26 | // UI tests must launch the application that they test. 27 | let app = XCUIApplication() 28 | app.launch() 29 | 30 | // Use XCTAssert and related functions to verify your tests produce the correct results. 31 | } 32 | 33 | func testLaunchPerformance() throws { 34 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) { 35 | // This measures how long it takes to launch your application. 36 | measure(metrics: [XCTApplicationLaunchMetric()]) { 37 | XCUIApplication().launch() 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /StickyHeaderUITests/StickyHeaderUITestsLaunchTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StickyHeaderUITestsLaunchTests.swift 3 | // StickyHeaderUITests 4 | // 5 | // Created by Le Bon B' Bauma on 03/12/2022. 6 | // 7 | 8 | import XCTest 9 | 10 | final class StickyHeaderUITestsLaunchTests: XCTestCase { 11 | 12 | override class var runsForEachTargetApplicationUIConfiguration: Bool { 13 | true 14 | } 15 | 16 | override func setUpWithError() throws { 17 | continueAfterFailure = false 18 | } 19 | 20 | func testLaunch() throws { 21 | let app = XCUIApplication() 22 | app.launch() 23 | 24 | // Insert steps here to perform after app launch but before taking a screenshot, 25 | // such as logging into a test account or navigating somewhere in the app 26 | 27 | let attachment = XCTAttachment(screenshot: app.screenshot()) 28 | attachment.name = "Launch Screen" 29 | attachment.lifetime = .keepAlways 30 | add(attachment) 31 | } 32 | } 33 | --------------------------------------------------------------------------------