├── .gitignore ├── README.md ├── Screenshots ├── Screenshot-1.png ├── Screenshot-2.png ├── Screenshot-3.png └── Screenshot-4.png ├── WaterfallCollectionView.xcodeproj ├── project.pbxproj └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── WaterfallCollectionView.xccheckout └── WaterfallCollectionView ├── Default-568h@2x.png ├── Default.png ├── Default@2x.png ├── EIAppDelegate.h ├── EIAppDelegate.m ├── EIViewController.h ├── EIViewController.m ├── FRGWaterfallCollectionViewCell.h ├── FRGWaterfallCollectionViewCell.m ├── FRGWaterfallCollectionViewLayout.h ├── FRGWaterfallCollectionViewLayout.m ├── FRGWaterfallDecorationReusableView.h ├── FRGWaterfallDecorationReusableView.m ├── FRGWaterfallHeaderReusableView.h ├── FRGWaterfallHeaderReusableView.m ├── WaterfallCollectionView-Info.plist ├── WaterfallCollectionView-Prefix.pch ├── decorationImage.png ├── decorationImage@2x.png ├── en.lproj ├── InfoPlist.strings └── MainStoryboard.storyboard └── main.m /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | .DS_Store 3 | */build/* 4 | *.pbxuser 5 | !default.pbxuser 6 | *.mode1v3 7 | !default.mode1v3 8 | *.mode2v3 9 | !default.mode2v3 10 | *.perspectivev3 11 | !default.perspectivev3 12 | xcuserdata 13 | profile 14 | *.moved-aside 15 | DerivedData 16 | .idea/ 17 | *.hmap 18 | 19 | #CocoaPods 20 | Pods -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WaterfallCollectionView 2 | ======================= 3 | 4 | WaterfallCollectionView is custom layout for UICollectionView, based on Pinterest style. It supports (sticky or not) header supplementary view and decoration view. 5 | 6 | ...and now It's in development phase. :) 7 | 8 | Sample usage 9 | ------------ 10 | 11 | Setup FRGWaterfallCollectionViewLayout as UICollectionView layout in you view controller. 12 | 13 | ``` objective-c 14 | /*...*/ 15 | FRGWaterfallCollectionViewLayout *cvLayout = [[FRGWaterfallCollectionViewLayout alloc] init]; 16 | cvLayout.delegate = self; 17 | 18 | cvLayout.itemWidth = 140.0f; 19 | cvLayout.topInset = 10.0f; 20 | cvLayout.bottomInset = 10.0f; 21 | cvLayout.stickyHeader = YES; 22 | 23 | [self.cv setCollectionViewLayout:cvLayout]; //cv is UICollectionView property 24 | /*...*/ 25 | ``` 26 | 27 | If you want to show section header you should implement delegate method 28 | 29 | ``` objective-c 30 | - (CGFloat) collectionView:(UICollectionView *)collectionView 31 | layout:(FRGWaterfallCollectionViewLayout *)collectionViewLayout 32 | heightForHeaderAtIndexPath:(NSIndexPath *)indexPath; 33 | ``` 34 | 35 | Screenshots 36 | ------------ 37 | ![Sections](https://raw.github.com/frogermcs/WaterfallCollectionView/master/Screenshots/Screenshot-1.png) 38 | 39 | ![Decoration view](https://raw.github.com/frogermcs/WaterfallCollectionView/master/Screenshots/Screenshot-2.png) 40 | 41 | ![Horizontal orientation](https://raw.github.com/frogermcs/WaterfallCollectionView/master/Screenshots/Screenshot-3.png) 42 | 43 | ![Sticky header](https://raw.github.com/frogermcs/WaterfallCollectionView/master/Screenshots/Screenshot-4.png) 44 | 45 | -------------------------------------------------------------------------------- /Screenshots/Screenshot-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SureCase/WaterfallCollectionView/8fd8b24d26519d26591fbcae67a1aa72171e0196/Screenshots/Screenshot-1.png -------------------------------------------------------------------------------- /Screenshots/Screenshot-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SureCase/WaterfallCollectionView/8fd8b24d26519d26591fbcae67a1aa72171e0196/Screenshots/Screenshot-2.png -------------------------------------------------------------------------------- /Screenshots/Screenshot-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SureCase/WaterfallCollectionView/8fd8b24d26519d26591fbcae67a1aa72171e0196/Screenshots/Screenshot-3.png -------------------------------------------------------------------------------- /Screenshots/Screenshot-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SureCase/WaterfallCollectionView/8fd8b24d26519d26591fbcae67a1aa72171e0196/Screenshots/Screenshot-4.png -------------------------------------------------------------------------------- /WaterfallCollectionView.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9976E3FD178FFE3F005966E8 /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9976E3FC178FFE3F005966E8 /* UIKit.framework */; }; 11 | 9976E3FF178FFE3F005966E8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9976E3FE178FFE3F005966E8 /* Foundation.framework */; }; 12 | 9976E401178FFE3F005966E8 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9976E400178FFE3F005966E8 /* CoreGraphics.framework */; }; 13 | 9976E407178FFE3F005966E8 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 9976E405178FFE3F005966E8 /* InfoPlist.strings */; }; 14 | 9976E409178FFE3F005966E8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9976E408178FFE3F005966E8 /* main.m */; }; 15 | 9976E40D178FFE3F005966E8 /* EIAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9976E40C178FFE3F005966E8 /* EIAppDelegate.m */; }; 16 | 9976E40F178FFE3F005966E8 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = 9976E40E178FFE3F005966E8 /* Default.png */; }; 17 | 9976E411178FFE3F005966E8 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9976E410178FFE3F005966E8 /* Default@2x.png */; }; 18 | 9976E413178FFE3F005966E8 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9976E412178FFE3F005966E8 /* Default-568h@2x.png */; }; 19 | 9976E416178FFE3F005966E8 /* MainStoryboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9976E414178FFE3F005966E8 /* MainStoryboard.storyboard */; }; 20 | 9976E419178FFE3F005966E8 /* EIViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9976E418178FFE3F005966E8 /* EIViewController.m */; }; 21 | 9976E427179022B9005966E8 /* FRGWaterfallCollectionViewLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = 9976E426179022B9005966E8 /* FRGWaterfallCollectionViewLayout.m */; }; 22 | 9976E42A1790238C005966E8 /* FRGWaterfallCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 9976E4291790238C005966E8 /* FRGWaterfallCollectionViewCell.m */; }; 23 | 9976E42D1790819E005966E8 /* FRGWaterfallHeaderReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9976E42C1790819E005966E8 /* FRGWaterfallHeaderReusableView.m */; }; 24 | 9986EA701792A00800850AF3 /* FRGWaterfallDecorationReusableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 9986EA6F1792A00800850AF3 /* FRGWaterfallDecorationReusableView.m */; }; 25 | 9986EA731792A13000850AF3 /* decorationImage.png in Resources */ = {isa = PBXBuildFile; fileRef = 9986EA711792A13000850AF3 /* decorationImage.png */; }; 26 | 9986EA741792A13000850AF3 /* decorationImage@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9986EA721792A13000850AF3 /* decorationImage@2x.png */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 9976E3F9178FFE3F005966E8 /* WaterfallCollectionView.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WaterfallCollectionView.app; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | 9976E3FC178FFE3F005966E8 /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 32 | 9976E3FE178FFE3F005966E8 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 33 | 9976E400178FFE3F005966E8 /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 34 | 9976E404178FFE3F005966E8 /* WaterfallCollectionView-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "WaterfallCollectionView-Info.plist"; sourceTree = ""; }; 35 | 9976E406178FFE3F005966E8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 36 | 9976E408178FFE3F005966E8 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 37 | 9976E40A178FFE3F005966E8 /* WaterfallCollectionView-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "WaterfallCollectionView-Prefix.pch"; sourceTree = ""; }; 38 | 9976E40B178FFE3F005966E8 /* EIAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EIAppDelegate.h; sourceTree = ""; }; 39 | 9976E40C178FFE3F005966E8 /* EIAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EIAppDelegate.m; sourceTree = ""; }; 40 | 9976E40E178FFE3F005966E8 /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 41 | 9976E410178FFE3F005966E8 /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 42 | 9976E412178FFE3F005966E8 /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 43 | 9976E415178FFE3F005966E8 /* en */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = en; path = en.lproj/MainStoryboard.storyboard; sourceTree = ""; }; 44 | 9976E417178FFE3F005966E8 /* EIViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EIViewController.h; sourceTree = ""; }; 45 | 9976E418178FFE3F005966E8 /* EIViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = EIViewController.m; sourceTree = ""; }; 46 | 9976E425179022B9005966E8 /* FRGWaterfallCollectionViewLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRGWaterfallCollectionViewLayout.h; sourceTree = ""; }; 47 | 9976E426179022B9005966E8 /* FRGWaterfallCollectionViewLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRGWaterfallCollectionViewLayout.m; sourceTree = ""; }; 48 | 9976E4281790238C005966E8 /* FRGWaterfallCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRGWaterfallCollectionViewCell.h; sourceTree = ""; }; 49 | 9976E4291790238C005966E8 /* FRGWaterfallCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRGWaterfallCollectionViewCell.m; sourceTree = ""; }; 50 | 9976E42B1790819D005966E8 /* FRGWaterfallHeaderReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRGWaterfallHeaderReusableView.h; sourceTree = ""; }; 51 | 9976E42C1790819E005966E8 /* FRGWaterfallHeaderReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRGWaterfallHeaderReusableView.m; sourceTree = ""; }; 52 | 9986EA6E1792A00800850AF3 /* FRGWaterfallDecorationReusableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FRGWaterfallDecorationReusableView.h; sourceTree = ""; }; 53 | 9986EA6F1792A00800850AF3 /* FRGWaterfallDecorationReusableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FRGWaterfallDecorationReusableView.m; sourceTree = ""; }; 54 | 9986EA711792A13000850AF3 /* decorationImage.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = decorationImage.png; sourceTree = ""; }; 55 | 9986EA721792A13000850AF3 /* decorationImage@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "decorationImage@2x.png"; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | 9976E3F6178FFE3F005966E8 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | 9976E3FD178FFE3F005966E8 /* UIKit.framework in Frameworks */, 64 | 9976E3FF178FFE3F005966E8 /* Foundation.framework in Frameworks */, 65 | 9976E401178FFE3F005966E8 /* CoreGraphics.framework in Frameworks */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | 9976E3F0178FFE3F005966E8 = { 73 | isa = PBXGroup; 74 | children = ( 75 | 9976E402178FFE3F005966E8 /* WaterfallCollectionView */, 76 | 9976E3FB178FFE3F005966E8 /* Frameworks */, 77 | 9976E3FA178FFE3F005966E8 /* Products */, 78 | ); 79 | sourceTree = ""; 80 | }; 81 | 9976E3FA178FFE3F005966E8 /* Products */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | 9976E3F9178FFE3F005966E8 /* WaterfallCollectionView.app */, 85 | ); 86 | name = Products; 87 | sourceTree = ""; 88 | }; 89 | 9976E3FB178FFE3F005966E8 /* Frameworks */ = { 90 | isa = PBXGroup; 91 | children = ( 92 | 9976E3FC178FFE3F005966E8 /* UIKit.framework */, 93 | 9976E3FE178FFE3F005966E8 /* Foundation.framework */, 94 | 9976E400178FFE3F005966E8 /* CoreGraphics.framework */, 95 | ); 96 | name = Frameworks; 97 | sourceTree = ""; 98 | }; 99 | 9976E402178FFE3F005966E8 /* WaterfallCollectionView */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | 9976E40B178FFE3F005966E8 /* EIAppDelegate.h */, 103 | 9976E40C178FFE3F005966E8 /* EIAppDelegate.m */, 104 | 9976E414178FFE3F005966E8 /* MainStoryboard.storyboard */, 105 | 9976E417178FFE3F005966E8 /* EIViewController.h */, 106 | 9976E418178FFE3F005966E8 /* EIViewController.m */, 107 | 9976E403178FFE3F005966E8 /* Supporting Files */, 108 | 9976E425179022B9005966E8 /* FRGWaterfallCollectionViewLayout.h */, 109 | 9976E426179022B9005966E8 /* FRGWaterfallCollectionViewLayout.m */, 110 | 9976E4281790238C005966E8 /* FRGWaterfallCollectionViewCell.h */, 111 | 9976E4291790238C005966E8 /* FRGWaterfallCollectionViewCell.m */, 112 | 9976E42B1790819D005966E8 /* FRGWaterfallHeaderReusableView.h */, 113 | 9976E42C1790819E005966E8 /* FRGWaterfallHeaderReusableView.m */, 114 | 9986EA6E1792A00800850AF3 /* FRGWaterfallDecorationReusableView.h */, 115 | 9986EA6F1792A00800850AF3 /* FRGWaterfallDecorationReusableView.m */, 116 | 9986EA711792A13000850AF3 /* decorationImage.png */, 117 | 9986EA721792A13000850AF3 /* decorationImage@2x.png */, 118 | ); 119 | path = WaterfallCollectionView; 120 | sourceTree = ""; 121 | }; 122 | 9976E403178FFE3F005966E8 /* Supporting Files */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | 9976E404178FFE3F005966E8 /* WaterfallCollectionView-Info.plist */, 126 | 9976E405178FFE3F005966E8 /* InfoPlist.strings */, 127 | 9976E408178FFE3F005966E8 /* main.m */, 128 | 9976E40A178FFE3F005966E8 /* WaterfallCollectionView-Prefix.pch */, 129 | 9976E40E178FFE3F005966E8 /* Default.png */, 130 | 9976E410178FFE3F005966E8 /* Default@2x.png */, 131 | 9976E412178FFE3F005966E8 /* Default-568h@2x.png */, 132 | ); 133 | name = "Supporting Files"; 134 | sourceTree = ""; 135 | }; 136 | /* End PBXGroup section */ 137 | 138 | /* Begin PBXNativeTarget section */ 139 | 9976E3F8178FFE3F005966E8 /* WaterfallCollectionView */ = { 140 | isa = PBXNativeTarget; 141 | buildConfigurationList = 9976E41C178FFE3F005966E8 /* Build configuration list for PBXNativeTarget "WaterfallCollectionView" */; 142 | buildPhases = ( 143 | 9976E3F5178FFE3F005966E8 /* Sources */, 144 | 9976E3F6178FFE3F005966E8 /* Frameworks */, 145 | 9976E3F7178FFE3F005966E8 /* Resources */, 146 | ); 147 | buildRules = ( 148 | ); 149 | dependencies = ( 150 | ); 151 | name = WaterfallCollectionView; 152 | productName = WaterfallCollectionView; 153 | productReference = 9976E3F9178FFE3F005966E8 /* WaterfallCollectionView.app */; 154 | productType = "com.apple.product-type.application"; 155 | }; 156 | /* End PBXNativeTarget section */ 157 | 158 | /* Begin PBXProject section */ 159 | 9976E3F1178FFE3F005966E8 /* Project object */ = { 160 | isa = PBXProject; 161 | attributes = { 162 | CLASSPREFIX = EI; 163 | LastUpgradeCheck = 0460; 164 | ORGANIZATIONNAME = "Event Info Ltd."; 165 | }; 166 | buildConfigurationList = 9976E3F4178FFE3F005966E8 /* Build configuration list for PBXProject "WaterfallCollectionView" */; 167 | compatibilityVersion = "Xcode 3.2"; 168 | developmentRegion = English; 169 | hasScannedForEncodings = 0; 170 | knownRegions = ( 171 | en, 172 | ); 173 | mainGroup = 9976E3F0178FFE3F005966E8; 174 | productRefGroup = 9976E3FA178FFE3F005966E8 /* Products */; 175 | projectDirPath = ""; 176 | projectRoot = ""; 177 | targets = ( 178 | 9976E3F8178FFE3F005966E8 /* WaterfallCollectionView */, 179 | ); 180 | }; 181 | /* End PBXProject section */ 182 | 183 | /* Begin PBXResourcesBuildPhase section */ 184 | 9976E3F7178FFE3F005966E8 /* Resources */ = { 185 | isa = PBXResourcesBuildPhase; 186 | buildActionMask = 2147483647; 187 | files = ( 188 | 9976E407178FFE3F005966E8 /* InfoPlist.strings in Resources */, 189 | 9976E40F178FFE3F005966E8 /* Default.png in Resources */, 190 | 9976E411178FFE3F005966E8 /* Default@2x.png in Resources */, 191 | 9976E413178FFE3F005966E8 /* Default-568h@2x.png in Resources */, 192 | 9976E416178FFE3F005966E8 /* MainStoryboard.storyboard in Resources */, 193 | 9986EA731792A13000850AF3 /* decorationImage.png in Resources */, 194 | 9986EA741792A13000850AF3 /* decorationImage@2x.png in Resources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXResourcesBuildPhase section */ 199 | 200 | /* Begin PBXSourcesBuildPhase section */ 201 | 9976E3F5178FFE3F005966E8 /* Sources */ = { 202 | isa = PBXSourcesBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | 9976E409178FFE3F005966E8 /* main.m in Sources */, 206 | 9976E40D178FFE3F005966E8 /* EIAppDelegate.m in Sources */, 207 | 9976E419178FFE3F005966E8 /* EIViewController.m in Sources */, 208 | 9976E427179022B9005966E8 /* FRGWaterfallCollectionViewLayout.m in Sources */, 209 | 9976E42A1790238C005966E8 /* FRGWaterfallCollectionViewCell.m in Sources */, 210 | 9976E42D1790819E005966E8 /* FRGWaterfallHeaderReusableView.m in Sources */, 211 | 9986EA701792A00800850AF3 /* FRGWaterfallDecorationReusableView.m in Sources */, 212 | ); 213 | runOnlyForDeploymentPostprocessing = 0; 214 | }; 215 | /* End PBXSourcesBuildPhase section */ 216 | 217 | /* Begin PBXVariantGroup section */ 218 | 9976E405178FFE3F005966E8 /* InfoPlist.strings */ = { 219 | isa = PBXVariantGroup; 220 | children = ( 221 | 9976E406178FFE3F005966E8 /* en */, 222 | ); 223 | name = InfoPlist.strings; 224 | sourceTree = ""; 225 | }; 226 | 9976E414178FFE3F005966E8 /* MainStoryboard.storyboard */ = { 227 | isa = PBXVariantGroup; 228 | children = ( 229 | 9976E415178FFE3F005966E8 /* en */, 230 | ); 231 | name = MainStoryboard.storyboard; 232 | sourceTree = ""; 233 | }; 234 | /* End PBXVariantGroup section */ 235 | 236 | /* Begin XCBuildConfiguration section */ 237 | 9976E41A178FFE3F005966E8 /* Debug */ = { 238 | isa = XCBuildConfiguration; 239 | buildSettings = { 240 | ALWAYS_SEARCH_USER_PATHS = NO; 241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 242 | CLANG_CXX_LIBRARY = "libc++"; 243 | CLANG_ENABLE_OBJC_ARC = YES; 244 | CLANG_WARN_CONSTANT_CONVERSION = YES; 245 | CLANG_WARN_EMPTY_BODY = YES; 246 | CLANG_WARN_ENUM_CONVERSION = YES; 247 | CLANG_WARN_INT_CONVERSION = YES; 248 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 249 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 250 | COPY_PHASE_STRIP = NO; 251 | GCC_C_LANGUAGE_STANDARD = gnu99; 252 | GCC_DYNAMIC_NO_PIC = NO; 253 | GCC_OPTIMIZATION_LEVEL = 0; 254 | GCC_PREPROCESSOR_DEFINITIONS = ( 255 | "DEBUG=1", 256 | "$(inherited)", 257 | ); 258 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 259 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 260 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 261 | GCC_WARN_UNUSED_VARIABLE = YES; 262 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 263 | ONLY_ACTIVE_ARCH = YES; 264 | SDKROOT = iphoneos; 265 | }; 266 | name = Debug; 267 | }; 268 | 9976E41B178FFE3F005966E8 /* Release */ = { 269 | isa = XCBuildConfiguration; 270 | buildSettings = { 271 | ALWAYS_SEARCH_USER_PATHS = NO; 272 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 273 | CLANG_CXX_LIBRARY = "libc++"; 274 | CLANG_ENABLE_OBJC_ARC = YES; 275 | CLANG_WARN_CONSTANT_CONVERSION = YES; 276 | CLANG_WARN_EMPTY_BODY = YES; 277 | CLANG_WARN_ENUM_CONVERSION = YES; 278 | CLANG_WARN_INT_CONVERSION = YES; 279 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 280 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 281 | COPY_PHASE_STRIP = YES; 282 | GCC_C_LANGUAGE_STANDARD = gnu99; 283 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 284 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 285 | GCC_WARN_UNUSED_VARIABLE = YES; 286 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 287 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 288 | SDKROOT = iphoneos; 289 | VALIDATE_PRODUCT = YES; 290 | }; 291 | name = Release; 292 | }; 293 | 9976E41D178FFE3F005966E8 /* Debug */ = { 294 | isa = XCBuildConfiguration; 295 | buildSettings = { 296 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 297 | GCC_PREFIX_HEADER = "WaterfallCollectionView/WaterfallCollectionView-Prefix.pch"; 298 | INFOPLIST_FILE = "WaterfallCollectionView/WaterfallCollectionView-Info.plist"; 299 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 300 | PRODUCT_NAME = "$(TARGET_NAME)"; 301 | TARGETED_DEVICE_FAMILY = "1,2"; 302 | WRAPPER_EXTENSION = app; 303 | }; 304 | name = Debug; 305 | }; 306 | 9976E41E178FFE3F005966E8 /* Release */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 310 | GCC_PREFIX_HEADER = "WaterfallCollectionView/WaterfallCollectionView-Prefix.pch"; 311 | INFOPLIST_FILE = "WaterfallCollectionView/WaterfallCollectionView-Info.plist"; 312 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 313 | PRODUCT_NAME = "$(TARGET_NAME)"; 314 | TARGETED_DEVICE_FAMILY = "1,2"; 315 | WRAPPER_EXTENSION = app; 316 | }; 317 | name = Release; 318 | }; 319 | /* End XCBuildConfiguration section */ 320 | 321 | /* Begin XCConfigurationList section */ 322 | 9976E3F4178FFE3F005966E8 /* Build configuration list for PBXProject "WaterfallCollectionView" */ = { 323 | isa = XCConfigurationList; 324 | buildConfigurations = ( 325 | 9976E41A178FFE3F005966E8 /* Debug */, 326 | 9976E41B178FFE3F005966E8 /* Release */, 327 | ); 328 | defaultConfigurationIsVisible = 0; 329 | defaultConfigurationName = Release; 330 | }; 331 | 9976E41C178FFE3F005966E8 /* Build configuration list for PBXNativeTarget "WaterfallCollectionView" */ = { 332 | isa = XCConfigurationList; 333 | buildConfigurations = ( 334 | 9976E41D178FFE3F005966E8 /* Debug */, 335 | 9976E41E178FFE3F005966E8 /* Release */, 336 | ); 337 | defaultConfigurationIsVisible = 0; 338 | defaultConfigurationName = Release; 339 | }; 340 | /* End XCConfigurationList section */ 341 | }; 342 | rootObject = 9976E3F1178FFE3F005966E8 /* Project object */; 343 | } 344 | -------------------------------------------------------------------------------- /WaterfallCollectionView.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WaterfallCollectionView.xcodeproj/project.xcworkspace/xcshareddata/WaterfallCollectionView.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | 13D6FD8D-DC1A-40DE-B2FE-54BC9D84008D 9 | IDESourceControlProjectName 10 | WaterfallCollectionView 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 1EDBA7F1-63E1-44A6-8206-1D7F5347DFE9 14 | https://github.com/frogermcs/WaterfallCollectionView.git 15 | 16 | IDESourceControlProjectPath 17 | WaterfallCollectionView.xcodeproj/project.xcworkspace 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 1EDBA7F1-63E1-44A6-8206-1D7F5347DFE9 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/frogermcs/WaterfallCollectionView.git 25 | IDESourceControlProjectVersion 26 | 110 27 | IDESourceControlProjectWCCIdentifier 28 | 1EDBA7F1-63E1-44A6-8206-1D7F5347DFE9 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 1EDBA7F1-63E1-44A6-8206-1D7F5347DFE9 36 | IDESourceControlWCCName 37 | WaterfallCollectionView 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /WaterfallCollectionView/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SureCase/WaterfallCollectionView/8fd8b24d26519d26591fbcae67a1aa72171e0196/WaterfallCollectionView/Default-568h@2x.png -------------------------------------------------------------------------------- /WaterfallCollectionView/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SureCase/WaterfallCollectionView/8fd8b24d26519d26591fbcae67a1aa72171e0196/WaterfallCollectionView/Default.png -------------------------------------------------------------------------------- /WaterfallCollectionView/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SureCase/WaterfallCollectionView/8fd8b24d26519d26591fbcae67a1aa72171e0196/WaterfallCollectionView/Default@2x.png -------------------------------------------------------------------------------- /WaterfallCollectionView/EIAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // EIAppDelegate.h 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 12.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface EIAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /WaterfallCollectionView/EIAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // EIAppDelegate.m 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 12.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import "EIAppDelegate.h" 10 | 11 | @implementation EIAppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 14 | // Override point for customization after application launch. 15 | return YES; 16 | } 17 | 18 | - (void)applicationWillResignActive:(UIApplication *)application 19 | { 20 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 21 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 22 | } 23 | 24 | - (void)applicationDidEnterBackground:(UIApplication *)application 25 | { 26 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 27 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 28 | } 29 | 30 | - (void)applicationWillEnterForeground:(UIApplication *)application 31 | { 32 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 33 | } 34 | 35 | - (void)applicationDidBecomeActive:(UIApplication *)application 36 | { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | - (void)applicationWillTerminate:(UIApplication *)application 41 | { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /WaterfallCollectionView/EIViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // EIViewController.h 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 12.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface EIViewController : UIViewController 12 | 13 | @property (weak, nonatomic) IBOutlet UICollectionView *cv; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /WaterfallCollectionView/EIViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // EIViewController.m 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 12.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import "EIViewController.h" 10 | #import "FRGWaterfallCollectionViewCell.h" 11 | #import "FRGWaterfallCollectionViewLayout.h" 12 | #import "FRGWaterfallHeaderReusableView.h" 13 | 14 | static NSString* const WaterfallCellIdentifier = @"WaterfallCell"; 15 | static NSString* const WaterfallHeaderIdentifier = @"WaterfallHeader"; 16 | 17 | @interface EIViewController () 18 | 19 | @property (nonatomic, strong) NSMutableArray *cellHeights; 20 | 21 | @end 22 | 23 | @implementation EIViewController 24 | 25 | - (void)viewDidLoad { 26 | [super viewDidLoad]; 27 | self.cv.delegate = self; 28 | 29 | FRGWaterfallCollectionViewLayout *cvLayout = [[FRGWaterfallCollectionViewLayout alloc] init]; 30 | cvLayout.delegate = self; 31 | cvLayout.itemWidth = 140.0f; 32 | cvLayout.topInset = 10.0f; 33 | cvLayout.bottomInset = 10.0f; 34 | cvLayout.stickyHeader = YES; 35 | 36 | [self.cv setCollectionViewLayout:cvLayout]; 37 | [self.cv reloadData]; 38 | } 39 | 40 | - (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation { 41 | [[self.cv collectionViewLayout] invalidateLayout]; 42 | } 43 | 44 | #pragma mark - UICollectionViewDataSource 45 | 46 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { 47 | return 30; 48 | } 49 | 50 | - (NSInteger)collectionView:(UICollectionView *)collectionView 51 | numberOfItemsInSection:(NSInteger)section { 52 | return 30; 53 | } 54 | 55 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView 56 | cellForItemAtIndexPath:(NSIndexPath *)indexPath { 57 | FRGWaterfallCollectionViewCell *waterfallCell = [collectionView dequeueReusableCellWithReuseIdentifier:WaterfallCellIdentifier 58 | forIndexPath:indexPath]; 59 | waterfallCell.lblTitle.text = [NSString stringWithFormat: @"Item %d", indexPath.item]; 60 | return waterfallCell; 61 | } 62 | 63 | - (CGFloat)collectionView:(UICollectionView *)collectionView 64 | layout:(FRGWaterfallCollectionViewLayout *)collectionViewLayout 65 | heightForItemAtIndexPath:(NSIndexPath *)indexPath { 66 | return [self.cellHeights[indexPath.section + 1 * indexPath.item] floatValue]; 67 | } 68 | 69 | - (CGFloat)collectionView:(UICollectionView *)collectionView 70 | layout:(FRGWaterfallCollectionViewLayout *)collectionViewLayout 71 | heightForHeaderAtIndexPath:(NSIndexPath *)indexPath { 72 | return (indexPath.section + 1) * 26.0f; 73 | } 74 | 75 | - (NSMutableArray *)cellHeights { 76 | if (!_cellHeights) { 77 | _cellHeights = [NSMutableArray arrayWithCapacity:900]; 78 | for (NSInteger i = 0; i < 900; i++) { 79 | _cellHeights[i] = @(arc4random()%100*2+100); 80 | } 81 | } 82 | return _cellHeights; 83 | } 84 | 85 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView 86 | viewForSupplementaryElementOfKind:(NSString *)kind 87 | atIndexPath:(NSIndexPath *)indexPath; { 88 | FRGWaterfallHeaderReusableView *titleView = 89 | [collectionView dequeueReusableSupplementaryViewOfKind:kind 90 | withReuseIdentifier:WaterfallHeaderIdentifier 91 | forIndexPath:indexPath]; 92 | titleView.lblTitle.text = [NSString stringWithFormat: @"Section %d", indexPath.section]; 93 | return titleView; 94 | } 95 | 96 | @end 97 | -------------------------------------------------------------------------------- /WaterfallCollectionView/FRGWaterfallCollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRGWaterfallCollectionViewCell.h 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 12.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FRGWaterfallCollectionViewCell : UICollectionViewCell 12 | 13 | @property (weak, nonatomic) IBOutlet UILabel *lblTitle; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /WaterfallCollectionView/FRGWaterfallCollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRGWaterfallCollectionViewCell.m 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 12.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import "FRGWaterfallCollectionViewCell.h" 10 | 11 | @interface FRGWaterfallCollectionViewCell() 12 | 13 | @end 14 | 15 | @implementation FRGWaterfallCollectionViewCell 16 | 17 | - (id)initWithFrame:(CGRect)frame { 18 | self = [super initWithFrame:frame]; 19 | if (self) { 20 | 21 | } 22 | return self; 23 | } 24 | 25 | - (id)initWithCoder:(NSCoder *)aDecoder { 26 | self = [super initWithCoder:aDecoder]; 27 | if (self) { 28 | 29 | } 30 | return self; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /WaterfallCollectionView/FRGWaterfallCollectionViewLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRGWaterfallCollectionViewLayout.h 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 12.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class FRGWaterfallCollectionViewLayout; 12 | 13 | @protocol FRGWaterfallCollectionViewDelegate 14 | 15 | - (CGFloat)collectionView:(UICollectionView *)collectionView 16 | layout:(FRGWaterfallCollectionViewLayout *)collectionViewLayout 17 | heightForItemAtIndexPath:(NSIndexPath *)indexPath; 18 | 19 | @optional 20 | 21 | - (CGFloat) collectionView:(UICollectionView *)collectionView 22 | layout:(FRGWaterfallCollectionViewLayout *)collectionViewLayout 23 | heightForHeaderAtIndexPath:(NSIndexPath *)indexPath; 24 | 25 | @end 26 | 27 | @interface FRGWaterfallCollectionViewLayout : UICollectionViewLayout 28 | 29 | @property (nonatomic, weak) IBOutlet id delegate; 30 | @property (nonatomic) CGFloat itemWidth; 31 | 32 | @property (nonatomic) CGFloat topInset; 33 | @property (nonatomic) CGFloat bottomInset; 34 | @property (nonatomic) BOOL stickyHeader; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /WaterfallCollectionView/FRGWaterfallCollectionViewLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRGWaterfallCollectionViewLayout.m 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 12.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import "FRGWaterfallCollectionViewLayout.h" 10 | #import "FRGWaterfallDecorationReusableView.h" 11 | 12 | NSString* const FRGWaterfallLayoutCellKind = @"WaterfallCell"; 13 | NSString* const FRGWaterfallLayouDecorationKind = @"Decoration"; 14 | 15 | @interface FRGWaterfallCollectionViewLayout() 16 | 17 | @property (nonatomic) NSInteger columnsCount; 18 | @property (nonatomic) CGFloat itemInnerMargin; 19 | @property (nonatomic) NSDictionary *layoutInfo; 20 | @property (nonatomic) NSArray *sectionsHeights; 21 | @property (nonatomic) NSArray *itemsInSectionsHeights; 22 | 23 | @end 24 | 25 | @implementation FRGWaterfallCollectionViewLayout 26 | 27 | #pragma mark - Lifecycle 28 | 29 | - (id)init { 30 | self = [super init]; 31 | if (self) { 32 | [self setup]; 33 | } 34 | 35 | return self; 36 | } 37 | 38 | - (id)initWithCoder:(NSCoder *)aDecoder { 39 | self = [super init]; 40 | if (self) { 41 | [self setup]; 42 | } 43 | 44 | return self; 45 | } 46 | 47 | - (void) setItemWidth:(CGFloat)itemWidth { 48 | if(_itemWidth == itemWidth) return; 49 | _itemWidth = itemWidth; 50 | [self invalidateLayout]; 51 | } 52 | 53 | - (void)setup { 54 | [self registerClass:[FRGWaterfallDecorationReusableView class] forDecorationViewOfKind:FRGWaterfallLayouDecorationKind]; 55 | self.itemWidth = 140.0f; 56 | self.topInset = 10.0f; 57 | self.bottomInset = 10.0f; 58 | self.stickyHeader = YES; 59 | } 60 | 61 | - (void)prepareLayout { 62 | if (self.collectionView.isDecelerating || self.collectionView.isDragging) { 63 | 64 | } else { 65 | [self calculateMaxColumnsCount]; 66 | [self calculateItemsInnerMargin]; 67 | [self calculateItemsHeights]; 68 | [self calculateSectionsHeights]; 69 | [self calculateItemsAttributes]; 70 | } 71 | } 72 | 73 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { 74 | NSMutableArray *allAttributes = [NSMutableArray arrayWithCapacity:self.layoutInfo.count]; 75 | 76 | [self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSString *elementIdentifier, 77 | NSDictionary *elementsInfo, 78 | BOOL *stop) { 79 | [elementsInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, 80 | UICollectionViewLayoutAttributes *attributes, 81 | BOOL *innerStop) { 82 | if (CGRectIntersectsRect(rect, attributes.frame) || [elementIdentifier isEqualToString:UICollectionElementKindSectionHeader]) { 83 | [allAttributes addObject:attributes]; 84 | } 85 | }]; 86 | }]; 87 | 88 | if(!self.stickyHeader) { 89 | return allAttributes; 90 | } 91 | 92 | for (UICollectionViewLayoutAttributes *layoutAttributes in allAttributes) { 93 | if ([layoutAttributes.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) { 94 | NSInteger section = layoutAttributes.indexPath.section; 95 | NSIndexPath *firstCellIndexPath = [NSIndexPath indexPathForItem:0 inSection:section]; 96 | UICollectionViewLayoutAttributes *firstCellAttrs = [self layoutAttributesForItemAtIndexPath:firstCellIndexPath]; 97 | 98 | CGFloat headerHeight = CGRectGetHeight(layoutAttributes.frame) + self.itemInnerMargin; 99 | CGFloat currentHeaderHeight = [self headerHeightForIndexPath:firstCellIndexPath]; 100 | CGPoint origin = layoutAttributes.frame.origin; 101 | origin.y = MIN( 102 | MAX(self.collectionView.contentOffset.y, (CGRectGetMinY(firstCellAttrs.frame) - headerHeight) - self.topInset), 103 | CGRectGetMinY(firstCellAttrs.frame) - headerHeight + [[self.sectionsHeights objectAtIndex:section] floatValue] - currentHeaderHeight - self.topInset 104 | ) + self.topInset; 105 | 106 | CGFloat width = layoutAttributes.frame.size.width; 107 | if(self.collectionView.contentOffset.y >= origin.y) { 108 | width = self.collectionView.bounds.size.width; 109 | origin.x = 0; 110 | } else { 111 | width = self.collectionView.bounds.size.width - 112 | MIN((2 * self.itemInnerMargin), 113 | (origin.y - self.collectionView.contentOffset.y)); 114 | origin.x = (self.collectionView.bounds.size.width - width) / 2; 115 | } 116 | 117 | layoutAttributes.zIndex = 1024; 118 | layoutAttributes.frame = (CGRect){ 119 | .origin = origin, 120 | .size = CGSizeMake(width, layoutAttributes.frame.size.height) 121 | }; 122 | } 123 | } 124 | 125 | return allAttributes; 126 | } 127 | 128 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { 129 | return self.layoutInfo[FRGWaterfallLayoutCellKind][indexPath]; 130 | } 131 | 132 | - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind 133 | atIndexPath:(NSIndexPath *)indexPath { 134 | return self.layoutInfo[UICollectionElementKindSectionHeader][indexPath]; 135 | } 136 | 137 | - (UICollectionViewLayoutAttributes *)layoutAttributesForDecorationViewOfKind: 138 | (NSString*)decorationViewKind atIndexPath:(NSIndexPath *)indexPath { 139 | return self.layoutInfo[FRGWaterfallLayouDecorationKind][indexPath]; 140 | } 141 | 142 | - (CGSize)collectionViewContentSize { 143 | CGFloat height = self.topInset; 144 | for (NSNumber *h in self.sectionsHeights) { 145 | height += [h integerValue]; 146 | } 147 | height += self.bottomInset; 148 | 149 | return CGSizeMake(self.collectionView.bounds.size.width, height); 150 | } 151 | 152 | -(BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBound { 153 | return self.stickyHeader; 154 | } 155 | 156 | #pragma mark - Prepare layout calculation 157 | 158 | - (void) calculateMaxColumnsCount { 159 | self.columnsCount = self.collectionView.bounds.size.width / self.itemWidth; 160 | } 161 | 162 | - (void) calculateItemsInnerMargin { 163 | if(self.columnsCount > 1) { 164 | self.itemInnerMargin = 165 | (self.collectionView.bounds.size.width - 166 | self.columnsCount * self.itemWidth) 167 | / 168 | (self.columnsCount + 1); 169 | } 170 | } 171 | 172 | - (void) calculateItemsHeights { 173 | NSMutableArray *itemsInSectionsHeights = [NSMutableArray arrayWithCapacity:self.collectionView.numberOfSections]; 174 | NSIndexPath *itemIndex; 175 | for (NSInteger section = 0; section < self.collectionView.numberOfSections; section++) { 176 | NSMutableArray *itemsHeights = [NSMutableArray arrayWithCapacity:[self.collectionView numberOfItemsInSection:section]]; 177 | for (NSInteger item = 0; item < [self.collectionView numberOfItemsInSection:section]; item++) { 178 | itemIndex = [NSIndexPath indexPathForItem:item inSection:section]; 179 | CGFloat itemHeight = [self.delegate collectionView:self.collectionView 180 | layout:self 181 | heightForItemAtIndexPath:itemIndex]; 182 | [itemsHeights addObject:[NSNumber numberWithFloat:itemHeight]]; 183 | } 184 | [itemsInSectionsHeights addObject:itemsHeights]; 185 | } 186 | 187 | self.itemsInSectionsHeights = itemsInSectionsHeights; 188 | } 189 | 190 | - (void) calculateSectionsHeights { 191 | NSMutableArray *newSectionsHeights = [NSMutableArray array]; 192 | NSInteger sectionCount = [self.collectionView numberOfSections]; 193 | for (NSInteger section = 0; section < sectionCount; section++) { 194 | [newSectionsHeights addObject:[self calculateHeightForSection:section]]; 195 | } 196 | self.sectionsHeights = [NSArray arrayWithArray:newSectionsHeights]; 197 | } 198 | 199 | - (NSNumber*) calculateHeightForSection: (NSInteger)section { 200 | NSInteger sectionColumns[self.columnsCount]; 201 | NSIndexPath* indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; 202 | for (NSInteger column = 0; column < self.columnsCount; column++) { 203 | sectionColumns[column] = [self headerHeightForIndexPath:indexPath] 204 | + self.itemInnerMargin; 205 | } 206 | 207 | NSInteger itemCount = [self.collectionView numberOfItemsInSection:section]; 208 | for (NSInteger item = 0; item < itemCount; item++) { 209 | indexPath = [NSIndexPath indexPathForItem:item inSection:section]; 210 | 211 | NSInteger currentColumn = 0; 212 | for (NSInteger column = 0; column < self.columnsCount; column++) { 213 | if(sectionColumns[currentColumn] > sectionColumns[column]) { 214 | currentColumn = column; 215 | } 216 | } 217 | 218 | sectionColumns[currentColumn] += [[[self.itemsInSectionsHeights objectAtIndex:section] 219 | objectAtIndex:indexPath.item] floatValue]; 220 | sectionColumns[currentColumn] += self.itemInnerMargin; 221 | } 222 | 223 | int biggestColumn = 0; 224 | for (NSInteger column = 0; column < self.columnsCount; column++) { 225 | if(sectionColumns[biggestColumn] < sectionColumns[column]) { 226 | biggestColumn = column; 227 | } 228 | } 229 | 230 | return [NSNumber numberWithFloat: sectionColumns[biggestColumn]]; 231 | } 232 | 233 | - (void) calculateItemsAttributes { 234 | NSMutableDictionary *newLayoutInfo = [NSMutableDictionary dictionary]; 235 | NSMutableDictionary *cellLayoutInfo = [NSMutableDictionary dictionary]; 236 | NSMutableDictionary *titleLayoutInfo = [NSMutableDictionary dictionary]; 237 | 238 | NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; 239 | UICollectionViewLayoutAttributes *emblemAttributes = 240 | [UICollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:FRGWaterfallLayouDecorationKind 241 | withIndexPath:indexPath]; 242 | emblemAttributes.frame = [self frameForWaterfallDecoration]; 243 | 244 | for (NSInteger section = 0; section < [self.collectionView numberOfSections]; section++) { 245 | for (NSInteger item = 0; item < [self.collectionView numberOfItemsInSection:section]; item++) { 246 | indexPath = [NSIndexPath indexPathForItem:item inSection:section]; 247 | 248 | UICollectionViewLayoutAttributes *itemAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; 249 | itemAttributes.frame = [self frameForWaterfallCellIndexPath:indexPath]; 250 | cellLayoutInfo[indexPath] = itemAttributes; 251 | 252 | //Only one header in section, so we get only item at 0 position 253 | if (indexPath.item == 0) { 254 | UICollectionViewLayoutAttributes *titleAttributes = [UICollectionViewLayoutAttributes 255 | layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader 256 | withIndexPath:indexPath]; 257 | titleAttributes.frame = [self frameForWaterfallHeaderAtIndexPath:indexPath]; 258 | titleLayoutInfo[indexPath] = titleAttributes; 259 | } 260 | } 261 | } 262 | 263 | newLayoutInfo[FRGWaterfallLayoutCellKind] = cellLayoutInfo; 264 | newLayoutInfo[UICollectionElementKindSectionHeader] = titleLayoutInfo; 265 | newLayoutInfo[FRGWaterfallLayouDecorationKind] = @{indexPath: emblemAttributes}; 266 | 267 | self.layoutInfo = newLayoutInfo; 268 | 269 | } 270 | 271 | #pragma mark - Items frames 272 | 273 | - (CGRect)frameForWaterfallCellIndexPath:(NSIndexPath *)indexPath { 274 | CGFloat width = self.itemWidth; 275 | CGFloat height = [[[self.itemsInSectionsHeights objectAtIndex:indexPath.section] 276 | objectAtIndex:indexPath.item] floatValue]; 277 | 278 | CGFloat topInset = self.topInset; 279 | for (NSInteger section = 0; section < indexPath.section; section++) { 280 | topInset += [[self.sectionsHeights objectAtIndex:section] integerValue]; 281 | } 282 | 283 | NSInteger columnsHeights[self.columnsCount]; 284 | for (NSInteger column = 0; column < self.columnsCount; column++) { 285 | columnsHeights[column] = [self headerHeightForIndexPath:indexPath] + self.itemInnerMargin; 286 | } 287 | 288 | for (NSInteger item = 0; item < indexPath.item; item++) { 289 | NSIndexPath *ip = [NSIndexPath indexPathForItem:item inSection:indexPath.section]; 290 | NSInteger currentColumn = 0; 291 | for(NSInteger column = 0; column < self.columnsCount; column++) { 292 | if(columnsHeights[currentColumn] > columnsHeights[column]) { 293 | currentColumn = column; 294 | } 295 | } 296 | 297 | columnsHeights[currentColumn] += [[[self.itemsInSectionsHeights objectAtIndex:ip.section] 298 | objectAtIndex:ip.item] floatValue]; 299 | columnsHeights[currentColumn] += self.itemInnerMargin; 300 | } 301 | 302 | NSInteger columnForCurrentItem = 0; 303 | for (NSInteger column = 0; column < self.columnsCount; column++) { 304 | if(columnsHeights[columnForCurrentItem] > columnsHeights[column]) { 305 | columnForCurrentItem = column; 306 | } 307 | } 308 | 309 | CGFloat originX = self.itemInnerMargin + 310 | columnForCurrentItem * self.itemWidth + 311 | columnForCurrentItem * self.itemInnerMargin; 312 | CGFloat originY = columnsHeights[columnForCurrentItem] + topInset; 313 | 314 | return CGRectMake(originX, originY, width, height); 315 | } 316 | 317 | - (CGRect)frameForWaterfallHeaderAtIndexPath:(NSIndexPath *)indexPath { 318 | CGFloat width = self.collectionView.bounds.size.width - 319 | self.itemInnerMargin * 2; 320 | CGFloat height = [self headerHeightForIndexPath:indexPath]; 321 | 322 | CGFloat originY = self.topInset; 323 | for (NSInteger i = 0; i < indexPath.section; i++) { 324 | originY += [[self.sectionsHeights objectAtIndex:i] floatValue]; 325 | } 326 | 327 | CGFloat originX = self.itemInnerMargin; 328 | return CGRectMake(originX, originY, width, height); 329 | } 330 | 331 | - (CGRect) frameForWaterfallDecoration { 332 | CGSize size = [FRGWaterfallDecorationReusableView defaultSize]; 333 | CGFloat originX = floorf((self.collectionView.bounds.size.width - size.width) * 0.5f); 334 | CGFloat originY = -size.height - 30.0f; 335 | return CGRectMake(originX, originY, size.width, size.height); 336 | } 337 | 338 | - (CGFloat) headerHeightForIndexPath:(NSIndexPath*)indexPath { 339 | if ([self.delegate respondsToSelector:@selector(collectionView:layout:heightForHeaderAtIndexPath:)]) { 340 | return [self.delegate collectionView:self.collectionView 341 | layout:self 342 | heightForHeaderAtIndexPath:indexPath]; 343 | } 344 | 345 | return 0; 346 | } 347 | 348 | @end 349 | -------------------------------------------------------------------------------- /WaterfallCollectionView/FRGWaterfallDecorationReusableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRGWaterfallDecorationReusableView.h 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 14.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FRGWaterfallDecorationReusableView : UICollectionReusableView 12 | 13 | + (CGSize)defaultSize; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /WaterfallCollectionView/FRGWaterfallDecorationReusableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRGWaterfallDecorationReusableView.m 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 14.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import "FRGWaterfallDecorationReusableView.h" 10 | 11 | @implementation FRGWaterfallDecorationReusableView 12 | 13 | 14 | - (id)initWithFrame:(CGRect)frame { 15 | self = [super initWithFrame:frame]; 16 | if (self) { 17 | UIImage *image = [UIImage imageNamed:@"decorationImage"]; 18 | UIImageView *imageView = [[UIImageView alloc] initWithImage:image]; 19 | imageView.frame = self.bounds; 20 | [self addSubview:imageView]; 21 | } 22 | return self; 23 | } 24 | 25 | + (CGSize)defaultSize { 26 | return [UIImage imageNamed:@"decorationImage"].size; 27 | } 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /WaterfallCollectionView/FRGWaterfallHeaderReusableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FRGHeaderReusableView.h 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 12.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FRGWaterfallHeaderReusableView : UICollectionReusableView 12 | 13 | @property (weak, nonatomic) IBOutlet UILabel *lblTitle; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /WaterfallCollectionView/FRGWaterfallHeaderReusableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FRGHeaderReusableView.m 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 12.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import "FRGWaterfallHeaderReusableView.h" 10 | 11 | @interface FRGWaterfallHeaderReusableView() 12 | 13 | @end 14 | 15 | @implementation FRGWaterfallHeaderReusableView 16 | 17 | - (id)initWithFrame:(CGRect)frame { 18 | self = [super initWithFrame:frame]; 19 | if (self) { 20 | 21 | } 22 | return self; 23 | } 24 | 25 | - (id)initWithCoder:(NSCoder *)aDecoder { 26 | self = [super initWithCoder:aDecoder]; 27 | if (self) { 28 | 29 | } 30 | return self; 31 | } 32 | 33 | @end 34 | -------------------------------------------------------------------------------- /WaterfallCollectionView/WaterfallCollectionView-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | co.eventinfo.waterfall.${PRODUCT_NAME:rfc1034identifier} 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | ${PRODUCT_NAME} 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1.0 25 | LSRequiresIPhoneOS 26 | 27 | UIMainStoryboardFile 28 | MainStoryboard 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /WaterfallCollectionView/WaterfallCollectionView-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'WaterfallCollectionView' target in the 'WaterfallCollectionView' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_5_0 8 | #warning "This project uses features only available in iOS SDK 5.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /WaterfallCollectionView/decorationImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SureCase/WaterfallCollectionView/8fd8b24d26519d26591fbcae67a1aa72171e0196/WaterfallCollectionView/decorationImage.png -------------------------------------------------------------------------------- /WaterfallCollectionView/decorationImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SureCase/WaterfallCollectionView/8fd8b24d26519d26591fbcae67a1aa72171e0196/WaterfallCollectionView/decorationImage@2x.png -------------------------------------------------------------------------------- /WaterfallCollectionView/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /WaterfallCollectionView/en.lproj/MainStoryboard.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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /WaterfallCollectionView/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // WaterfallCollectionView 4 | // 5 | // Created by Miroslaw Stanek on 12.07.2013. 6 | // Copyright (c) 2013 Event Info Ltd. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "EIAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([EIAppDelegate class])); 17 | } 18 | } 19 | --------------------------------------------------------------------------------