├── .gitignore ├── GrowingHeader.gif ├── README.md ├── StretchyCollection.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata └── StretchyCollection ├── AppDelegate.swift ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json ├── Contents.json └── Photo.imageset │ ├── Contents.json │ └── IMG_8179.JPG ├── Base.lproj └── LaunchScreen.storyboard ├── Info.plist ├── StretchyCollectionViewLayout.swift ├── StretchyHeaderView.swift └── ViewController.swift /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata 19 | 20 | ## Other 21 | *.xccheckout 22 | *.moved-aside 23 | *.xcuserstate 24 | *.xcscmblueprint 25 | .DS_Store 26 | 27 | ## Obj-C/Swift specific 28 | *.hmap 29 | *.ipa 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | .build/ 40 | 41 | # CocoaPods 42 | # 43 | # We recommend against adding the Pods directory to your .gitignore. However 44 | # you should judge for yourself, the pros and cons are mentioned at: 45 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 46 | # 47 | # Pods/ 48 | 49 | # Carthage 50 | # 51 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 52 | # Carthage/Checkouts 53 | 54 | Carthage/Build 55 | 56 | # fastlane 57 | # 58 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 59 | # screenshots whenever they are needed. 60 | # For more information about the recommended setup visit: 61 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 62 | 63 | fastlane/report.xml 64 | fastlane/screenshots 65 | -------------------------------------------------------------------------------- /GrowingHeader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmperiorEric/StretchyHeaderCollectionViewLayout/47b8611dd2c1ec2dac8387b2eaebec18733b3a75/GrowingHeader.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # How to add a stretchy flair to your UICollectionView 2 | This repo is sample code for [this tutorial](https://medium.com/p/e403822e0f33/edit) 3 | 4 | ![Stretchy Header](GrowingHeader.gif) -------------------------------------------------------------------------------- /StretchyCollection.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | EF9739F31C4F0C15009E9CE9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF9739F21C4F0C15009E9CE9 /* AppDelegate.swift */; }; 11 | EF9739F51C4F0C15009E9CE9 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF9739F41C4F0C15009E9CE9 /* ViewController.swift */; }; 12 | EF9739FA1C4F0C15009E9CE9 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EF9739F91C4F0C15009E9CE9 /* Assets.xcassets */; }; 13 | EF9739FD1C4F0C15009E9CE9 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EF9739FB1C4F0C15009E9CE9 /* LaunchScreen.storyboard */; }; 14 | EF973A051C4F0DA3009E9CE9 /* StretchyCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF973A041C4F0DA3009E9CE9 /* StretchyCollectionViewLayout.swift */; }; 15 | EF973A071C4F110D009E9CE9 /* StretchyHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EF973A061C4F110D009E9CE9 /* StretchyHeaderView.swift */; }; 16 | /* End PBXBuildFile section */ 17 | 18 | /* Begin PBXFileReference section */ 19 | EF9739EF1C4F0C15009E9CE9 /* StretchyCollection.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StretchyCollection.app; sourceTree = BUILT_PRODUCTS_DIR; }; 20 | EF9739F21C4F0C15009E9CE9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 21 | EF9739F41C4F0C15009E9CE9 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 22 | EF9739F91C4F0C15009E9CE9 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 23 | EF9739FC1C4F0C15009E9CE9 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 24 | EF9739FE1C4F0C15009E9CE9 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 25 | EF973A041C4F0DA3009E9CE9 /* StretchyCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StretchyCollectionViewLayout.swift; sourceTree = ""; }; 26 | EF973A061C4F110D009E9CE9 /* StretchyHeaderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StretchyHeaderView.swift; sourceTree = ""; }; 27 | /* End PBXFileReference section */ 28 | 29 | /* Begin PBXFrameworksBuildPhase section */ 30 | EF9739EC1C4F0C15009E9CE9 /* Frameworks */ = { 31 | isa = PBXFrameworksBuildPhase; 32 | buildActionMask = 2147483647; 33 | files = ( 34 | ); 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXFrameworksBuildPhase section */ 38 | 39 | /* Begin PBXGroup section */ 40 | EF9739E61C4F0C15009E9CE9 = { 41 | isa = PBXGroup; 42 | children = ( 43 | EF9739F11C4F0C15009E9CE9 /* StretchyCollection */, 44 | EF9739F01C4F0C15009E9CE9 /* Products */, 45 | ); 46 | sourceTree = ""; 47 | }; 48 | EF9739F01C4F0C15009E9CE9 /* Products */ = { 49 | isa = PBXGroup; 50 | children = ( 51 | EF9739EF1C4F0C15009E9CE9 /* StretchyCollection.app */, 52 | ); 53 | name = Products; 54 | sourceTree = ""; 55 | }; 56 | EF9739F11C4F0C15009E9CE9 /* StretchyCollection */ = { 57 | isa = PBXGroup; 58 | children = ( 59 | EF9739F21C4F0C15009E9CE9 /* AppDelegate.swift */, 60 | EF9739F41C4F0C15009E9CE9 /* ViewController.swift */, 61 | EF973A061C4F110D009E9CE9 /* StretchyHeaderView.swift */, 62 | EF973A041C4F0DA3009E9CE9 /* StretchyCollectionViewLayout.swift */, 63 | EF9739F91C4F0C15009E9CE9 /* Assets.xcassets */, 64 | EF9739FB1C4F0C15009E9CE9 /* LaunchScreen.storyboard */, 65 | EF9739FE1C4F0C15009E9CE9 /* Info.plist */, 66 | ); 67 | path = StretchyCollection; 68 | sourceTree = ""; 69 | }; 70 | /* End PBXGroup section */ 71 | 72 | /* Begin PBXNativeTarget section */ 73 | EF9739EE1C4F0C15009E9CE9 /* StretchyCollection */ = { 74 | isa = PBXNativeTarget; 75 | buildConfigurationList = EF973A011C4F0C15009E9CE9 /* Build configuration list for PBXNativeTarget "StretchyCollection" */; 76 | buildPhases = ( 77 | EF9739EB1C4F0C15009E9CE9 /* Sources */, 78 | EF9739EC1C4F0C15009E9CE9 /* Frameworks */, 79 | EF9739ED1C4F0C15009E9CE9 /* Resources */, 80 | ); 81 | buildRules = ( 82 | ); 83 | dependencies = ( 84 | ); 85 | name = StretchyCollection; 86 | productName = StretchyCollection; 87 | productReference = EF9739EF1C4F0C15009E9CE9 /* StretchyCollection.app */; 88 | productType = "com.apple.product-type.application"; 89 | }; 90 | /* End PBXNativeTarget section */ 91 | 92 | /* Begin PBXProject section */ 93 | EF9739E71C4F0C15009E9CE9 /* Project object */ = { 94 | isa = PBXProject; 95 | attributes = { 96 | LastSwiftUpdateCheck = 0720; 97 | LastUpgradeCheck = 0720; 98 | ORGANIZATIONNAME = "Frozen Fire Studios, Inc."; 99 | TargetAttributes = { 100 | EF9739EE1C4F0C15009E9CE9 = { 101 | CreatedOnToolsVersion = 7.2; 102 | }; 103 | }; 104 | }; 105 | buildConfigurationList = EF9739EA1C4F0C15009E9CE9 /* Build configuration list for PBXProject "StretchyCollection" */; 106 | compatibilityVersion = "Xcode 3.2"; 107 | developmentRegion = English; 108 | hasScannedForEncodings = 0; 109 | knownRegions = ( 110 | en, 111 | Base, 112 | ); 113 | mainGroup = EF9739E61C4F0C15009E9CE9; 114 | productRefGroup = EF9739F01C4F0C15009E9CE9 /* Products */; 115 | projectDirPath = ""; 116 | projectRoot = ""; 117 | targets = ( 118 | EF9739EE1C4F0C15009E9CE9 /* StretchyCollection */, 119 | ); 120 | }; 121 | /* End PBXProject section */ 122 | 123 | /* Begin PBXResourcesBuildPhase section */ 124 | EF9739ED1C4F0C15009E9CE9 /* Resources */ = { 125 | isa = PBXResourcesBuildPhase; 126 | buildActionMask = 2147483647; 127 | files = ( 128 | EF9739FD1C4F0C15009E9CE9 /* LaunchScreen.storyboard in Resources */, 129 | EF9739FA1C4F0C15009E9CE9 /* Assets.xcassets in Resources */, 130 | ); 131 | runOnlyForDeploymentPostprocessing = 0; 132 | }; 133 | /* End PBXResourcesBuildPhase section */ 134 | 135 | /* Begin PBXSourcesBuildPhase section */ 136 | EF9739EB1C4F0C15009E9CE9 /* Sources */ = { 137 | isa = PBXSourcesBuildPhase; 138 | buildActionMask = 2147483647; 139 | files = ( 140 | EF9739F51C4F0C15009E9CE9 /* ViewController.swift in Sources */, 141 | EF973A071C4F110D009E9CE9 /* StretchyHeaderView.swift in Sources */, 142 | EF973A051C4F0DA3009E9CE9 /* StretchyCollectionViewLayout.swift in Sources */, 143 | EF9739F31C4F0C15009E9CE9 /* AppDelegate.swift in Sources */, 144 | ); 145 | runOnlyForDeploymentPostprocessing = 0; 146 | }; 147 | /* End PBXSourcesBuildPhase section */ 148 | 149 | /* Begin PBXVariantGroup section */ 150 | EF9739FB1C4F0C15009E9CE9 /* LaunchScreen.storyboard */ = { 151 | isa = PBXVariantGroup; 152 | children = ( 153 | EF9739FC1C4F0C15009E9CE9 /* Base */, 154 | ); 155 | name = LaunchScreen.storyboard; 156 | sourceTree = ""; 157 | }; 158 | /* End PBXVariantGroup section */ 159 | 160 | /* Begin XCBuildConfiguration section */ 161 | EF9739FF1C4F0C15009E9CE9 /* Debug */ = { 162 | isa = XCBuildConfiguration; 163 | buildSettings = { 164 | ALWAYS_SEARCH_USER_PATHS = NO; 165 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 166 | CLANG_CXX_LIBRARY = "libc++"; 167 | CLANG_ENABLE_MODULES = YES; 168 | CLANG_ENABLE_OBJC_ARC = YES; 169 | CLANG_WARN_BOOL_CONVERSION = YES; 170 | CLANG_WARN_CONSTANT_CONVERSION = YES; 171 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 172 | CLANG_WARN_EMPTY_BODY = YES; 173 | CLANG_WARN_ENUM_CONVERSION = YES; 174 | CLANG_WARN_INT_CONVERSION = YES; 175 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 176 | CLANG_WARN_UNREACHABLE_CODE = YES; 177 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 178 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 179 | COPY_PHASE_STRIP = NO; 180 | DEBUG_INFORMATION_FORMAT = dwarf; 181 | ENABLE_STRICT_OBJC_MSGSEND = YES; 182 | ENABLE_TESTABILITY = YES; 183 | GCC_C_LANGUAGE_STANDARD = gnu99; 184 | GCC_DYNAMIC_NO_PIC = NO; 185 | GCC_NO_COMMON_BLOCKS = YES; 186 | GCC_OPTIMIZATION_LEVEL = 0; 187 | GCC_PREPROCESSOR_DEFINITIONS = ( 188 | "DEBUG=1", 189 | "$(inherited)", 190 | ); 191 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 192 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 193 | GCC_WARN_UNDECLARED_SELECTOR = YES; 194 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 195 | GCC_WARN_UNUSED_FUNCTION = YES; 196 | GCC_WARN_UNUSED_VARIABLE = YES; 197 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 198 | MTL_ENABLE_DEBUG_INFO = YES; 199 | ONLY_ACTIVE_ARCH = YES; 200 | SDKROOT = iphoneos; 201 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 202 | }; 203 | name = Debug; 204 | }; 205 | EF973A001C4F0C15009E9CE9 /* Release */ = { 206 | isa = XCBuildConfiguration; 207 | buildSettings = { 208 | ALWAYS_SEARCH_USER_PATHS = NO; 209 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 210 | CLANG_CXX_LIBRARY = "libc++"; 211 | CLANG_ENABLE_MODULES = YES; 212 | CLANG_ENABLE_OBJC_ARC = YES; 213 | CLANG_WARN_BOOL_CONVERSION = YES; 214 | CLANG_WARN_CONSTANT_CONVERSION = YES; 215 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 216 | CLANG_WARN_EMPTY_BODY = YES; 217 | CLANG_WARN_ENUM_CONVERSION = YES; 218 | CLANG_WARN_INT_CONVERSION = YES; 219 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 220 | CLANG_WARN_UNREACHABLE_CODE = YES; 221 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 222 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 223 | COPY_PHASE_STRIP = NO; 224 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 225 | ENABLE_NS_ASSERTIONS = NO; 226 | ENABLE_STRICT_OBJC_MSGSEND = YES; 227 | GCC_C_LANGUAGE_STANDARD = gnu99; 228 | GCC_NO_COMMON_BLOCKS = YES; 229 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 230 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 231 | GCC_WARN_UNDECLARED_SELECTOR = YES; 232 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 233 | GCC_WARN_UNUSED_FUNCTION = YES; 234 | GCC_WARN_UNUSED_VARIABLE = YES; 235 | IPHONEOS_DEPLOYMENT_TARGET = 9.2; 236 | MTL_ENABLE_DEBUG_INFO = NO; 237 | SDKROOT = iphoneos; 238 | VALIDATE_PRODUCT = YES; 239 | }; 240 | name = Release; 241 | }; 242 | EF973A021C4F0C15009E9CE9 /* Debug */ = { 243 | isa = XCBuildConfiguration; 244 | buildSettings = { 245 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 246 | INFOPLIST_FILE = StretchyCollection/Info.plist; 247 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 248 | PRODUCT_BUNDLE_IDENTIFIER = com.frozenfirestudios.StretchyCollection; 249 | PRODUCT_NAME = "$(TARGET_NAME)"; 250 | }; 251 | name = Debug; 252 | }; 253 | EF973A031C4F0C15009E9CE9 /* Release */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 257 | INFOPLIST_FILE = StretchyCollection/Info.plist; 258 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 259 | PRODUCT_BUNDLE_IDENTIFIER = com.frozenfirestudios.StretchyCollection; 260 | PRODUCT_NAME = "$(TARGET_NAME)"; 261 | }; 262 | name = Release; 263 | }; 264 | /* End XCBuildConfiguration section */ 265 | 266 | /* Begin XCConfigurationList section */ 267 | EF9739EA1C4F0C15009E9CE9 /* Build configuration list for PBXProject "StretchyCollection" */ = { 268 | isa = XCConfigurationList; 269 | buildConfigurations = ( 270 | EF9739FF1C4F0C15009E9CE9 /* Debug */, 271 | EF973A001C4F0C15009E9CE9 /* Release */, 272 | ); 273 | defaultConfigurationIsVisible = 0; 274 | defaultConfigurationName = Release; 275 | }; 276 | EF973A011C4F0C15009E9CE9 /* Build configuration list for PBXNativeTarget "StretchyCollection" */ = { 277 | isa = XCConfigurationList; 278 | buildConfigurations = ( 279 | EF973A021C4F0C15009E9CE9 /* Debug */, 280 | EF973A031C4F0C15009E9CE9 /* Release */, 281 | ); 282 | defaultConfigurationIsVisible = 0; 283 | }; 284 | /* End XCConfigurationList section */ 285 | }; 286 | rootObject = EF9739E71C4F0C15009E9CE9 /* Project object */; 287 | } 288 | -------------------------------------------------------------------------------- /StretchyCollection.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /StretchyCollection/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // StretchyCollection 4 | // 5 | // Created by Ryan Poolos on 1/19/16. 6 | // Copyright © 2016 Frozen Fire Studios, Inc. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 18 | 19 | let initialViewController = ViewController() 20 | let navController = UINavigationController(rootViewController: initialViewController) 21 | 22 | window = UIWindow(frame: UIScreen.mainScreen().bounds) 23 | window?.rootViewController = navController 24 | window?.backgroundColor = UIColor.blackColor() 25 | window?.makeKeyAndVisible() 26 | 27 | return true 28 | } 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /StretchyCollection/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /StretchyCollection/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /StretchyCollection/Assets.xcassets/Photo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "IMG_8179.JPG", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "version" : 1, 19 | "author" : "xcode" 20 | } 21 | } -------------------------------------------------------------------------------- /StretchyCollection/Assets.xcassets/Photo.imageset/IMG_8179.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EmperiorEric/StretchyHeaderCollectionViewLayout/47b8611dd2c1ec2dac8387b2eaebec18733b3a75/StretchyCollection/Assets.xcassets/Photo.imageset/IMG_8179.JPG -------------------------------------------------------------------------------- /StretchyCollection/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 | 27 | 28 | -------------------------------------------------------------------------------- /StretchyCollection/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /StretchyCollection/StretchyCollectionViewLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StretchyCollectionViewLayout.swift 3 | // StretchyCollection 4 | // 5 | // Created by Ryan Poolos on 1/19/16. 6 | // Copyright © 2016 Frozen Fire Studios, Inc. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | let StretchyCollectionHeaderKind = "StretchyCollectionHeaderKind" 12 | 13 | class StretchyCollectionViewLayout: UICollectionViewLayout { 14 | 15 | let startingHeaderHeight: CGFloat = 128.0 16 | 17 | var sectionInset = UIEdgeInsetsZero 18 | var itemSize = CGSize.zero 19 | var itemSpacing: CGFloat = 0.0 20 | 21 | var attributes: [UICollectionViewLayoutAttributes] = [] 22 | 23 | override func prepareLayout() { 24 | super.prepareLayout() 25 | 26 | // Start with a fresh array of attributes 27 | attributes = [] 28 | 29 | // Can't do much without a collectionView. 30 | guard let collectionView = collectionView else { 31 | return 32 | } 33 | 34 | let numberOfSections = collectionView.numberOfSections() 35 | 36 | for section in 0.. Bool { 54 | return true 55 | } 56 | 57 | override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? { 58 | let visibleAttributes = attributes.filter { attribute -> Bool in 59 | return rect.contains(attribute.frame) || rect.intersects(attribute.frame) 60 | } 61 | 62 | // Check for our Stretchy Header 63 | // We want to find a collectionHeader and stretch it while scrolling. 64 | // But first lets make sure we've scrolled far enough. 65 | let offset = collectionView?.contentOffset ?? CGPoint.zero 66 | let minY = -sectionInset.top 67 | if offset.y < minY { 68 | let extraOffset = fabs(offset.y - minY) 69 | 70 | // Find our collectionHeader and stretch it while scrolling. 71 | let stretchyHeader = visibleAttributes.filter { attribute -> Bool in 72 | return attribute.representedElementKind == StretchyCollectionHeaderKind 73 | }.first 74 | 75 | if let collectionHeader = stretchyHeader { 76 | let headerSize = collectionHeader.frame.size 77 | collectionHeader.frame.size.height = max(minY, headerSize.height + extraOffset) 78 | collectionHeader.frame.origin.y = collectionHeader.frame.origin.y - extraOffset 79 | } 80 | } 81 | 82 | return visibleAttributes 83 | } 84 | 85 | override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { 86 | guard let collectionView = collectionView else { 87 | return nil 88 | } 89 | 90 | let attribute = UICollectionViewLayoutAttributes(forCellWithIndexPath: indexPath) 91 | 92 | var sectionOriginY = startingHeaderHeight + sectionInset.top 93 | 94 | if indexPath.section > 0 { 95 | let previousSection = indexPath.section - 1 96 | let lastItem = collectionView.numberOfItemsInSection(previousSection) - 1 97 | let previousCell = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: lastItem, inSection: previousSection)) 98 | sectionOriginY = (previousCell?.frame.maxY ?? 0) + sectionInset.bottom 99 | } 100 | 101 | let itemOriginY = sectionOriginY + CGFloat(indexPath.item) * (itemSize.height + itemSpacing) 102 | 103 | attribute.frame = CGRect(x: sectionInset.left, y: itemOriginY, width: itemSize.width, height: itemSize.height) 104 | 105 | return attribute 106 | } 107 | 108 | override func layoutAttributesForSupplementaryViewOfKind(elementKind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? { 109 | guard let collectionView = collectionView else { 110 | return nil 111 | } 112 | 113 | let attribute = UICollectionViewLayoutAttributes(forSupplementaryViewOfKind: StretchyCollectionHeaderKind, withIndexPath: indexPath) 114 | attribute.frame = CGRect(x: 0, y: 0, width: collectionView.frame.width, height: startingHeaderHeight) 115 | return attribute 116 | } 117 | 118 | override func collectionViewContentSize() -> CGSize { 119 | guard let collectionView = collectionView else { 120 | return CGSize.zero 121 | } 122 | 123 | let numberOfSections = collectionView.numberOfSections() 124 | let lastSection = numberOfSections - 1 125 | let numberOfItems = collectionView.numberOfItemsInSection(lastSection) 126 | let lastItem = numberOfItems - 1 127 | 128 | guard let lastCell = layoutAttributesForItemAtIndexPath(NSIndexPath(forItem: lastItem, inSection: lastSection)) else { 129 | return CGSize.zero 130 | } 131 | 132 | return CGSize(width: collectionView.frame.width, height: lastCell.frame.maxY + sectionInset.bottom) 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /StretchyCollection/StretchyHeaderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // StretchyHeaderView.swift 3 | // StretchyCollection 4 | // 5 | // Created by Ryan Poolos on 1/19/16. 6 | // Copyright © 2016 Frozen Fire Studios, Inc. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class StretchyHeaderView: UICollectionReusableView { 12 | 13 | override init(frame: CGRect) { 14 | super.init(frame: frame) 15 | 16 | clipsToBounds = true 17 | 18 | addSubview(imageView) 19 | 20 | let views = ["imageView": imageView] 21 | 22 | NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[imageView]|", options: [], metrics: nil, views: views)) 23 | NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[imageView]|", options: [], metrics: nil, views: views)) 24 | } 25 | 26 | required init?(coder aDecoder: NSCoder) { 27 | fatalError("Storyboards Ugh") 28 | } 29 | 30 | override func prepareForReuse() { 31 | super.prepareForReuse() 32 | 33 | imageView.image = nil 34 | } 35 | 36 | //========================================================================== 37 | // MARK: - Views 38 | //========================================================================== 39 | 40 | lazy var imageView: UIImageView = { 41 | let imageView = UIImageView() 42 | imageView.translatesAutoresizingMaskIntoConstraints = false 43 | imageView.contentMode = .ScaleAspectFill 44 | return imageView 45 | }() 46 | } 47 | -------------------------------------------------------------------------------- /StretchyCollection/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // StretchyCollection 4 | // 5 | // Created by Ryan Poolos on 1/19/16. 6 | // Copyright © 2016 Frozen Fire Studios, Inc. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate { 12 | 13 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { 14 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 15 | 16 | title = "Stretchy Header" 17 | } 18 | 19 | required init?(coder aDecoder: NSCoder) { 20 | fatalError("Storyboards Ugh") 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | collectionView.backgroundColor = UIColor.lightGrayColor() 27 | 28 | view.addSubview(collectionView) 29 | 30 | let views = ["collectionView": collectionView] 31 | 32 | NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[collectionView]|", options: [], metrics: nil, views: views)) 33 | NSLayoutConstraint.activateConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[collectionView]|", options: [], metrics: nil, views: views)) 34 | } 35 | 36 | override func viewWillTransitionToSize(size: CGSize, withTransitionCoordinator coordinator: UIViewControllerTransitionCoordinator) { 37 | super.viewWillTransitionToSize(size, withTransitionCoordinator: coordinator) 38 | 39 | collectionLayout.itemSize = CGSize(width: size.width - (padding * 2.0), height: 64.0) 40 | } 41 | 42 | //========================================================================== 43 | // MARK: - UICollectionViewDataSource 44 | //========================================================================== 45 | 46 | private let cellIdentifier = "UniqueCellIdentifier" 47 | private let headerIdentifier = "UniqueHeaderIdentifier" 48 | 49 | func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { 50 | return 4 51 | } 52 | 53 | func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 54 | return 16 55 | } 56 | 57 | func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { 58 | let cell = collectionView.dequeueReusableCellWithReuseIdentifier(cellIdentifier, forIndexPath: indexPath) 59 | cell.backgroundColor = UIColor.whiteColor() 60 | return cell 61 | } 62 | 63 | func collectionView(collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, atIndexPath indexPath: NSIndexPath) -> UICollectionReusableView { 64 | let view = collectionView.dequeueReusableSupplementaryViewOfKind(kind, withReuseIdentifier: headerIdentifier, forIndexPath: indexPath) as! StretchyHeaderView 65 | view.imageView.image = UIImage(named: "Photo") 66 | return view 67 | } 68 | 69 | //========================================================================== 70 | // MARK: - Views 71 | //========================================================================== 72 | 73 | let padding: CGFloat = 8.0 74 | 75 | lazy var collectionLayout: StretchyCollectionViewLayout = { [unowned self] in 76 | let layout = StretchyCollectionViewLayout() 77 | layout.itemSpacing = self.padding 78 | layout.itemSize = CGSize(width: self.view.bounds.width - (self.padding * 2.0), height: 64.0) 79 | layout.sectionInset = UIEdgeInsets(top: self.padding, left: self.padding, bottom: 32.0, right: self.padding) 80 | return layout 81 | }() 82 | 83 | lazy var collectionView: UICollectionView = { [unowned self] in 84 | let collectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: self.collectionLayout) 85 | collectionView.translatesAutoresizingMaskIntoConstraints = false 86 | 87 | collectionView.dataSource = self 88 | collectionView.delegate = self 89 | 90 | collectionView.backgroundColor = UIColor.lightGrayColor() 91 | 92 | collectionView.registerClass(UICollectionViewCell.self, forCellWithReuseIdentifier: self.cellIdentifier) 93 | collectionView.registerClass(StretchyHeaderView.self, forSupplementaryViewOfKind: StretchyCollectionHeaderKind, withReuseIdentifier: self.headerIdentifier) 94 | 95 | return collectionView 96 | }() 97 | } 98 | 99 | --------------------------------------------------------------------------------