├── .gitignore ├── README.md ├── StaggeredGridLayout.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── StaggeredGridLayout ├── Assets.xcassets │ ├── 1200-1200.imageset │ │ ├── 1200-1200.jpg │ │ └── Contents.json │ ├── 1600-1200.imageset │ │ ├── 1600-1200.jpg │ │ └── Contents.json │ ├── 900-1200.imageset │ │ ├── 900-1200.jpg │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard ├── Info.plist ├── Posts.json ├── SGLAppDelegate.h ├── SGLAppDelegate.m ├── SGLPost.h ├── SGLPost.m ├── SGLPostCell.h ├── SGLPostCell.m ├── SGLPostsViewController.h ├── SGLPostsViewController.m ├── SGLStaggeredGridLayout.h ├── SGLStaggeredGridLayout.m ├── SGLStaggeredGridLayoutAttributes.h ├── SGLStaggeredGridLayoutAttributes.m └── main.m └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | *.DS_Store 2 | 3 | # Created by https://www.gitignore.io/api/objective-c 4 | 5 | ### Objective-C ### 6 | # Xcode 7 | # 8 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 9 | 10 | ## Build generated 11 | build/ 12 | DerivedData/ 13 | 14 | ## Various settings 15 | *.pbxuser 16 | !default.pbxuser 17 | *.mode1v3 18 | !default.mode1v3 19 | *.mode2v3 20 | !default.mode2v3 21 | *.perspectivev3 22 | !default.perspectivev3 23 | xcuserdata/ 24 | 25 | ## Other 26 | *.moved-aside 27 | *.xcuserstate 28 | 29 | ## Obj-C/Swift specific 30 | *.hmap 31 | *.ipa 32 | 33 | # CocoaPods 34 | # 35 | # We recommend against adding the Pods directory to your .gitignore. However 36 | # you should judge for yourself, the pros and cons are mentioned at: 37 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 38 | # 39 | Pods/ 40 | 41 | # Carthage 42 | # 43 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 44 | # Carthage/Checkouts 45 | 46 | Carthage/Build 47 | 48 | # fastlane 49 | # 50 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 51 | # screenshots whenever they are needed. 52 | # For more information about the recommended setup visit: 53 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md 54 | 55 | fastlane/report.xml 56 | fastlane/screenshots 57 | 58 | ### Objective-C Patch ### 59 | *.xcscmblueprint 60 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StaggeredGridLayout 2 | UICollectionViewLayout subclass implementation sample. Provide grid layout like Pinterest App. 3 | 4 | ![](screenshot.png) 5 | -------------------------------------------------------------------------------- /StaggeredGridLayout.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C95328101C9AA02200C02B8C /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C953280F1C9AA02200C02B8C /* main.m */; }; 11 | C95328131C9AA02200C02B8C /* SGLAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C95328121C9AA02200C02B8C /* SGLAppDelegate.m */; }; 12 | C95328191C9AA02200C02B8C /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C95328171C9AA02200C02B8C /* Main.storyboard */; }; 13 | C953281B1C9AA02200C02B8C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C953281A1C9AA02200C02B8C /* Assets.xcassets */; }; 14 | C953281E1C9AA02200C02B8C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C953281C1C9AA02200C02B8C /* LaunchScreen.storyboard */; }; 15 | C953282F1C9AA10F00C02B8C /* SGLPostCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C95328261C9AA10F00C02B8C /* SGLPostCell.m */; }; 16 | C95328301C9AA10F00C02B8C /* SGLPost.m in Sources */ = {isa = PBXBuildFile; fileRef = C95328281C9AA10F00C02B8C /* SGLPost.m */; }; 17 | C95328311C9AA10F00C02B8C /* SGLPostsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C953282A1C9AA10F00C02B8C /* SGLPostsViewController.m */; }; 18 | C95328321C9AA10F00C02B8C /* SGLStaggeredGridLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = C953282C1C9AA10F00C02B8C /* SGLStaggeredGridLayout.m */; }; 19 | C95328331C9AA10F00C02B8C /* SGLStaggeredGridLayoutAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = C953282E1C9AA10F00C02B8C /* SGLStaggeredGridLayoutAttributes.m */; }; 20 | C95328391C9AA88F00C02B8C /* Posts.json in Resources */ = {isa = PBXBuildFile; fileRef = C95328381C9AA88F00C02B8C /* Posts.json */; }; 21 | /* End PBXBuildFile section */ 22 | 23 | /* Begin PBXFileReference section */ 24 | C953280B1C9AA02200C02B8C /* StaggeredGridLayout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = StaggeredGridLayout.app; sourceTree = BUILT_PRODUCTS_DIR; }; 25 | C953280F1C9AA02200C02B8C /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 26 | C95328111C9AA02200C02B8C /* SGLAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SGLAppDelegate.h; sourceTree = ""; }; 27 | C95328121C9AA02200C02B8C /* SGLAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SGLAppDelegate.m; sourceTree = ""; }; 28 | C95328181C9AA02200C02B8C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 29 | C953281A1C9AA02200C02B8C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | C953281D1C9AA02200C02B8C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | C953281F1C9AA02200C02B8C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | C95328251C9AA10F00C02B8C /* SGLPostCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SGLPostCell.h; sourceTree = ""; }; 33 | C95328261C9AA10F00C02B8C /* SGLPostCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SGLPostCell.m; sourceTree = ""; }; 34 | C95328271C9AA10F00C02B8C /* SGLPost.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SGLPost.h; sourceTree = ""; }; 35 | C95328281C9AA10F00C02B8C /* SGLPost.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SGLPost.m; sourceTree = ""; }; 36 | C95328291C9AA10F00C02B8C /* SGLPostsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SGLPostsViewController.h; sourceTree = ""; }; 37 | C953282A1C9AA10F00C02B8C /* SGLPostsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SGLPostsViewController.m; sourceTree = ""; }; 38 | C953282B1C9AA10F00C02B8C /* SGLStaggeredGridLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SGLStaggeredGridLayout.h; sourceTree = ""; }; 39 | C953282C1C9AA10F00C02B8C /* SGLStaggeredGridLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SGLStaggeredGridLayout.m; sourceTree = ""; }; 40 | C953282D1C9AA10F00C02B8C /* SGLStaggeredGridLayoutAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SGLStaggeredGridLayoutAttributes.h; sourceTree = ""; }; 41 | C953282E1C9AA10F00C02B8C /* SGLStaggeredGridLayoutAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SGLStaggeredGridLayoutAttributes.m; sourceTree = ""; }; 42 | C95328381C9AA88F00C02B8C /* Posts.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = Posts.json; sourceTree = ""; }; 43 | /* End PBXFileReference section */ 44 | 45 | /* Begin PBXFrameworksBuildPhase section */ 46 | C95328081C9AA02200C02B8C /* Frameworks */ = { 47 | isa = PBXFrameworksBuildPhase; 48 | buildActionMask = 2147483647; 49 | files = ( 50 | ); 51 | runOnlyForDeploymentPostprocessing = 0; 52 | }; 53 | /* End PBXFrameworksBuildPhase section */ 54 | 55 | /* Begin PBXGroup section */ 56 | C95328021C9AA02200C02B8C = { 57 | isa = PBXGroup; 58 | children = ( 59 | C953280D1C9AA02200C02B8C /* StaggeredGridLayout */, 60 | C953280C1C9AA02200C02B8C /* Products */, 61 | ); 62 | sourceTree = ""; 63 | }; 64 | C953280C1C9AA02200C02B8C /* Products */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | C953280B1C9AA02200C02B8C /* StaggeredGridLayout.app */, 68 | ); 69 | name = Products; 70 | sourceTree = ""; 71 | }; 72 | C953280D1C9AA02200C02B8C /* StaggeredGridLayout */ = { 73 | isa = PBXGroup; 74 | children = ( 75 | C95328171C9AA02200C02B8C /* Main.storyboard */, 76 | C95328111C9AA02200C02B8C /* SGLAppDelegate.h */, 77 | C95328121C9AA02200C02B8C /* SGLAppDelegate.m */, 78 | C95328271C9AA10F00C02B8C /* SGLPost.h */, 79 | C95328281C9AA10F00C02B8C /* SGLPost.m */, 80 | C95328291C9AA10F00C02B8C /* SGLPostsViewController.h */, 81 | C953282A1C9AA10F00C02B8C /* SGLPostsViewController.m */, 82 | C95328251C9AA10F00C02B8C /* SGLPostCell.h */, 83 | C95328261C9AA10F00C02B8C /* SGLPostCell.m */, 84 | C953282B1C9AA10F00C02B8C /* SGLStaggeredGridLayout.h */, 85 | C953282C1C9AA10F00C02B8C /* SGLStaggeredGridLayout.m */, 86 | C953282D1C9AA10F00C02B8C /* SGLStaggeredGridLayoutAttributes.h */, 87 | C953282E1C9AA10F00C02B8C /* SGLStaggeredGridLayoutAttributes.m */, 88 | C953280E1C9AA02200C02B8C /* Supporting Files */, 89 | ); 90 | path = StaggeredGridLayout; 91 | sourceTree = ""; 92 | }; 93 | C953280E1C9AA02200C02B8C /* Supporting Files */ = { 94 | isa = PBXGroup; 95 | children = ( 96 | C953280F1C9AA02200C02B8C /* main.m */, 97 | C953281F1C9AA02200C02B8C /* Info.plist */, 98 | C953281C1C9AA02200C02B8C /* LaunchScreen.storyboard */, 99 | C953281A1C9AA02200C02B8C /* Assets.xcassets */, 100 | C95328381C9AA88F00C02B8C /* Posts.json */, 101 | ); 102 | name = "Supporting Files"; 103 | sourceTree = ""; 104 | }; 105 | /* End PBXGroup section */ 106 | 107 | /* Begin PBXNativeTarget section */ 108 | C953280A1C9AA02200C02B8C /* StaggeredGridLayout */ = { 109 | isa = PBXNativeTarget; 110 | buildConfigurationList = C95328221C9AA02200C02B8C /* Build configuration list for PBXNativeTarget "StaggeredGridLayout" */; 111 | buildPhases = ( 112 | C95328071C9AA02200C02B8C /* Sources */, 113 | C95328081C9AA02200C02B8C /* Frameworks */, 114 | C95328091C9AA02200C02B8C /* Resources */, 115 | ); 116 | buildRules = ( 117 | ); 118 | dependencies = ( 119 | ); 120 | name = StaggeredGridLayout; 121 | productName = StaggeredGridLayout; 122 | productReference = C953280B1C9AA02200C02B8C /* StaggeredGridLayout.app */; 123 | productType = "com.apple.product-type.application"; 124 | }; 125 | /* End PBXNativeTarget section */ 126 | 127 | /* Begin PBXProject section */ 128 | C95328031C9AA02200C02B8C /* Project object */ = { 129 | isa = PBXProject; 130 | attributes = { 131 | CLASSPREFIX = SGL; 132 | LastUpgradeCheck = 0720; 133 | ORGANIZATIONNAME = "Shingo Hiraya"; 134 | TargetAttributes = { 135 | C953280A1C9AA02200C02B8C = { 136 | CreatedOnToolsVersion = 7.2; 137 | }; 138 | }; 139 | }; 140 | buildConfigurationList = C95328061C9AA02200C02B8C /* Build configuration list for PBXProject "StaggeredGridLayout" */; 141 | compatibilityVersion = "Xcode 3.2"; 142 | developmentRegion = English; 143 | hasScannedForEncodings = 0; 144 | knownRegions = ( 145 | en, 146 | Base, 147 | ); 148 | mainGroup = C95328021C9AA02200C02B8C; 149 | productRefGroup = C953280C1C9AA02200C02B8C /* Products */; 150 | projectDirPath = ""; 151 | projectRoot = ""; 152 | targets = ( 153 | C953280A1C9AA02200C02B8C /* StaggeredGridLayout */, 154 | ); 155 | }; 156 | /* End PBXProject section */ 157 | 158 | /* Begin PBXResourcesBuildPhase section */ 159 | C95328091C9AA02200C02B8C /* Resources */ = { 160 | isa = PBXResourcesBuildPhase; 161 | buildActionMask = 2147483647; 162 | files = ( 163 | C953281E1C9AA02200C02B8C /* LaunchScreen.storyboard in Resources */, 164 | C95328391C9AA88F00C02B8C /* Posts.json in Resources */, 165 | C953281B1C9AA02200C02B8C /* Assets.xcassets in Resources */, 166 | C95328191C9AA02200C02B8C /* Main.storyboard in Resources */, 167 | ); 168 | runOnlyForDeploymentPostprocessing = 0; 169 | }; 170 | /* End PBXResourcesBuildPhase section */ 171 | 172 | /* Begin PBXSourcesBuildPhase section */ 173 | C95328071C9AA02200C02B8C /* Sources */ = { 174 | isa = PBXSourcesBuildPhase; 175 | buildActionMask = 2147483647; 176 | files = ( 177 | C95328331C9AA10F00C02B8C /* SGLStaggeredGridLayoutAttributes.m in Sources */, 178 | C95328301C9AA10F00C02B8C /* SGLPost.m in Sources */, 179 | C95328131C9AA02200C02B8C /* SGLAppDelegate.m in Sources */, 180 | C95328321C9AA10F00C02B8C /* SGLStaggeredGridLayout.m in Sources */, 181 | C953282F1C9AA10F00C02B8C /* SGLPostCell.m in Sources */, 182 | C95328101C9AA02200C02B8C /* main.m in Sources */, 183 | C95328311C9AA10F00C02B8C /* SGLPostsViewController.m in Sources */, 184 | ); 185 | runOnlyForDeploymentPostprocessing = 0; 186 | }; 187 | /* End PBXSourcesBuildPhase section */ 188 | 189 | /* Begin PBXVariantGroup section */ 190 | C95328171C9AA02200C02B8C /* Main.storyboard */ = { 191 | isa = PBXVariantGroup; 192 | children = ( 193 | C95328181C9AA02200C02B8C /* Base */, 194 | ); 195 | name = Main.storyboard; 196 | sourceTree = ""; 197 | }; 198 | C953281C1C9AA02200C02B8C /* LaunchScreen.storyboard */ = { 199 | isa = PBXVariantGroup; 200 | children = ( 201 | C953281D1C9AA02200C02B8C /* Base */, 202 | ); 203 | name = LaunchScreen.storyboard; 204 | sourceTree = ""; 205 | }; 206 | /* End PBXVariantGroup section */ 207 | 208 | /* Begin XCBuildConfiguration section */ 209 | C95328201C9AA02200C02B8C /* Debug */ = { 210 | isa = XCBuildConfiguration; 211 | buildSettings = { 212 | ALWAYS_SEARCH_USER_PATHS = NO; 213 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 214 | CLANG_CXX_LIBRARY = "libc++"; 215 | CLANG_ENABLE_MODULES = YES; 216 | CLANG_ENABLE_OBJC_ARC = YES; 217 | CLANG_WARN_BOOL_CONVERSION = YES; 218 | CLANG_WARN_CONSTANT_CONVERSION = YES; 219 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 220 | CLANG_WARN_EMPTY_BODY = YES; 221 | CLANG_WARN_ENUM_CONVERSION = YES; 222 | CLANG_WARN_INT_CONVERSION = YES; 223 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 224 | CLANG_WARN_UNREACHABLE_CODE = YES; 225 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 226 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 227 | COPY_PHASE_STRIP = NO; 228 | DEBUG_INFORMATION_FORMAT = dwarf; 229 | ENABLE_STRICT_OBJC_MSGSEND = YES; 230 | ENABLE_TESTABILITY = YES; 231 | GCC_C_LANGUAGE_STANDARD = gnu99; 232 | GCC_DYNAMIC_NO_PIC = NO; 233 | GCC_NO_COMMON_BLOCKS = YES; 234 | GCC_OPTIMIZATION_LEVEL = 0; 235 | GCC_PREPROCESSOR_DEFINITIONS = ( 236 | "DEBUG=1", 237 | "$(inherited)", 238 | ); 239 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 240 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 241 | GCC_WARN_UNDECLARED_SELECTOR = YES; 242 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 243 | GCC_WARN_UNUSED_FUNCTION = YES; 244 | GCC_WARN_UNUSED_VARIABLE = YES; 245 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 246 | MTL_ENABLE_DEBUG_INFO = YES; 247 | ONLY_ACTIVE_ARCH = YES; 248 | SDKROOT = iphoneos; 249 | }; 250 | name = Debug; 251 | }; 252 | C95328211C9AA02200C02B8C /* Release */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | ALWAYS_SEARCH_USER_PATHS = NO; 256 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 257 | CLANG_CXX_LIBRARY = "libc++"; 258 | CLANG_ENABLE_MODULES = YES; 259 | CLANG_ENABLE_OBJC_ARC = YES; 260 | CLANG_WARN_BOOL_CONVERSION = YES; 261 | CLANG_WARN_CONSTANT_CONVERSION = YES; 262 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 263 | CLANG_WARN_EMPTY_BODY = YES; 264 | CLANG_WARN_ENUM_CONVERSION = YES; 265 | CLANG_WARN_INT_CONVERSION = YES; 266 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 267 | CLANG_WARN_UNREACHABLE_CODE = YES; 268 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 269 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 270 | COPY_PHASE_STRIP = NO; 271 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 272 | ENABLE_NS_ASSERTIONS = NO; 273 | ENABLE_STRICT_OBJC_MSGSEND = YES; 274 | GCC_C_LANGUAGE_STANDARD = gnu99; 275 | GCC_NO_COMMON_BLOCKS = YES; 276 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 277 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 278 | GCC_WARN_UNDECLARED_SELECTOR = YES; 279 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 280 | GCC_WARN_UNUSED_FUNCTION = YES; 281 | GCC_WARN_UNUSED_VARIABLE = YES; 282 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 283 | MTL_ENABLE_DEBUG_INFO = NO; 284 | SDKROOT = iphoneos; 285 | VALIDATE_PRODUCT = YES; 286 | }; 287 | name = Release; 288 | }; 289 | C95328231C9AA02200C02B8C /* Debug */ = { 290 | isa = XCBuildConfiguration; 291 | buildSettings = { 292 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 293 | INFOPLIST_FILE = StaggeredGridLayout/Info.plist; 294 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 295 | PRODUCT_BUNDLE_IDENTIFIER = com.example.StaggeredGridLayout; 296 | PRODUCT_NAME = "$(TARGET_NAME)"; 297 | }; 298 | name = Debug; 299 | }; 300 | C95328241C9AA02200C02B8C /* Release */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 304 | INFOPLIST_FILE = StaggeredGridLayout/Info.plist; 305 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 306 | PRODUCT_BUNDLE_IDENTIFIER = com.example.StaggeredGridLayout; 307 | PRODUCT_NAME = "$(TARGET_NAME)"; 308 | }; 309 | name = Release; 310 | }; 311 | /* End XCBuildConfiguration section */ 312 | 313 | /* Begin XCConfigurationList section */ 314 | C95328061C9AA02200C02B8C /* Build configuration list for PBXProject "StaggeredGridLayout" */ = { 315 | isa = XCConfigurationList; 316 | buildConfigurations = ( 317 | C95328201C9AA02200C02B8C /* Debug */, 318 | C95328211C9AA02200C02B8C /* Release */, 319 | ); 320 | defaultConfigurationIsVisible = 0; 321 | defaultConfigurationName = Release; 322 | }; 323 | C95328221C9AA02200C02B8C /* Build configuration list for PBXNativeTarget "StaggeredGridLayout" */ = { 324 | isa = XCConfigurationList; 325 | buildConfigurations = ( 326 | C95328231C9AA02200C02B8C /* Debug */, 327 | C95328241C9AA02200C02B8C /* Release */, 328 | ); 329 | defaultConfigurationIsVisible = 0; 330 | defaultConfigurationName = Release; 331 | }; 332 | /* End XCConfigurationList section */ 333 | }; 334 | rootObject = C95328031C9AA02200C02B8C /* Project object */; 335 | } 336 | -------------------------------------------------------------------------------- /StaggeredGridLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /StaggeredGridLayout/Assets.xcassets/1200-1200.imageset/1200-1200.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shingohry/StaggeredGridLayout/55bfec7273ff9e3f4ba769e09f325f0d2562e68f/StaggeredGridLayout/Assets.xcassets/1200-1200.imageset/1200-1200.jpg -------------------------------------------------------------------------------- /StaggeredGridLayout/Assets.xcassets/1200-1200.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "1200-1200.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 | } -------------------------------------------------------------------------------- /StaggeredGridLayout/Assets.xcassets/1600-1200.imageset/1600-1200.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shingohry/StaggeredGridLayout/55bfec7273ff9e3f4ba769e09f325f0d2562e68f/StaggeredGridLayout/Assets.xcassets/1600-1200.imageset/1600-1200.jpg -------------------------------------------------------------------------------- /StaggeredGridLayout/Assets.xcassets/1600-1200.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "1600-1200.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 | } -------------------------------------------------------------------------------- /StaggeredGridLayout/Assets.xcassets/900-1200.imageset/900-1200.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shingohry/StaggeredGridLayout/55bfec7273ff9e3f4ba769e09f325f0d2562e68f/StaggeredGridLayout/Assets.xcassets/900-1200.imageset/900-1200.jpg -------------------------------------------------------------------------------- /StaggeredGridLayout/Assets.xcassets/900-1200.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "900-1200.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 | } -------------------------------------------------------------------------------- /StaggeredGridLayout/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 | } -------------------------------------------------------------------------------- /StaggeredGridLayout/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /StaggeredGridLayout/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 | -------------------------------------------------------------------------------- /StaggeredGridLayout/Base.lproj/Main.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 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /StaggeredGridLayout/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 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /StaggeredGridLayout/Posts.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "imageName": "900-1200", 3 | "text": "[1] Banners are up on the side of the Metreon building." 4 | },{ 5 | "text": "[2] Pellentesque habitant morbi tristique." 6 | },{ 7 | "imageName": "1200-1200", 8 | "text": "[3] WWDC 2015 was held June 8th through 12th in the Moscone West conference center." 9 | },{ 10 | "text": "[4] Pellentesque habitant morbi tristique." 11 | },{ 12 | "text": "[5] Text" 13 | },{ 14 | "imageName": "1600-1200", 15 | "text": "[6] The Apple Campus is the headquarters of Apple Inc.\n1 Infinite Loop, Cupertino, CA 95014" 16 | },{ 17 | "text": "[7] Pellentesque habitant morbi tristique." 18 | },{ 19 | "imageName": "900-1200", 20 | "text": "[8] Banners are up on the side of the Metreon building." 21 | },{ 22 | "text": "[9] Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo." 23 | },{ 24 | "imageName": "1600-1200", 25 | "text": "[10] The Apple Campus is the headquarters of Apple Inc.\n1 Infinite Loop, Cupertino, CA 95014" 26 | },{ 27 | "text": "[11] Pellentesque habitant morbi tristique." 28 | }] 29 | -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // StaggeredGridLayout 4 | // 5 | // Created by hiraya.shingo on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGLAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // StaggeredGridLayout 4 | // 5 | // Created by hiraya.shingo on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import "SGLAppDelegate.h" 10 | 11 | @interface SGLAppDelegate () 12 | 13 | @end 14 | 15 | @implementation SGLAppDelegate 16 | 17 | #pragma mark - UIApplicationDelegate 18 | 19 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 20 | { 21 | return YES; 22 | } 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLPost.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGLPost.h 3 | // StaggeredGridLayout 4 | // 5 | // Created by Shingo Hiraya on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGLPost : NSObject 12 | 13 | @property (nonatomic) NSString *text; 14 | @property (nonatomic) UIImage *image; 15 | 16 | + (NSArray *)allPosts; 17 | 18 | @end 19 | -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLPost.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGLPost.m 3 | // StaggeredGridLayout 4 | // 5 | // Created by Shingo Hiraya on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import "SGLPost.h" 10 | 11 | @implementation SGLPost 12 | 13 | #pragma mark - Life Cycle 14 | 15 | - (instancetype)initWithDictionary:(NSDictionary *)dictionary 16 | { 17 | self = [super init]; 18 | 19 | if (self) { 20 | _text = dictionary[@"text"]; 21 | 22 | NSString *imageName = dictionary[@"imageName"]; 23 | _image = imageName ? [UIImage imageNamed:imageName] : nil; 24 | } 25 | 26 | return self; 27 | } 28 | 29 | #pragma mark - Public 30 | 31 | + (NSArray *)allPosts 32 | { 33 | NSString *filePath = [[NSBundle mainBundle] pathForResource:@"Posts" 34 | ofType:@"json"]; 35 | NSData *fileData = [NSData dataWithContentsOfFile:filePath]; 36 | NSArray *jsonPosts = [NSJSONSerialization JSONObjectWithData:fileData 37 | options:NSJSONReadingMutableContainers 38 | error:nil]; 39 | 40 | NSMutableArray *posts = [NSMutableArray array]; 41 | 42 | for (NSDictionary *dictionary in jsonPosts) { 43 | SGLPost *post = [[SGLPost alloc] initWithDictionary:dictionary]; 44 | [posts addObject:post]; 45 | } 46 | 47 | return [posts copy]; 48 | } 49 | 50 | @end 51 | -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLPostCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGLPostCell.h 3 | // StaggeredGridLayout 4 | // 5 | // Created by Shingo Hiraya on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SGLPost.h" 11 | 12 | @interface SGLPostCell : UICollectionViewCell 13 | 14 | @property (nonatomic) SGLPost *post; 15 | 16 | + (CGFloat)imageHeightWithImage:(UIImage *)image 17 | cellWidth:(CGFloat)cellWidth; 18 | 19 | + (CGFloat)bodyHeightWithText:(NSString *)text 20 | cellWidth:(CGFloat)cellWidth; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLPostCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGLPostCell.m 3 | // StaggeredGridLayout 4 | // 5 | // Created by Shingo Hiraya on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import "SGLPostCell.h" 10 | #import "SGLStaggeredGridLayoutAttributes.h" 11 | 12 | @import AVFoundation; 13 | 14 | @interface SGLPostCell () 15 | 16 | @property (nonatomic, weak) IBOutlet UIImageView *imageView; 17 | @property (nonatomic, weak) IBOutlet NSLayoutConstraint *imageViewHeightLayoutConstraint; 18 | @property (nonatomic, weak) IBOutlet UILabel *commentLabel; 19 | 20 | @end 21 | 22 | @implementation SGLPostCell 23 | 24 | #pragma mark - Accessor 25 | 26 | - (void)setPost:(SGLPost *)post 27 | { 28 | _post = post; 29 | 30 | self.imageView.image = post.image; 31 | self.commentLabel.text = post.text; 32 | } 33 | 34 | #pragma mark - UICollectionReusableView 35 | 36 | - (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes 37 | { 38 | if ([layoutAttributes isKindOfClass:[SGLStaggeredGridLayoutAttributes class]]) { 39 | SGLStaggeredGridLayoutAttributes *attributes = (SGLStaggeredGridLayoutAttributes *)layoutAttributes; 40 | self.imageViewHeightLayoutConstraint.constant = attributes.imageHeight; 41 | } 42 | } 43 | 44 | #pragma mark - Public 45 | 46 | // image と セルの幅を基に、ボディ部分に必要な高さを返す 47 | + (CGFloat)imageHeightWithImage:(UIImage *)image 48 | cellWidth:(CGFloat)cellWidth 49 | { 50 | if (image) { 51 | CGRect boundingRect = CGRectMake(0, 0, cellWidth, CGFLOAT_MAX); 52 | CGRect rect = AVMakeRectWithAspectRatioInsideRect(image.size, boundingRect); 53 | 54 | return rect.size.height; 55 | } else { 56 | return 0.0f; 57 | } 58 | } 59 | 60 | // text と セルの幅を基に、ボディ部分に必要な高さを返す 61 | + (CGFloat)bodyHeightWithText:(NSString *)text 62 | cellWidth:(CGFloat)cellWidth 63 | { 64 | CGFloat padding = 4.0f; 65 | CGFloat width = (cellWidth - padding * 2); 66 | 67 | CGRect rect = [text boundingRectWithSize:CGSizeMake(width, CGFLOAT_MAX) 68 | options:NSStringDrawingUsesLineFragmentOrigin 69 | attributes:@{ NSFontAttributeName: [UIFont systemFontOfSize:10.0f]} 70 | context:nil]; 71 | 72 | return padding + ceil(rect.size.height) + padding; 73 | } 74 | 75 | @end 76 | -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLPostsViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGLPostsViewController.h 3 | // StaggeredGridLayout 4 | // 5 | // Created by Shingo Hiraya on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGLPostsViewController : UICollectionViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLPostsViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGLPostsViewController.m 3 | // StaggeredGridLayout 4 | // 5 | // Created by Shingo Hiraya on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import "SGLPostsViewController.h" 10 | #import "SGLPost.h" 11 | #import "SGLStaggeredGridLayout.h" 12 | #import "SGLPostCell.h" 13 | 14 | @import AVFoundation; 15 | 16 | @interface SGLPostsViewController () 17 | 18 | @property (nonatomic) NSArray *posts; 19 | 20 | @end 21 | 22 | @implementation SGLPostsViewController 23 | 24 | #pragma mark - Life Cycle 25 | 26 | - (void)viewDidLoad 27 | { 28 | [super viewDidLoad]; 29 | 30 | self.posts = [SGLPost allPosts]; 31 | 32 | self.collectionView.contentInset = UIEdgeInsetsMake(24.0f, 10.0f, 10.0f, 10.0f); 33 | 34 | SGLStaggeredGridLayout *layout = (SGLStaggeredGridLayout *)self.collectionView.collectionViewLayout; 35 | layout.delegate = self; 36 | } 37 | 38 | - (void)didReceiveMemoryWarning 39 | { 40 | [super didReceiveMemoryWarning]; 41 | } 42 | 43 | #pragma mark - UICollectionViewDataSource 44 | 45 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 46 | { 47 | return self.posts.count; 48 | } 49 | 50 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 51 | { 52 | SGLPostCell *cell = 53 | [collectionView dequeueReusableCellWithReuseIdentifier:@"AnnotatedPhotoCell" 54 | forIndexPath:indexPath]; 55 | cell.post = self.posts[indexPath.item]; 56 | 57 | return cell; 58 | } 59 | 60 | #pragma mark - SGLStaggeredGridLayoutDelegate 61 | 62 | - (CGFloat)collectionView:(UICollectionView *)collectionView 63 | heightForImageAtIndexPath:(NSIndexPath *)indexPath 64 | width:(CGFloat)width 65 | { 66 | return [SGLPostCell imageHeightWithImage:self.posts[indexPath.item].image 67 | cellWidth:width]; 68 | } 69 | 70 | - (CGFloat)collectionView:(UICollectionView *)collectionView 71 | heightForBodyAtIndexPath:(NSIndexPath *)indexPath 72 | width:(CGFloat)width 73 | { 74 | return [SGLPostCell bodyHeightWithText:self.posts[indexPath.item].text 75 | cellWidth:width]; 76 | } 77 | 78 | @end 79 | -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLStaggeredGridLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGLStaggeredGridLayout.h 3 | // StaggeredGridLayout 4 | // 5 | // Created by Shingo Hiraya on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol SGLStaggeredGridLayoutDelegate; 12 | 13 | @interface SGLStaggeredGridLayout : UICollectionViewLayout 14 | 15 | @property (nonatomic, weak) id delegate; 16 | 17 | @end 18 | 19 | @protocol SGLStaggeredGridLayoutDelegate 20 | 21 | - (CGFloat)collectionView:(UICollectionView *)collectionView 22 | heightForImageAtIndexPath:(NSIndexPath *)indexPath 23 | width:(CGFloat)width; 24 | 25 | - (CGFloat)collectionView:(UICollectionView *)collectionView 26 | heightForBodyAtIndexPath:(NSIndexPath *)indexPath 27 | width:(CGFloat)width; 28 | 29 | @end -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLStaggeredGridLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGLStaggeredGridLayout.m 3 | // StaggeredGridLayout 4 | // 5 | // Created by Shingo Hiraya on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import "SGLStaggeredGridLayout.h" 10 | #import "SGLStaggeredGridLayoutAttributes.h" 11 | 12 | static NSInteger const kNumberOfColumns = 2; 13 | static CGFloat const kCellMargin = 10.0f; 14 | 15 | @interface SGLStaggeredGridLayout () 16 | 17 | @property (nonatomic) NSMutableArray<__kindof UICollectionViewLayoutAttributes *> *cachedAttributes; 18 | @property (nonatomic) CGFloat contentHeight; 19 | @property (nonatomic, readonly) CGFloat contentWidth; 20 | 21 | @end 22 | 23 | @implementation SGLStaggeredGridLayout 24 | 25 | #pragma mark - Life Cycle 26 | 27 | - (instancetype)initWithCoder:(NSCoder *)coder 28 | { 29 | self = [super initWithCoder:coder]; 30 | 31 | if (self) { 32 | _contentHeight = 0.0f; 33 | _cachedAttributes = [NSMutableArray new]; 34 | } 35 | 36 | return self; 37 | } 38 | 39 | #pragma mark - Accessor 40 | 41 | // collectionView のコンテンツ部分の幅を返す 42 | - (CGFloat)contentWidth 43 | { 44 | return CGRectGetWidth(self.collectionView.bounds) - (self.collectionView.contentInset.left + self.collectionView.contentInset.right); 45 | } 46 | 47 | #pragma mark - UICollectionViewLayout 48 | 49 | // collectionView のコンテンツ部分のサイズを返す 50 | - (CGSize)collectionViewContentSize 51 | { 52 | return CGSizeMake(self.contentWidth, self.contentHeight); 53 | } 54 | 55 | // 引数で渡されたCGRectの範囲内に表示される要素の UICollectionViewLayoutAttributes の配列を返す 56 | - (NSArray<__kindof UICollectionViewLayoutAttributes *> *)layoutAttributesForElementsInRect:(CGRect)rect 57 | { 58 | NSMutableArray<__kindof UICollectionViewLayoutAttributes *> *layoutAttributes = [NSMutableArray new]; 59 | 60 | for (SGLStaggeredGridLayoutAttributes *attributes in self.cachedAttributes) { 61 | if (CGRectIntersectsRect(attributes.frame, rect)) { 62 | [layoutAttributes addObject:attributes]; 63 | } 64 | } 65 | 66 | return [layoutAttributes copy]; 67 | } 68 | 69 | // NSIndexPathで指定される要素のレイアウト情報を返す 70 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 71 | { 72 | return self.cachedAttributes[indexPath.item]; 73 | } 74 | 75 | // UICollectionView をスクロールしたタイミングでレイアウトを invalidete をする場合は YES を返す 76 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 77 | { 78 | return NO; 79 | } 80 | 81 | - (void)prepareLayout 82 | { 83 | // [1] レイアウト情報をキャッシュ済みの場合は処理を終了する 84 | if (self.cachedAttributes.count > 0) { 85 | return; 86 | } 87 | 88 | NSInteger column = 0; 89 | 90 | // [2] セルの幅を計算する 91 | CGFloat totalHorizontalMargin = (kCellMargin * (kNumberOfColumns - 1)); 92 | CGFloat cellWidth = (self.contentWidth - totalHorizontalMargin) / kNumberOfColumns; 93 | 94 | // [3] 「セルの原点 x」の配列を計算する 95 | NSMutableArray *cellOriginXList = [NSMutableArray new]; 96 | 97 | for (NSInteger i = 0; i < kNumberOfColumns; i++) { 98 | CGFloat originX = i * (cellWidth + kCellMargin); 99 | [cellOriginXList addObject:@(originX)]; 100 | } 101 | 102 | // [4] カラムごとの「現在計算対象にしているセルの原点 y」を格納した配列を計算する 103 | NSMutableArray *currentCellOriginYList = [NSMutableArray new]; 104 | 105 | for (NSInteger i = 0; i < kNumberOfColumns; i++) { 106 | [currentCellOriginYList addObject:@(0.0f)]; 107 | } 108 | 109 | // [5] 各セルのサイズ・原点座標を計算する 110 | for (NSInteger item = 0; item < [self.collectionView numberOfItemsInSection:0]; item++) { 111 | 112 | NSIndexPath *indexPath = [NSIndexPath indexPathForItem:item inSection:0]; 113 | 114 | // [6] セルの写真部分・ボディ部分のそれぞれの高さを取得する 115 | CGFloat imageHeight = [self.delegate collectionView:self.collectionView 116 | heightForImageAtIndexPath:indexPath 117 | width:cellWidth]; 118 | CGFloat bodyHeight = [self.delegate collectionView:self.collectionView 119 | heightForBodyAtIndexPath:indexPath 120 | width:cellWidth]; 121 | CGFloat cellHeight = imageHeight + bodyHeight; 122 | 123 | // [7] セルの frame を作成する 124 | CGRect cellFrame = CGRectMake(cellOriginXList[column].floatValue, 125 | currentCellOriginYList[column].floatValue, 126 | cellWidth, 127 | cellHeight); 128 | 129 | // [8] SGLStaggeredGridLayoutAttributes オブジェクトを作成して、cachedAttributes プロパティに格納する 130 | SGLStaggeredGridLayoutAttributes *attributes = [SGLStaggeredGridLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; 131 | attributes.imageHeight = imageHeight; 132 | attributes.frame = cellFrame; 133 | [self.cachedAttributes addObject:attributes]; 134 | 135 | // [9] UICollectionView のコンテンツの高さを計算して contentHeight プロパティに格納する 136 | self.contentHeight = MAX(self.contentHeight, CGRectGetMaxY(cellFrame)); 137 | 138 | // [10] 次のセルの原点 y を計算する 139 | currentCellOriginYList[column] = @(currentCellOriginYList[column].floatValue + cellHeight + kCellMargin); 140 | 141 | // [11] 次のカラムを決める 142 | __block NSInteger nextColumn = 0; 143 | __block NSNumber *minOriginY = @(MAXFLOAT); 144 | [currentCellOriginYList enumerateObjectsUsingBlock:^(NSNumber *originY, NSUInteger index, BOOL *stop) { 145 | if ([originY compare:minOriginY] == NSOrderedAscending) { 146 | minOriginY = originY; 147 | nextColumn = index; 148 | } 149 | }]; 150 | 151 | column = nextColumn; 152 | } 153 | } 154 | 155 | // UICollectionViewLayoutAttributes のサブクラスのクラスオブジェクトを返す 156 | + (Class)layoutAttributesClass 157 | { 158 | return [SGLStaggeredGridLayoutAttributes class]; 159 | } 160 | 161 | @end 162 | -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLStaggeredGridLayoutAttributes.h: -------------------------------------------------------------------------------- 1 | // 2 | // SGLStaggeredGridLayoutAttributes.h 3 | // StaggeredGridLayout 4 | // 5 | // Created by Shingo Hiraya on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SGLStaggeredGridLayoutAttributes : UICollectionViewLayoutAttributes 12 | 13 | @property (nonatomic) CGFloat imageHeight; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /StaggeredGridLayout/SGLStaggeredGridLayoutAttributes.m: -------------------------------------------------------------------------------- 1 | // 2 | // SGLStaggeredGridLayoutAttributes.m 3 | // StaggeredGridLayout 4 | // 5 | // Created by Shingo Hiraya on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import "SGLStaggeredGridLayoutAttributes.h" 10 | 11 | @implementation SGLStaggeredGridLayoutAttributes 12 | 13 | #pragma mark - Life Cycle 14 | 15 | - (instancetype)init 16 | { 17 | self = [super init]; 18 | 19 | if (self) { 20 | _imageHeight = 0.0f; 21 | } 22 | 23 | return self; 24 | } 25 | 26 | #pragma mark - NSCopying 27 | 28 | - (id)copyWithZone:(NSZone *)zone 29 | { 30 | SGLStaggeredGridLayoutAttributes *copy = [super copyWithZone:zone]; 31 | copy.imageHeight = self.imageHeight; 32 | 33 | return copy; 34 | } 35 | 36 | #pragma mark - NSObject 37 | 38 | - (BOOL)isEqual:(id)object 39 | { 40 | if ([object isKindOfClass:[self class]]) { 41 | SGLStaggeredGridLayoutAttributes *attributtes = object; 42 | 43 | if (attributtes.imageHeight == self.imageHeight) { 44 | return [super isEqual:attributtes]; 45 | } 46 | } 47 | 48 | return NO; 49 | } 50 | 51 | @end 52 | -------------------------------------------------------------------------------- /StaggeredGridLayout/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // StaggeredGridLayout 4 | // 5 | // Created by hiraya.shingo on 2016/03/17. 6 | // Copyright © 2016年 Shingo Hiraya. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "SGLAppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([SGLAppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shingohry/StaggeredGridLayout/55bfec7273ff9e3f4ba769e09f325f0d2562e68f/screenshot.png --------------------------------------------------------------------------------