├── .DS_Store ├── .gitignore ├── GooglePlusLikeLayout.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── gautam.xcuserdatad │ │ └── UserInterfaceState.xcuserstate └── xcuserdata │ └── gautam.xcuserdatad │ └── xcschemes │ ├── GooglePlusLikeLayout.xcscheme │ └── xcschememanagement.plist ├── GooglePlusLikeLayout ├── .DS_Store ├── Default-568h@2x.png ├── Default.png ├── Default@2x.png ├── GLAppDelegate.h ├── GLAppDelegate.m ├── GLCell.h ├── GLCell.m ├── GLDemoViewController.h ├── GLDemoViewController.m ├── GLGooglePlusLikeLayout │ ├── GLFlowLayout.h │ ├── GLFlowLayout.m │ ├── GLGooglePlusLikeLayout.h │ └── GLGooglePlusLikeLayout.m ├── GLSectionView.h ├── GLSectionView.m ├── GooglePlusLikeLayout-Info.plist ├── GooglePlusLikeLayout-Prefix.pch ├── SVPullToRefresh │ ├── SVPullToRefresh.h │ ├── UIScrollView+SVInfiniteScrolling.h │ ├── UIScrollView+SVInfiniteScrolling.m │ ├── UIScrollView+SVPullToRefresh.h │ └── UIScrollView+SVPullToRefresh.m ├── en.lproj │ └── InfoPlist.strings └── main.m ├── README.md ├── Screen Shot 1.png ├── Screen Shot 2.png ├── Screen Shot 3.png ├── Screen Shot 4.png ├── Screen Shot 5.png └── Screen Shot 6.png /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/.DS_Store -------------------------------------------------------------------------------- /.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 | *.xcworkspace 13 | !default.xcworkspace 14 | xcuserdata 15 | profile 16 | *.moved-aside 17 | DerivedData 18 | .idea/ 19 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C744DE981736A1C500A76EAC /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C744DE971736A1C500A76EAC /* UIKit.framework */; }; 11 | C744DE9A1736A1C500A76EAC /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C744DE991736A1C500A76EAC /* Foundation.framework */; }; 12 | C744DE9C1736A1C500A76EAC /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C744DE9B1736A1C500A76EAC /* CoreGraphics.framework */; }; 13 | C744DEA21736A1C500A76EAC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = C744DEA01736A1C500A76EAC /* InfoPlist.strings */; }; 14 | C744DEA41736A1C500A76EAC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C744DEA31736A1C500A76EAC /* main.m */; }; 15 | C744DEA81736A1C500A76EAC /* GLAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = C744DEA71736A1C500A76EAC /* GLAppDelegate.m */; }; 16 | C744DEAA1736A1C500A76EAC /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = C744DEA91736A1C500A76EAC /* Default.png */; }; 17 | C744DEAC1736A1C500A76EAC /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C744DEAB1736A1C500A76EAC /* Default@2x.png */; }; 18 | C744DEAE1736A1C500A76EAC /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = C744DEAD1736A1C500A76EAC /* Default-568h@2x.png */; }; 19 | C744DEBC1736BCF700A76EAC /* GLDemoViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C744DEBB1736BCF700A76EAC /* GLDemoViewController.m */; }; 20 | C744DEBF1736BDDF00A76EAC /* GLCell.m in Sources */ = {isa = PBXBuildFile; fileRef = C744DEBE1736BDDF00A76EAC /* GLCell.m */; }; 21 | C744DEC61736C15500A76EAC /* UIScrollView+SVInfiniteScrolling.m in Sources */ = {isa = PBXBuildFile; fileRef = C744DEC31736C15500A76EAC /* UIScrollView+SVInfiniteScrolling.m */; }; 22 | C744DEC71736C15500A76EAC /* UIScrollView+SVPullToRefresh.m in Sources */ = {isa = PBXBuildFile; fileRef = C744DEC51736C15500A76EAC /* UIScrollView+SVPullToRefresh.m */; }; 23 | C744DEC91736C1D100A76EAC /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C744DEC81736C1D100A76EAC /* QuartzCore.framework */; }; 24 | C744DECF1736C3D500A76EAC /* GLFlowLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = C744DECC1736C3D400A76EAC /* GLFlowLayout.m */; }; 25 | C744DED01736C3D500A76EAC /* GLGooglePlusLikeLayout.m in Sources */ = {isa = PBXBuildFile; fileRef = C744DECE1736C3D500A76EAC /* GLGooglePlusLikeLayout.m */; }; 26 | C744DED31736CA3300A76EAC /* GLSectionView.m in Sources */ = {isa = PBXBuildFile; fileRef = C744DED21736CA3300A76EAC /* GLSectionView.m */; }; 27 | /* End PBXBuildFile section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | C744DE941736A1C500A76EAC /* GooglePlusLikeLayout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = GooglePlusLikeLayout.app; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | C744DE971736A1C500A76EAC /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; 32 | C744DE991736A1C500A76EAC /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 33 | C744DE9B1736A1C500A76EAC /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 34 | C744DE9F1736A1C500A76EAC /* GooglePlusLikeLayout-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "GooglePlusLikeLayout-Info.plist"; sourceTree = ""; }; 35 | C744DEA11736A1C500A76EAC /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 36 | C744DEA31736A1C500A76EAC /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 37 | C744DEA51736A1C500A76EAC /* GooglePlusLikeLayout-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "GooglePlusLikeLayout-Prefix.pch"; sourceTree = ""; }; 38 | C744DEA61736A1C500A76EAC /* GLAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GLAppDelegate.h; sourceTree = ""; }; 39 | C744DEA71736A1C500A76EAC /* GLAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GLAppDelegate.m; sourceTree = ""; }; 40 | C744DEA91736A1C500A76EAC /* Default.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Default.png; sourceTree = ""; }; 41 | C744DEAB1736A1C500A76EAC /* Default@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default@2x.png"; sourceTree = ""; }; 42 | C744DEAD1736A1C500A76EAC /* Default-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "Default-568h@2x.png"; sourceTree = ""; }; 43 | C744DEBA1736BCF700A76EAC /* GLDemoViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLDemoViewController.h; sourceTree = ""; }; 44 | C744DEBB1736BCF700A76EAC /* GLDemoViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLDemoViewController.m; sourceTree = ""; }; 45 | C744DEBD1736BDDF00A76EAC /* GLCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLCell.h; sourceTree = ""; }; 46 | C744DEBE1736BDDF00A76EAC /* GLCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLCell.m; sourceTree = ""; }; 47 | C744DEC11736C15500A76EAC /* SVPullToRefresh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SVPullToRefresh.h; sourceTree = ""; }; 48 | C744DEC21736C15500A76EAC /* UIScrollView+SVInfiniteScrolling.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+SVInfiniteScrolling.h"; sourceTree = ""; }; 49 | C744DEC31736C15500A76EAC /* UIScrollView+SVInfiniteScrolling.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+SVInfiniteScrolling.m"; sourceTree = ""; }; 50 | C744DEC41736C15500A76EAC /* UIScrollView+SVPullToRefresh.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIScrollView+SVPullToRefresh.h"; sourceTree = ""; }; 51 | C744DEC51736C15500A76EAC /* UIScrollView+SVPullToRefresh.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIScrollView+SVPullToRefresh.m"; sourceTree = ""; }; 52 | C744DEC81736C1D100A76EAC /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; }; 53 | C744DECB1736C3D400A76EAC /* GLFlowLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLFlowLayout.h; sourceTree = ""; }; 54 | C744DECC1736C3D400A76EAC /* GLFlowLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLFlowLayout.m; sourceTree = ""; }; 55 | C744DECD1736C3D500A76EAC /* GLGooglePlusLikeLayout.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLGooglePlusLikeLayout.h; sourceTree = ""; }; 56 | C744DECE1736C3D500A76EAC /* GLGooglePlusLikeLayout.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLGooglePlusLikeLayout.m; sourceTree = ""; }; 57 | C744DED11736CA3300A76EAC /* GLSectionView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GLSectionView.h; sourceTree = ""; }; 58 | C744DED21736CA3300A76EAC /* GLSectionView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GLSectionView.m; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | C744DE911736A1C500A76EAC /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | C744DEC91736C1D100A76EAC /* QuartzCore.framework in Frameworks */, 67 | C744DE981736A1C500A76EAC /* UIKit.framework in Frameworks */, 68 | C744DE9A1736A1C500A76EAC /* Foundation.framework in Frameworks */, 69 | C744DE9C1736A1C500A76EAC /* CoreGraphics.framework in Frameworks */, 70 | ); 71 | runOnlyForDeploymentPostprocessing = 0; 72 | }; 73 | /* End PBXFrameworksBuildPhase section */ 74 | 75 | /* Begin PBXGroup section */ 76 | C744DE8B1736A1C500A76EAC = { 77 | isa = PBXGroup; 78 | children = ( 79 | C744DEC81736C1D100A76EAC /* QuartzCore.framework */, 80 | C744DE9D1736A1C500A76EAC /* GooglePlusLikeLayout */, 81 | C744DE961736A1C500A76EAC /* Frameworks */, 82 | C744DE951736A1C500A76EAC /* Products */, 83 | ); 84 | sourceTree = ""; 85 | }; 86 | C744DE951736A1C500A76EAC /* Products */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | C744DE941736A1C500A76EAC /* GooglePlusLikeLayout.app */, 90 | ); 91 | name = Products; 92 | sourceTree = ""; 93 | }; 94 | C744DE961736A1C500A76EAC /* Frameworks */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | C744DE971736A1C500A76EAC /* UIKit.framework */, 98 | C744DE991736A1C500A76EAC /* Foundation.framework */, 99 | C744DE9B1736A1C500A76EAC /* CoreGraphics.framework */, 100 | ); 101 | name = Frameworks; 102 | sourceTree = ""; 103 | }; 104 | C744DE9D1736A1C500A76EAC /* GooglePlusLikeLayout */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | C744DECA1736C36F00A76EAC /* GLGooglePlusLikeLayout */, 108 | C744DEC01736C12D00A76EAC /* SVPullToRefresh */, 109 | C744DEA61736A1C500A76EAC /* GLAppDelegate.h */, 110 | C744DEA71736A1C500A76EAC /* GLAppDelegate.m */, 111 | C744DEBA1736BCF700A76EAC /* GLDemoViewController.h */, 112 | C744DEBB1736BCF700A76EAC /* GLDemoViewController.m */, 113 | C744DED11736CA3300A76EAC /* GLSectionView.h */, 114 | C744DED21736CA3300A76EAC /* GLSectionView.m */, 115 | C744DEBD1736BDDF00A76EAC /* GLCell.h */, 116 | C744DEBE1736BDDF00A76EAC /* GLCell.m */, 117 | C744DE9E1736A1C500A76EAC /* Supporting Files */, 118 | ); 119 | path = GooglePlusLikeLayout; 120 | sourceTree = ""; 121 | }; 122 | C744DE9E1736A1C500A76EAC /* Supporting Files */ = { 123 | isa = PBXGroup; 124 | children = ( 125 | C744DE9F1736A1C500A76EAC /* GooglePlusLikeLayout-Info.plist */, 126 | C744DEA01736A1C500A76EAC /* InfoPlist.strings */, 127 | C744DEA31736A1C500A76EAC /* main.m */, 128 | C744DEA51736A1C500A76EAC /* GooglePlusLikeLayout-Prefix.pch */, 129 | C744DEA91736A1C500A76EAC /* Default.png */, 130 | C744DEAB1736A1C500A76EAC /* Default@2x.png */, 131 | C744DEAD1736A1C500A76EAC /* Default-568h@2x.png */, 132 | ); 133 | name = "Supporting Files"; 134 | sourceTree = ""; 135 | }; 136 | C744DEC01736C12D00A76EAC /* SVPullToRefresh */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | C744DEC11736C15500A76EAC /* SVPullToRefresh.h */, 140 | C744DEC21736C15500A76EAC /* UIScrollView+SVInfiniteScrolling.h */, 141 | C744DEC31736C15500A76EAC /* UIScrollView+SVInfiniteScrolling.m */, 142 | C744DEC41736C15500A76EAC /* UIScrollView+SVPullToRefresh.h */, 143 | C744DEC51736C15500A76EAC /* UIScrollView+SVPullToRefresh.m */, 144 | ); 145 | path = SVPullToRefresh; 146 | sourceTree = ""; 147 | }; 148 | C744DECA1736C36F00A76EAC /* GLGooglePlusLikeLayout */ = { 149 | isa = PBXGroup; 150 | children = ( 151 | C744DECB1736C3D400A76EAC /* GLFlowLayout.h */, 152 | C744DECC1736C3D400A76EAC /* GLFlowLayout.m */, 153 | C744DECD1736C3D500A76EAC /* GLGooglePlusLikeLayout.h */, 154 | C744DECE1736C3D500A76EAC /* GLGooglePlusLikeLayout.m */, 155 | ); 156 | path = GLGooglePlusLikeLayout; 157 | sourceTree = ""; 158 | }; 159 | /* End PBXGroup section */ 160 | 161 | /* Begin PBXNativeTarget section */ 162 | C744DE931736A1C500A76EAC /* GooglePlusLikeLayout */ = { 163 | isa = PBXNativeTarget; 164 | buildConfigurationList = C744DEB11736A1C500A76EAC /* Build configuration list for PBXNativeTarget "GooglePlusLikeLayout" */; 165 | buildPhases = ( 166 | C744DE901736A1C500A76EAC /* Sources */, 167 | C744DE911736A1C500A76EAC /* Frameworks */, 168 | C744DE921736A1C500A76EAC /* Resources */, 169 | ); 170 | buildRules = ( 171 | ); 172 | dependencies = ( 173 | ); 174 | name = GooglePlusLikeLayout; 175 | productName = GooglePlusLikeLayout; 176 | productReference = C744DE941736A1C500A76EAC /* GooglePlusLikeLayout.app */; 177 | productType = "com.apple.product-type.application"; 178 | }; 179 | /* End PBXNativeTarget section */ 180 | 181 | /* Begin PBXProject section */ 182 | C744DE8C1736A1C500A76EAC /* Project object */ = { 183 | isa = PBXProject; 184 | attributes = { 185 | CLASSPREFIX = GL; 186 | LastUpgradeCheck = 0460; 187 | ORGANIZATIONNAME = "Gautam Lodhiya"; 188 | }; 189 | buildConfigurationList = C744DE8F1736A1C500A76EAC /* Build configuration list for PBXProject "GooglePlusLikeLayout" */; 190 | compatibilityVersion = "Xcode 3.2"; 191 | developmentRegion = English; 192 | hasScannedForEncodings = 0; 193 | knownRegions = ( 194 | en, 195 | ); 196 | mainGroup = C744DE8B1736A1C500A76EAC; 197 | productRefGroup = C744DE951736A1C500A76EAC /* Products */; 198 | projectDirPath = ""; 199 | projectRoot = ""; 200 | targets = ( 201 | C744DE931736A1C500A76EAC /* GooglePlusLikeLayout */, 202 | ); 203 | }; 204 | /* End PBXProject section */ 205 | 206 | /* Begin PBXResourcesBuildPhase section */ 207 | C744DE921736A1C500A76EAC /* Resources */ = { 208 | isa = PBXResourcesBuildPhase; 209 | buildActionMask = 2147483647; 210 | files = ( 211 | C744DEA21736A1C500A76EAC /* InfoPlist.strings in Resources */, 212 | C744DEAA1736A1C500A76EAC /* Default.png in Resources */, 213 | C744DEAC1736A1C500A76EAC /* Default@2x.png in Resources */, 214 | C744DEAE1736A1C500A76EAC /* Default-568h@2x.png in Resources */, 215 | ); 216 | runOnlyForDeploymentPostprocessing = 0; 217 | }; 218 | /* End PBXResourcesBuildPhase section */ 219 | 220 | /* Begin PBXSourcesBuildPhase section */ 221 | C744DE901736A1C500A76EAC /* Sources */ = { 222 | isa = PBXSourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | C744DEA41736A1C500A76EAC /* main.m in Sources */, 226 | C744DEA81736A1C500A76EAC /* GLAppDelegate.m in Sources */, 227 | C744DEBC1736BCF700A76EAC /* GLDemoViewController.m in Sources */, 228 | C744DEBF1736BDDF00A76EAC /* GLCell.m in Sources */, 229 | C744DEC61736C15500A76EAC /* UIScrollView+SVInfiniteScrolling.m in Sources */, 230 | C744DEC71736C15500A76EAC /* UIScrollView+SVPullToRefresh.m in Sources */, 231 | C744DECF1736C3D500A76EAC /* GLFlowLayout.m in Sources */, 232 | C744DED01736C3D500A76EAC /* GLGooglePlusLikeLayout.m in Sources */, 233 | C744DED31736CA3300A76EAC /* GLSectionView.m in Sources */, 234 | ); 235 | runOnlyForDeploymentPostprocessing = 0; 236 | }; 237 | /* End PBXSourcesBuildPhase section */ 238 | 239 | /* Begin PBXVariantGroup section */ 240 | C744DEA01736A1C500A76EAC /* InfoPlist.strings */ = { 241 | isa = PBXVariantGroup; 242 | children = ( 243 | C744DEA11736A1C500A76EAC /* en */, 244 | ); 245 | name = InfoPlist.strings; 246 | sourceTree = ""; 247 | }; 248 | /* End PBXVariantGroup section */ 249 | 250 | /* Begin XCBuildConfiguration section */ 251 | C744DEAF1736A1C500A76EAC /* Debug */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | ALWAYS_SEARCH_USER_PATHS = NO; 255 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 256 | CLANG_CXX_LIBRARY = "libc++"; 257 | CLANG_ENABLE_OBJC_ARC = YES; 258 | CLANG_WARN_CONSTANT_CONVERSION = YES; 259 | CLANG_WARN_EMPTY_BODY = YES; 260 | CLANG_WARN_ENUM_CONVERSION = YES; 261 | CLANG_WARN_INT_CONVERSION = YES; 262 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 263 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 264 | COPY_PHASE_STRIP = NO; 265 | GCC_C_LANGUAGE_STANDARD = gnu99; 266 | GCC_DYNAMIC_NO_PIC = NO; 267 | GCC_OPTIMIZATION_LEVEL = 0; 268 | GCC_PREPROCESSOR_DEFINITIONS = ( 269 | "DEBUG=1", 270 | "$(inherited)", 271 | ); 272 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 273 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 274 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 275 | GCC_WARN_UNUSED_VARIABLE = YES; 276 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 277 | ONLY_ACTIVE_ARCH = YES; 278 | SDKROOT = iphoneos; 279 | TARGETED_DEVICE_FAMILY = 2; 280 | }; 281 | name = Debug; 282 | }; 283 | C744DEB01736A1C500A76EAC /* Release */ = { 284 | isa = XCBuildConfiguration; 285 | buildSettings = { 286 | ALWAYS_SEARCH_USER_PATHS = NO; 287 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 288 | CLANG_CXX_LIBRARY = "libc++"; 289 | CLANG_ENABLE_OBJC_ARC = YES; 290 | CLANG_WARN_CONSTANT_CONVERSION = YES; 291 | CLANG_WARN_EMPTY_BODY = YES; 292 | CLANG_WARN_ENUM_CONVERSION = YES; 293 | CLANG_WARN_INT_CONVERSION = YES; 294 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 295 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 296 | COPY_PHASE_STRIP = YES; 297 | GCC_C_LANGUAGE_STANDARD = gnu99; 298 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 299 | GCC_WARN_UNINITIALIZED_AUTOS = YES; 300 | GCC_WARN_UNUSED_VARIABLE = YES; 301 | IPHONEOS_DEPLOYMENT_TARGET = 6.1; 302 | OTHER_CFLAGS = "-DNS_BLOCK_ASSERTIONS=1"; 303 | SDKROOT = iphoneos; 304 | TARGETED_DEVICE_FAMILY = 2; 305 | VALIDATE_PRODUCT = YES; 306 | }; 307 | name = Release; 308 | }; 309 | C744DEB21736A1C500A76EAC /* Debug */ = { 310 | isa = XCBuildConfiguration; 311 | buildSettings = { 312 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 313 | GCC_PREFIX_HEADER = "GooglePlusLikeLayout/GooglePlusLikeLayout-Prefix.pch"; 314 | INFOPLIST_FILE = "GooglePlusLikeLayout/GooglePlusLikeLayout-Info.plist"; 315 | PRODUCT_NAME = "$(TARGET_NAME)"; 316 | WRAPPER_EXTENSION = app; 317 | }; 318 | name = Debug; 319 | }; 320 | C744DEB31736A1C500A76EAC /* Release */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 324 | GCC_PREFIX_HEADER = "GooglePlusLikeLayout/GooglePlusLikeLayout-Prefix.pch"; 325 | INFOPLIST_FILE = "GooglePlusLikeLayout/GooglePlusLikeLayout-Info.plist"; 326 | PRODUCT_NAME = "$(TARGET_NAME)"; 327 | WRAPPER_EXTENSION = app; 328 | }; 329 | name = Release; 330 | }; 331 | /* End XCBuildConfiguration section */ 332 | 333 | /* Begin XCConfigurationList section */ 334 | C744DE8F1736A1C500A76EAC /* Build configuration list for PBXProject "GooglePlusLikeLayout" */ = { 335 | isa = XCConfigurationList; 336 | buildConfigurations = ( 337 | C744DEAF1736A1C500A76EAC /* Debug */, 338 | C744DEB01736A1C500A76EAC /* Release */, 339 | ); 340 | defaultConfigurationIsVisible = 0; 341 | defaultConfigurationName = Release; 342 | }; 343 | C744DEB11736A1C500A76EAC /* Build configuration list for PBXNativeTarget "GooglePlusLikeLayout" */ = { 344 | isa = XCConfigurationList; 345 | buildConfigurations = ( 346 | C744DEB21736A1C500A76EAC /* Debug */, 347 | C744DEB31736A1C500A76EAC /* Release */, 348 | ); 349 | defaultConfigurationIsVisible = 0; 350 | defaultConfigurationName = Release; 351 | }; 352 | /* End XCConfigurationList section */ 353 | }; 354 | rootObject = C744DE8C1736A1C500A76EAC /* Project object */; 355 | } 356 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout.xcodeproj/project.xcworkspace/xcuserdata/gautam.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/GooglePlusLikeLayout.xcodeproj/project.xcworkspace/xcuserdata/gautam.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /GooglePlusLikeLayout.xcodeproj/xcuserdata/gautam.xcuserdatad/xcschemes/GooglePlusLikeLayout.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 51 | 52 | 58 | 59 | 60 | 61 | 62 | 63 | 69 | 70 | 76 | 77 | 78 | 79 | 81 | 82 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout.xcodeproj/xcuserdata/gautam.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | GooglePlusLikeLayout.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | C744DE931736A1C500A76EAC 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/GooglePlusLikeLayout/.DS_Store -------------------------------------------------------------------------------- /GooglePlusLikeLayout/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/GooglePlusLikeLayout/Default-568h@2x.png -------------------------------------------------------------------------------- /GooglePlusLikeLayout/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/GooglePlusLikeLayout/Default.png -------------------------------------------------------------------------------- /GooglePlusLikeLayout/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/GooglePlusLikeLayout/Default@2x.png -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLAppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // GLAppDelegate.h 3 | // GooglePlusLikeLayout 4 | // 5 | // Created by Gautam Lodhiya on 05/05/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GLAppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLAppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // GLAppDelegate.m 3 | // GooglePlusLikeLayout 4 | // 5 | // Created by Gautam Lodhiya on 05/05/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import "GLAppDelegate.h" 10 | #import "GLDemoViewController.h" 11 | 12 | @implementation GLAppDelegate 13 | 14 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 15 | { 16 | self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; 17 | // Override point for customization after application launch. 18 | 19 | GLDemoViewController* viewController = [[GLDemoViewController alloc] initWithNibName:nil bundle:nil]; 20 | [self.window setRootViewController:viewController]; 21 | [self.window makeKeyAndVisible]; 22 | return YES; 23 | } 24 | 25 | - (void)applicationWillResignActive:(UIApplication *)application 26 | { 27 | // 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. 28 | // 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. 29 | } 30 | 31 | - (void)applicationDidEnterBackground:(UIApplication *)application 32 | { 33 | // 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. 34 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 35 | } 36 | 37 | - (void)applicationWillEnterForeground:(UIApplication *)application 38 | { 39 | // 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. 40 | } 41 | 42 | - (void)applicationDidBecomeActive:(UIApplication *)application 43 | { 44 | // 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. 45 | } 46 | 47 | - (void)applicationWillTerminate:(UIApplication *)application 48 | { 49 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 50 | } 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // GLCell.h 3 | // GLGooglePlusLayout 4 | // 5 | // Created by Gautam Lodhiya on 21/04/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GLCell : UICollectionViewCell 12 | @property (nonatomic, copy) NSString *displayString; 13 | @end 14 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // GLCell.m 3 | // GLGooglePlusLayout 4 | // 5 | // Created by Gautam Lodhiya on 21/04/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import "GLCell.h" 10 | 11 | @interface GLCell() 12 | @property (nonatomic, strong) UILabel *displayLabel; 13 | @end 14 | 15 | @implementation GLCell 16 | 17 | #pragma mark - Accessors 18 | - (UILabel *)displayLabel 19 | { 20 | if (!_displayLabel) { 21 | _displayLabel = [[UILabel alloc] initWithFrame:self.contentView.bounds]; 22 | _displayLabel.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 23 | _displayLabel.backgroundColor = [UIColor lightGrayColor]; 24 | _displayLabel.textColor = [UIColor whiteColor]; 25 | _displayLabel.textAlignment = NSTextAlignmentCenter; 26 | } 27 | return _displayLabel; 28 | } 29 | 30 | - (void)setDisplayString:(NSString *)displayString 31 | { 32 | if (![_displayString isEqualToString:displayString]) { 33 | _displayString = [displayString copy]; 34 | self.displayLabel.text = _displayString; 35 | } 36 | } 37 | 38 | #pragma mark - Life Cycle 39 | - (void)dealloc 40 | { 41 | [_displayLabel removeFromSuperview]; 42 | _displayLabel = nil; 43 | } 44 | 45 | - (id)initWithFrame:(CGRect)frame 46 | { 47 | self = [super initWithFrame:frame]; 48 | if (self) { 49 | // Initialization code 50 | [self.contentView addSubview:self.displayLabel]; 51 | } 52 | return self; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLDemoViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // GLDemoViewController.h 3 | // GooglePlusLikeLayout 4 | // 5 | // Created by Gautam Lodhiya on 05/05/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GLDemoViewController : UIViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLDemoViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // GLDemoViewController.m 3 | // GooglePlusLikeLayout 4 | // 5 | // Created by Gautam Lodhiya on 05/05/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import "GLDemoViewController.h" 10 | #import "SVPullToRefresh.h" 11 | #import "GLGooglePlusLikeLayout.h" 12 | #import "GLSectionView.h" 13 | #import "GLCell.h" 14 | 15 | #define DATA_TO_ADD 30 16 | #define SECTION_IDENTIFIER @"section" 17 | #define CELL_IDENTIFIER @"cell" 18 | 19 | @interface GLDemoViewController () 20 | @property (nonatomic, strong) UICollectionView *collectionView; 21 | @property (nonatomic, strong) NSMutableArray *dataSource; 22 | @end 23 | 24 | @implementation GLDemoViewController 25 | 26 | #pragma mark - 27 | #pragma mark - Accessors 28 | 29 | - (UICollectionView *)collectionView 30 | { 31 | if (!_collectionView) { 32 | GLGooglePlusLikeLayout* layout = [[GLGooglePlusLikeLayout alloc] init]; 33 | CGFloat width = floorf((CGRectGetWidth(self.view.bounds) / 2)); 34 | layout.minimumItemSize = CGSizeMake(width, width); 35 | 36 | _collectionView = [[UICollectionView alloc] initWithFrame:self.view.bounds collectionViewLayout:layout]; 37 | [_collectionView setDelegate:self]; 38 | [_collectionView setDataSource:self]; 39 | _collectionView.backgroundColor = [UIColor colorWithWhite:0.85f alpha:1.0f]; 40 | [_collectionView setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin]; 41 | } 42 | return _collectionView; 43 | } 44 | 45 | - (NSMutableArray *)dataSource 46 | { 47 | if (!_dataSource) { 48 | _dataSource = [NSMutableArray array]; 49 | } 50 | return _dataSource; 51 | } 52 | 53 | #pragma mark - 54 | #pragma mark - Life Cycle 55 | 56 | - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil 57 | { 58 | self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 59 | if (self) { 60 | // Custom initialization 61 | [self commonInit]; 62 | } 63 | return self; 64 | } 65 | 66 | - (void)loadView 67 | { 68 | [super loadView]; 69 | [self.view addSubview:self.collectionView]; 70 | } 71 | 72 | - (void)viewDidLoad 73 | { 74 | [super viewDidLoad]; 75 | // Do any additional setup after loading the view. 76 | 77 | // configure views 78 | GLGooglePlusLikeLayout *layout = (GLGooglePlusLikeLayout *)[self.collectionView collectionViewLayout]; 79 | [layout setHasHeaders:YES]; 80 | 81 | [self.collectionView registerClass:[GLSectionView class] forSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:SECTION_IDENTIFIER]; 82 | [self.collectionView registerClass:[GLCell class] forCellWithReuseIdentifier:CELL_IDENTIFIER]; 83 | 84 | 85 | // load data 86 | [self configurePullToRefresh]; 87 | [self.collectionView triggerPullToRefresh]; 88 | } 89 | 90 | - (void)didReceiveMemoryWarning 91 | { 92 | [super didReceiveMemoryWarning]; 93 | // Dispose of any resources that can be recreated. 94 | [self.dataSource removeAllObjects]; 95 | self.dataSource = nil; 96 | } 97 | 98 | - (void)dealloc 99 | { 100 | [self.dataSource removeAllObjects]; 101 | self.dataSource = nil; 102 | 103 | [self.collectionView removeFromSuperview]; 104 | self.collectionView = nil; 105 | } 106 | 107 | 108 | 109 | #pragma mark - 110 | #pragma mark - Orientation 111 | 112 | - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation 113 | duration:(NSTimeInterval)duration 114 | { 115 | [super willAnimateRotationToInterfaceOrientation:toInterfaceOrientation 116 | duration:duration]; 117 | [self updateLayout]; 118 | } 119 | 120 | - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)orientation 121 | { 122 | return YES; 123 | } 124 | 125 | 126 | #pragma mark - UICollectionView Stuff 127 | 128 | - (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath 129 | { 130 | UICollectionReusableView *suppView; 131 | 132 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 133 | if (indexPath.section == 0) { 134 | GLSectionView *sectionView = [collectionView dequeueReusableSupplementaryViewOfKind:UICollectionElementKindSectionHeader withReuseIdentifier:SECTION_IDENTIFIER forIndexPath:indexPath]; 135 | [sectionView setBackgroundColor:[UIColor darkGrayColor]]; 136 | [sectionView setDisplayString:@"Section 1"]; 137 | suppView = sectionView; 138 | } 139 | } 140 | 141 | return suppView; 142 | } 143 | 144 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout*)collectionViewLayout heightForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; 145 | { 146 | return 30; 147 | } 148 | 149 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 150 | { 151 | return self.dataSource.count; 152 | } 153 | 154 | - (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView 155 | { 156 | return 1; 157 | } 158 | 159 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 160 | { 161 | GLCell *cell = (GLCell *)[collectionView dequeueReusableCellWithReuseIdentifier:CELL_IDENTIFIER forIndexPath:indexPath]; 162 | cell.displayString = [NSString stringWithFormat:@"%d", indexPath.row]; 163 | return cell; 164 | } 165 | 166 | #pragma mark - UICollectionViewDelegate 167 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath 168 | { 169 | CGSize size = CGSizeZero; 170 | 171 | if (indexPath.item <= (self.dataSource.count - 1)) { 172 | NSValue *sizeValue = self.dataSource[indexPath.item]; 173 | size = [sizeValue CGSizeValue]; 174 | return size; 175 | } 176 | 177 | return size; 178 | } 179 | 180 | 181 | 182 | #pragma mark - 183 | #pragma mark - Private methods 184 | 185 | - (void)commonInit 186 | { 187 | self.view.backgroundColor = [UIColor grayColor]; 188 | } 189 | 190 | - (void)updateLayout 191 | { 192 | GLGooglePlusLikeLayout *layout = (GLGooglePlusLikeLayout *)[self.collectionView collectionViewLayout]; 193 | CGFloat width = floorf((CGRectGetWidth(self.view.bounds) / 2)); 194 | layout.minimumItemSize = CGSizeMake(width, width); 195 | } 196 | 197 | - (CGSize)randomSize 198 | { 199 | CGFloat width = (CGFloat) (arc4random() % (int) self.view.bounds.size.width * 0.7); 200 | CGFloat heigth = (CGFloat) (arc4random() % (int) self.view.bounds.size.height * 0.7); 201 | CGSize randomSize = CGSizeMake(width, heigth); 202 | return randomSize; 203 | } 204 | 205 | -(void)configurePullToRefresh 206 | { 207 | __weak GLDemoViewController *weakSelf = self; 208 | 209 | // setup pull-to-refresh 210 | [self.collectionView addPullToRefreshWithActionHandler:^{ 211 | // add random sizes for demo (NB: You need to add actual content size you want here or at sizeForItemAtIndexPath delegate method as per your needs) 212 | NSMutableArray* tmp = [[NSMutableArray alloc] initWithCapacity:DATA_TO_ADD]; 213 | for (int i = 0; i < DATA_TO_ADD; i++) { 214 | [tmp addObject:[NSValue valueWithCGSize:[weakSelf randomSize]]]; 215 | } 216 | 217 | 218 | int64_t delayInSeconds = 2.0; 219 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 220 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 221 | [weakSelf.dataSource removeAllObjects]; 222 | [weakSelf.dataSource addObjectsFromArray:tmp]; 223 | [weakSelf.collectionView reloadData]; 224 | [weakSelf.collectionView.pullToRefreshView stopAnimating]; 225 | }); 226 | }]; 227 | 228 | // setup infinite scrolling 229 | [self.collectionView addInfiniteScrollingWithActionHandler:^{ 230 | // add random sizes for demo (NB: You need to add actual content size you want here or at sizeForItemAtIndexPath delegate method as per your needs) 231 | NSUInteger dataSourceCount = weakSelf.dataSource.count; 232 | NSMutableArray* tmp = [[NSMutableArray alloc] initWithCapacity:DATA_TO_ADD]; 233 | NSMutableArray* indexPaths = [[NSMutableArray alloc] initWithCapacity:DATA_TO_ADD]; 234 | 235 | for (int i = 0; i < DATA_TO_ADD; i++) { 236 | [tmp addObject:[NSValue valueWithCGSize:[weakSelf randomSize]]]; 237 | [indexPaths addObject:[NSIndexPath indexPathForItem:(dataSourceCount + i) inSection:0]]; 238 | } 239 | 240 | 241 | int64_t delayInSeconds = 2.0; 242 | dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); 243 | dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ 244 | [weakSelf.dataSource addObjectsFromArray:tmp]; 245 | [weakSelf.collectionView performBatchUpdates:^{ 246 | [weakSelf.collectionView insertItemsAtIndexPaths:(NSArray*)indexPaths]; 247 | 248 | } completion:nil]; 249 | [weakSelf.collectionView.infiniteScrollingView stopAnimating]; 250 | }); 251 | }]; 252 | } 253 | 254 | @end 255 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLGooglePlusLikeLayout/GLFlowLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // GLFlowLayout.h 3 | // GooglePlusLikeLayout 4 | // 5 | // Created by Gautam Lodhiya on 05/05/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | static NSString * const kItemAttributesKey = @"item_attr"; 12 | static NSString * const kItemSizesKey = @"item_size"; 13 | 14 | 15 | @protocol GLFlowLayoutDelegate 16 | @optional 17 | - (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath; 18 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout*)collectionViewLayout heightForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; 19 | - (CGFloat)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewFlowLayout*)collectionViewLayout interSectionSpacingForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; 20 | @end 21 | 22 | 23 | 24 | @interface GLFlowLayout : UICollectionViewFlowLayout 25 | 26 | @property (nonatomic, copy) NSString * cellKind; 27 | @property (nonatomic, assign) BOOL hasHeaders; 28 | @property (nonatomic, assign) BOOL hasFooters; 29 | @property (nonatomic, assign) BOOL shouldPerformItemLayout; 30 | @property (nonatomic,retain) NSMutableDictionary * itemLayoutInfo; 31 | @property (nonatomic, strong) NSMutableDictionary * supplementaryLayoutInfo; 32 | 33 | 34 | -(void)resetAll; 35 | -(CGSize)sizeForItemViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath*)indexPath; 36 | -(CGSize)sizeForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath*)indexPath; 37 | -(void)prepareSupplementaryViewLayout; 38 | -(void)prepareSupplementaryHeaderLayoutAtIndexPath:(NSIndexPath *)indexPath; 39 | -(void)prepareSupplementaryFooterLayoutAtIndexPath:(NSIndexPath *)indexPath; 40 | 41 | // This method need to be subclassed for layout to work 42 | -(CGRect)frameForItemAtIndexPath:(NSIndexPath *)indexPath; 43 | 44 | // This method need to be subclassed for layout to work 45 | // This will be called only if hasHeaders or hasFooters value will be set to YES 46 | -(CGRect)frameForSupplementaryViewOfKind:(NSString *)kind 47 | atIndexPath:(NSIndexPath *)indexPath; 48 | 49 | // This method need to be subclassed for layout to work 50 | -(CGSize)collectionViewContentSize; 51 | 52 | @end 53 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLGooglePlusLikeLayout/GLFlowLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // GLFlowLayout.m 3 | // GooglePlusLikeLayout 4 | // 5 | // Created by Gautam Lodhiya on 05/05/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import "GLFlowLayout.h" 10 | 11 | @interface GLFlowLayout() 12 | @property (nonatomic, strong)NSMutableDictionary * supplementaryViewHeaderLayoutInfo; 13 | @property (nonatomic, strong)NSMutableDictionary * supplementaryViewFooterLayoutInfo; 14 | @end 15 | 16 | @implementation GLFlowLayout 17 | 18 | - (id)init 19 | { 20 | self = [super init]; 21 | if(self) { 22 | self.supplementaryViewHeaderLayoutInfo = [NSMutableDictionary dictionary]; 23 | self.supplementaryViewFooterLayoutInfo = [NSMutableDictionary dictionary]; 24 | } 25 | return self; 26 | } 27 | 28 | -(void)dealloc { 29 | [self resetAll]; 30 | self.cellKind = nil; 31 | self.supplementaryViewHeaderLayoutInfo = nil; 32 | self.supplementaryViewFooterLayoutInfo = nil; 33 | } 34 | 35 | -(void)resetAll { 36 | self.itemLayoutInfo = nil; 37 | [self.supplementaryViewHeaderLayoutInfo removeAllObjects]; 38 | [self.supplementaryViewFooterLayoutInfo removeAllObjects]; 39 | self.supplementaryLayoutInfo = nil; 40 | } 41 | 42 | -(NSMutableDictionary *)itemLayoutInfo { 43 | if (!_itemLayoutInfo) 44 | _itemLayoutInfo = [[NSMutableDictionary alloc] initWithCapacity:3]; 45 | return _itemLayoutInfo; 46 | } 47 | 48 | -(NSString *)cellKind { 49 | if (!_cellKind) 50 | _cellKind = [@"DefaultCellKind" copy]; 51 | return _cellKind; 52 | } 53 | 54 | -(NSMutableDictionary *)supplementaryLayoutInfo { 55 | if (!_supplementaryLayoutInfo) { 56 | _supplementaryLayoutInfo = [[NSMutableDictionary alloc] init]; 57 | } 58 | return _supplementaryLayoutInfo; 59 | } 60 | 61 | 62 | 63 | -(void)prepareItemLayout { 64 | id delegate = (id)self.collectionView.delegate; 65 | 66 | NSMutableDictionary * layoutInfo = [NSMutableDictionary dictionary]; 67 | NSMutableDictionary * itemLayoutInfo = [NSMutableDictionary dictionary]; 68 | layoutInfo[self.cellKind] = itemLayoutInfo; 69 | [self.itemLayoutInfo addEntriesFromDictionary:layoutInfo]; 70 | 71 | NSInteger sectionCount = [self.collectionView numberOfSections]; 72 | 73 | for (int section = 0; section < sectionCount;section ++) { 74 | int itemCount = [self.collectionView numberOfItemsInSection:section]; 75 | 76 | for (int item = 0; item < itemCount; item++) { 77 | NSIndexPath * indexPath = [NSIndexPath indexPathForItem:item inSection:section]; 78 | NSMutableDictionary *valueInfo = [[NSMutableDictionary alloc] init]; 79 | itemLayoutInfo[indexPath] = valueInfo; 80 | 81 | 82 | // set default item size, then optionally override it 83 | CGSize size = self.itemSize; 84 | if(delegate && [delegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) 85 | { 86 | size = [delegate collectionView:(UICollectionView*)self.collectionView layout:self sizeForItemAtIndexPath:indexPath]; 87 | } 88 | [valueInfo setObject:[NSValue valueWithCGSize:size] forKey:kItemSizesKey]; 89 | 90 | 91 | UICollectionViewLayoutAttributes * itemAttributes = 92 | [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; 93 | itemAttributes.frame = [self frameForItemAtIndexPath:indexPath]; 94 | [valueInfo setObject:itemAttributes forKey:kItemAttributesKey]; 95 | } 96 | } 97 | } 98 | 99 | - (void)prepareSupplementaryHeaderLayoutAtIndexPath:(NSIndexPath *)indexPath 100 | { 101 | id delegate = (id)self.collectionView.delegate; 102 | NSMutableDictionary *valueInfo = [[NSMutableDictionary alloc] init]; 103 | self.supplementaryViewHeaderLayoutInfo[indexPath] = valueInfo; 104 | 105 | [self.supplementaryLayoutInfo removeAllObjects]; 106 | [self.supplementaryLayoutInfo setObject:[NSDictionary dictionaryWithDictionary:self.supplementaryViewHeaderLayoutInfo] 107 | forKey:UICollectionElementKindSectionHeader]; 108 | 109 | 110 | // set default item size, then optionally override it 111 | CGSize size = CGSizeMake(CGRectGetWidth(self.collectionView.bounds), self.headerReferenceSize.height); 112 | if(delegate && [delegate respondsToSelector:@selector(collectionView:layout:heightForSupplementaryViewOfKind:atIndexPath:)]) 113 | { 114 | CGFloat height = [delegate collectionView:(UICollectionView*)self.collectionView layout:self heightForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath]; 115 | size.height = height; 116 | } 117 | [valueInfo setObject:[NSValue valueWithCGSize:size] forKey:kItemSizesKey]; 118 | 119 | 120 | UICollectionViewLayoutAttributes * supplementaryAttributes = 121 | [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader 122 | withIndexPath:indexPath]; 123 | supplementaryAttributes.frame = [self frameForSupplementaryViewOfKind:UICollectionElementKindSectionHeader 124 | atIndexPath:indexPath]; 125 | [valueInfo setObject:supplementaryAttributes forKey:kItemAttributesKey]; 126 | } 127 | 128 | - (void)prepareSupplementaryFooterLayoutAtIndexPath:(NSIndexPath *)indexPath 129 | { 130 | id delegate = (id)self.collectionView.delegate; 131 | NSMutableDictionary *valueInfo = [[NSMutableDictionary alloc] init]; 132 | self.supplementaryViewFooterLayoutInfo[indexPath] = valueInfo; 133 | 134 | [self.supplementaryLayoutInfo removeAllObjects]; 135 | [self.supplementaryLayoutInfo setObject:[NSDictionary dictionaryWithDictionary:self.supplementaryViewFooterLayoutInfo] 136 | forKey:UICollectionElementKindSectionFooter]; 137 | 138 | // set default item size, then optionally override it 139 | CGSize size = CGSizeMake(CGRectGetWidth(self.collectionView.bounds), self.headerReferenceSize.height); 140 | if(delegate && [delegate respondsToSelector:@selector(collectionView:layout:heightForSupplementaryViewOfKind:atIndexPath:)]) 141 | { 142 | CGFloat height = [delegate collectionView:(UICollectionView*)self.collectionView layout:self heightForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:indexPath]; 143 | size.height = height; 144 | } 145 | [valueInfo setObject:[NSValue valueWithCGSize:size] forKey:kItemSizesKey]; 146 | 147 | 148 | UICollectionViewLayoutAttributes * supplementaryAttributes = 149 | [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter 150 | withIndexPath:indexPath]; 151 | supplementaryAttributes.frame = [self frameForSupplementaryViewOfKind:UICollectionElementKindSectionFooter 152 | atIndexPath:indexPath]; 153 | [valueInfo setObject:supplementaryAttributes forKey:kItemAttributesKey]; 154 | } 155 | 156 | 157 | - (void)prepareSupplementaryViewLayout { 158 | int sectionCount = [self.collectionView numberOfSections]; 159 | 160 | for (int section = 0; section < sectionCount; section ++) { 161 | NSIndexPath * indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; 162 | 163 | if (self.hasHeaders) { 164 | [self prepareSupplementaryHeaderLayoutAtIndexPath:indexPath]; 165 | } 166 | 167 | if (self.hasFooters) { 168 | [self prepareSupplementaryFooterLayoutAtIndexPath:indexPath]; 169 | } 170 | } 171 | } 172 | 173 | 174 | -(void)prepareLayout { 175 | if (self.shouldPerformItemLayout) { 176 | [self prepareItemLayout]; 177 | } 178 | 179 | if (self.hasHeaders || self.hasFooters) { 180 | [self prepareSupplementaryViewLayout]; 181 | } 182 | } 183 | 184 | -(CGSize)sizeForItemViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath*)indexPath; 185 | { 186 | NSDictionary *valueInfo = self.itemLayoutInfo[kind][indexPath]; //self.cellKind 187 | return [valueInfo[kItemSizesKey] CGSizeValue]; 188 | } 189 | 190 | -(CGSize)sizeForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath*)indexPath 191 | { 192 | NSDictionary *valueInfo = self.supplementaryLayoutInfo[kind][indexPath]; 193 | return [valueInfo[kItemSizesKey] CGSizeValue]; 194 | } 195 | 196 | -(CGRect)frameForItemAtIndexPath:(NSIndexPath *)indexPath { 197 | // This method is meant to be subclassed 198 | return CGRectZero; 199 | } 200 | 201 | -(CGRect)frameForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 202 | // This method is meant to be subclassed 203 | return CGRectZero; 204 | } 205 | 206 | -(CGSize)collectionViewContentSize { 207 | // this method is meant to be subclassed 208 | return CGSizeZero; 209 | } 210 | 211 | 212 | -(NSArray *)layoutAttributesForLayoutDictionary:(NSDictionary *)dictionary andRect:(CGRect)rect { 213 | 214 | NSMutableArray *allAttributes = [NSMutableArray array]; 215 | 216 | [dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *elementIdentifier, 217 | NSDictionary *elementsInfo, 218 | BOOL *stop) 219 | { 220 | [elementsInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, 221 | NSDictionary *valueInfo, 222 | BOOL *innerStop) 223 | { 224 | UICollectionViewLayoutAttributes *attributes = valueInfo[kItemAttributesKey]; 225 | if (CGRectIntersectsRect(rect, attributes.frame)) { 226 | [allAttributes addObject:attributes]; 227 | } 228 | }]; 229 | }]; 230 | 231 | return allAttributes; 232 | } 233 | 234 | 235 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 236 | { 237 | NSMutableArray * layoutAttributes = [[NSMutableArray alloc] init]; 238 | if (self.shouldPerformItemLayout) { 239 | [layoutAttributes addObjectsFromArray:[self layoutAttributesForLayoutDictionary:self.itemLayoutInfo 240 | andRect:rect]]; 241 | } 242 | 243 | if (self.hasHeaders || self.hasFooters) 244 | { 245 | [layoutAttributes addObjectsFromArray:[self layoutAttributesForLayoutDictionary:self.supplementaryLayoutInfo 246 | andRect:rect]]; 247 | } 248 | return layoutAttributes; 249 | } 250 | 251 | -(UICollectionViewLayoutAttributes *) layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 252 | { 253 | NSDictionary *valueInfo = self.itemLayoutInfo[self.cellKind][indexPath]; 254 | return valueInfo[kItemAttributesKey]; 255 | } 256 | 257 | -(UICollectionViewLayoutAttributes *) layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath 258 | { 259 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) 260 | { 261 | NSDictionary *valueInfo = self.supplementaryLayoutInfo[UICollectionElementKindSectionHeader][indexPath]; 262 | return valueInfo[kItemAttributesKey]; 263 | } 264 | if ([kind isEqualToString:UICollectionElementKindSectionFooter]) 265 | { 266 | NSDictionary *valueInfo = self.supplementaryLayoutInfo[UICollectionElementKindSectionFooter][indexPath]; 267 | return valueInfo[kItemAttributesKey]; 268 | } 269 | 270 | return nil; 271 | } 272 | 273 | @end 274 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLGooglePlusLikeLayout/GLGooglePlusLikeLayout.h: -------------------------------------------------------------------------------- 1 | // 2 | // GLGooglePlusLikeLayout.h 3 | // GooglePlusLikeLayout 4 | // 5 | // Created by Gautam Lodhiya on 05/05/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "GLFlowLayout.h" 11 | 12 | typedef NS_ENUM(NSInteger, CellStyle) { 13 | CellStyleSmall, // currently only supported in LayoutStyleCompact 14 | CellStyleNormal, 15 | CellStyleLargeVerticalMini, // currently only supported in LayoutStyleCompact 16 | CellStyleLargeVertical, 17 | CellStyleLargeHorizontal, 18 | CellStyleLargeVerticalAndHorizontal, 19 | }; 20 | 21 | typedef NS_ENUM(NSInteger, LayoutStyle) { 22 | LayoutStyleExpanded, 23 | LayoutStyleCompact, 24 | }; 25 | 26 | static NSString * const ContentCellKind = @"content_cell"; 27 | 28 | 29 | @interface GLGooglePlusLikeLayout : GLFlowLayout 30 | 31 | @property (nonatomic) UIEdgeInsets edgeInsets; 32 | @property (nonatomic) CGFloat interitemSpacing; 33 | @property (nonatomic) CGFloat interSectionSpacing; 34 | @property (nonatomic) CGSize minimumItemSize; 35 | @property (nonatomic) LayoutStyle layoutStyle; 36 | 37 | - (CellStyle)cellStyleForIndexPath:(NSIndexPath*)indePath; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLGooglePlusLikeLayout/GLGooglePlusLikeLayout.m: -------------------------------------------------------------------------------- 1 | // 2 | // GLGooglePlusLikeLayout.m 3 | // GooglePlusLikeLayout 4 | // 5 | // Created by Gautam Lodhiya on 05/05/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import "GLGooglePlusLikeLayout.h" 10 | 11 | static NSString * const CellSizeKey = @"cell_size"; 12 | static NSString * const CellStyleKey = @"cell_type"; 13 | static NSString * const CellRowIndexKey = @"cell_row_index"; 14 | static NSString * const CellColumnIndexKey = @"cell_column_index"; 15 | static NSString * const CellRowItemIndexKey = @"cell_row_item_index"; 16 | static NSString * const ChangeRowKey = @"change_row"; 17 | 18 | 19 | @interface GLGooglePlusLikeLayout() { 20 | } 21 | 22 | @property (nonatomic) NSUInteger numberOfColumns; // currently only 2 columns are supported (fixed 2 cols) 23 | 24 | @property(nonatomic, strong) NSMutableArray *columnHeights; // height for each column 25 | @property(nonatomic, strong) NSDictionary *layoutInfo; 26 | @property(nonatomic, strong) NSMutableDictionary *cellAttributes; 27 | @property(nonatomic, assign) NSInteger currentRow; 28 | @property(nonatomic, assign) NSInteger rowItem; 29 | 30 | @property(nonatomic, assign) CGFloat maxRowHeight; 31 | @property(nonatomic, strong) NSMutableArray *sizeSetForCellStyles; 32 | 33 | @end 34 | 35 | 36 | @implementation GLGooglePlusLikeLayout 37 | 38 | #pragma mark - Properties (Getters and Setters) 39 | 40 | - (void)setEdgeInsets:(UIEdgeInsets)edgeInsets 41 | { 42 | if (UIEdgeInsetsEqualToEdgeInsets(_edgeInsets, edgeInsets)) return; 43 | _edgeInsets = edgeInsets; 44 | 45 | [self invalidateLayout]; 46 | } 47 | 48 | - (void)setInteritemSpacing:(CGFloat)interitemSpacing 49 | { 50 | if (_interitemSpacing == interitemSpacing) return; 51 | _interitemSpacing = interitemSpacing; 52 | 53 | [self invalidateLayout]; 54 | } 55 | 56 | - (void)setInterSectionSpacing:(CGFloat)interSectionSpacing 57 | { 58 | if (_interSectionSpacing == interSectionSpacing) return; 59 | _interSectionSpacing = interSectionSpacing; 60 | 61 | [self invalidateLayout]; 62 | } 63 | 64 | -(void)setNumberOfColumns:(NSUInteger)numberOfColumns 65 | { 66 | if (_numberOfColumns == numberOfColumns) return; 67 | _numberOfColumns = numberOfColumns; 68 | 69 | [self invalidateLayout]; 70 | } 71 | 72 | - (void)setMinimumItemSize:(CGSize)minimumItemSize 73 | { 74 | if (CGSizeEqualToSize(_minimumItemSize, minimumItemSize)) return; 75 | minimumItemSize.width = floorf(minimumItemSize.width - ((self.edgeInsets.left + self.interitemSpacing + self.edgeInsets.right) / 2)); 76 | minimumItemSize.height = floorf(minimumItemSize.height - ((self.edgeInsets.top + self.interitemSpacing + self.edgeInsets.bottom) / 2)); 77 | _minimumItemSize = minimumItemSize; 78 | 79 | self.maxRowHeight = (2 * _minimumItemSize.height); 80 | 81 | 82 | self.sizeSetForCellStyles = [[NSMutableArray alloc] initWithCapacity:6]; 83 | [self.sizeSetForCellStyles addObject:[NSValue valueWithCGSize:CGSizeMake(minimumItemSize.width, minimumItemSize.height / 2)]]; 84 | [self.sizeSetForCellStyles addObject:[NSValue valueWithCGSize:minimumItemSize]]; 85 | [self.sizeSetForCellStyles addObject:[NSValue valueWithCGSize:CGSizeMake(minimumItemSize.width, minimumItemSize.height + (minimumItemSize.height / 2))]]; 86 | [self.sizeSetForCellStyles addObject:[NSValue valueWithCGSize:CGSizeMake(minimumItemSize.width, 2 * minimumItemSize.height)]]; 87 | 88 | 89 | [self invalidateLayout]; 90 | } 91 | 92 | 93 | 94 | #pragma mark - Lifecycle 95 | 96 | - (id)init 97 | { 98 | self = [super init]; 99 | if(self) 100 | [self setup]; 101 | 102 | return self; 103 | } 104 | 105 | - (void)awakeFromNib 106 | { 107 | [self setup]; 108 | } 109 | 110 | - (void)dealloc 111 | { 112 | [self.columnHeights removeAllObjects]; 113 | self.columnHeights = nil; 114 | self.layoutInfo = nil; 115 | [self.cellAttributes removeAllObjects]; 116 | self.cellAttributes = nil; 117 | } 118 | 119 | 120 | 121 | #pragma mark - Layout 122 | - (void)prepareLayout 123 | { 124 | [super resetAll]; 125 | [self.cellAttributes removeAllObjects]; 126 | 127 | 128 | NSMutableDictionary *newLayoutInfo = [NSMutableDictionary dictionary]; 129 | NSMutableDictionary *cellLayoutInfo = [NSMutableDictionary dictionary]; 130 | 131 | newLayoutInfo[ContentCellKind] = cellLayoutInfo; 132 | self.layoutInfo = newLayoutInfo; 133 | self.currentRow = 1; 134 | self.rowItem = 1; 135 | 136 | 137 | id delegate = (id)self.collectionView.delegate; 138 | NSInteger sectionCount = [self.collectionView numberOfSections]; 139 | NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; 140 | 141 | self.columnHeights = [NSMutableArray arrayWithCapacity:self.numberOfColumns]; 142 | for (NSInteger idx = 0; idx < self.numberOfColumns; idx++) { 143 | [self.columnHeights addObject:@(self.edgeInsets.top)]; 144 | } 145 | 146 | 147 | 148 | for (NSInteger section = 0; section < sectionCount; section++) { 149 | NSInteger itemCount = [self.collectionView numberOfItemsInSection:section]; 150 | 151 | for (NSInteger item = 0; item < itemCount; item++) { 152 | indexPath = [NSIndexPath indexPathForItem:item inSection:section]; 153 | 154 | // set header 155 | if (indexPath.item == 0) { 156 | if (self.hasHeaders) { 157 | float heightDiff = ABS(([self.columnHeights[0] floatValue]) - ([self.columnHeights[1] floatValue])); 158 | if (heightDiff > 0) { 159 | NSUInteger columnIndex = [self shortestColumnIndex]; 160 | self.columnHeights[columnIndex] = @([self.columnHeights[columnIndex] floatValue] + heightDiff); 161 | } 162 | 163 | for (NSInteger idx = 0; idx < self.numberOfColumns; idx++) { 164 | self.columnHeights[idx] = @([self.columnHeights[idx] floatValue] - self.interitemSpacing); 165 | } 166 | 167 | [super prepareSupplementaryHeaderLayoutAtIndexPath:indexPath]; 168 | } 169 | } 170 | 171 | 172 | NSMutableDictionary *cellProperties = [NSMutableDictionary new]; 173 | self.cellAttributes[indexPath] = cellProperties; 174 | 175 | 176 | 177 | // set default item size, then optionally override it 178 | CGSize intrinsicContentSize = self.minimumItemSize; 179 | if(delegate && [delegate respondsToSelector:@selector(collectionView:layout:sizeForItemAtIndexPath:)]) 180 | { 181 | intrinsicContentSize = [delegate collectionView:(UICollectionView*)self.collectionView layout:self sizeForItemAtIndexPath:indexPath]; 182 | } 183 | 184 | // Identify cell style 185 | CellStyle cellStyle = [self cellStyleForSize:intrinsicContentSize]; 186 | 187 | if (self.layoutStyle == LayoutStyleExpanded) { 188 | // Adjust layout 189 | [self adjustLayoutByExpadingForCellStyle:cellStyle atIndexPath:indexPath]; 190 | 191 | // Identify cell column index (Item will be put into shortest column) 192 | NSUInteger columnIndex = [self shortestColumnIndex]; 193 | 194 | // Prepare item 195 | [self prepareItemWithIntrinsicContentSize:intrinsicContentSize withStyle:cellStyle atColumn:columnIndex atIndexPath:indexPath]; 196 | 197 | } else if (self.layoutStyle == LayoutStyleCompact) { 198 | [self adjustLayoutByCompactingForCellStyle:cellStyle atIndexPath:indexPath withIntrinsicSize:intrinsicContentSize]; 199 | } 200 | } 201 | } 202 | 203 | 204 | // [super prepareLayout]; 205 | } 206 | 207 | - (CGSize)collectionViewContentSize 208 | { 209 | CGSize contentSize = self.collectionView.frame.size; 210 | NSUInteger columnIndex = [self longestColumnIndex]; 211 | CGFloat height = [self.columnHeights[columnIndex] floatValue]; 212 | contentSize.height = MAX(height - self.interitemSpacing + self.edgeInsets.bottom, CGRectGetHeight(self.collectionView.frame)); 213 | return contentSize; 214 | } 215 | 216 | 217 | - (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect 218 | { 219 | NSMutableArray *allAttributes = [NSMutableArray new]; 220 | 221 | [self.layoutInfo enumerateKeysAndObjectsUsingBlock:^(NSString *elementIdentifier, 222 | NSDictionary *elementsInfo, 223 | BOOL *stop) { 224 | [elementsInfo enumerateKeysAndObjectsUsingBlock:^(NSIndexPath *indexPath, 225 | UICollectionViewLayoutAttributes *attributes, 226 | BOOL *innerStop) { 227 | if (CGRectIntersectsRect(rect, attributes.frame)) { 228 | [allAttributes addObject:attributes]; 229 | } 230 | }]; 231 | }]; 232 | 233 | if (self.hasHeaders || self.hasFooters) { 234 | [allAttributes addObjectsFromArray:[super layoutAttributesForElementsInRect:rect]]; 235 | } 236 | 237 | return allAttributes; 238 | } 239 | 240 | - (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath 241 | { 242 | return self.layoutInfo[ContentCellKind][indexPath]; 243 | } 244 | 245 | - (UICollectionViewLayoutAttributes *)layoutAttributesForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath 246 | { 247 | return (UICollectionViewLayoutAttributes *)[super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; 248 | } 249 | 250 | - (CGRect)frameForSupplementaryViewOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath { 251 | id delegate = (id)self.collectionView.delegate; 252 | CGRect frame = CGRectZero; 253 | 254 | 255 | CGFloat interSectionSpacing = self.interSectionSpacing; 256 | if(delegate && [delegate respondsToSelector:@selector(collectionView:layout:interSectionSpacingForSupplementaryViewOfKind:atIndexPath:)]) 257 | { 258 | interSectionSpacing = [delegate collectionView:(UICollectionView*)self.collectionView layout:self interSectionSpacingForSupplementaryViewOfKind:kind atIndexPath:indexPath]; 259 | } 260 | CGFloat yOffset = floor([(self.columnHeights[0]) floatValue]); 261 | yOffset += interSectionSpacing; 262 | 263 | 264 | CGSize suppSize = CGSizeZero; 265 | if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { 266 | suppSize = [super sizeForSupplementaryViewOfKind:UICollectionElementKindSectionHeader atIndexPath:indexPath]; 267 | } else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { 268 | suppSize = [super sizeForSupplementaryViewOfKind:UICollectionElementKindSectionFooter atIndexPath:indexPath]; 269 | } 270 | 271 | 272 | frame = CGRectMake(0, yOffset, suppSize.width, suppSize.height); 273 | for (NSInteger idx = 0; idx < self.numberOfColumns; idx++) { 274 | self.columnHeights[idx] = @([self.columnHeights[idx] floatValue] + suppSize.height + (2 * interSectionSpacing)); 275 | } 276 | 277 | 278 | return frame; 279 | } 280 | 281 | - (void)finalizeCollectionViewUpdates 282 | { 283 | [super finalizeCollectionViewUpdates]; 284 | } 285 | 286 | 287 | - (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds 288 | { 289 | return NO; 290 | } 291 | 292 | 293 | 294 | #pragma mark - Private Methods 295 | 296 | - (void)adjustLayoutByExpadingForCellStyle:(CellStyle)cellStyle atIndexPath:(NSIndexPath *)indexPath 297 | { 298 | if (self.cellAttributes && self.cellAttributes.count > 0) { 299 | if (indexPath.item > 0) { 300 | NSIndexPath *prevIndexPath = [NSIndexPath indexPathForItem:0 inSection:indexPath.section]; 301 | prevIndexPath = [NSIndexPath indexPathForItem:(indexPath.item - 1) inSection:indexPath.section]; 302 | NSDictionary *itemProperties = self.cellAttributes[prevIndexPath]; 303 | BOOL isNewLayout = [itemProperties[ChangeRowKey] boolValue]; 304 | //NSInteger prevCellRow = [itemProperties[CellRowIndexKey] integerValue]; 305 | 306 | 307 | if (!isNewLayout) { 308 | NSInteger prevCellColumn = [itemProperties[CellColumnIndexKey] integerValue]; 309 | CellStyle prevCellStyle = [itemProperties[CellStyleKey] integerValue]; 310 | 311 | NSMutableDictionary *cellLayoutInfo = self.layoutInfo[ContentCellKind]; 312 | UICollectionViewLayoutAttributes* prevCellLayoutAttributes = cellLayoutInfo[prevIndexPath]; 313 | 314 | if (prevCellColumn == 0) { 315 | if (prevCellStyle == CellStyleNormal || prevCellStyle == CellStyleLargeVertical) { 316 | if (cellStyle == CellStyleLargeHorizontal || cellStyle == CellStyleLargeVerticalAndHorizontal) { 317 | CGFloat height = [self.columnHeights[prevCellColumn] floatValue]; 318 | self.columnHeights[prevCellColumn] = @(height - (CGRectGetHeight(prevCellLayoutAttributes.frame) + self.interitemSpacing)); 319 | 320 | CGSize intrinsicContentSize = CGSizeMake(floorf(CGRectGetWidth(prevCellLayoutAttributes.frame) + CGRectGetWidth(prevCellLayoutAttributes.frame)), floorf(CGRectGetHeight(prevCellLayoutAttributes.frame))); 321 | [self prepareItemWithIntrinsicContentSize:intrinsicContentSize withStyle:prevCellStyle atColumn:prevCellColumn atIndexPath:prevIndexPath]; 322 | } 323 | } 324 | 325 | } else if (prevCellColumn == 1) { 326 | if (prevCellStyle == CellStyleNormal) { 327 | if (indexPath.item > 1) { 328 | NSIndexPath *prevIndexPath2 = [NSIndexPath indexPathForItem:0 inSection:indexPath.section]; 329 | prevIndexPath2 = [NSIndexPath indexPathForItem:(indexPath.item - 2) inSection:indexPath.section]; 330 | NSDictionary *itemProperties2 = self.cellAttributes[prevIndexPath2]; 331 | BOOL isNewLayout2 = [itemProperties2[ChangeRowKey] boolValue]; 332 | 333 | if (!isNewLayout2) { 334 | CellStyle prevCellStyle2 = [itemProperties2[CellStyleKey] integerValue]; 335 | if (prevCellStyle2 == CellStyleLargeVertical) { 336 | CGFloat height = [self.columnHeights[prevCellColumn] floatValue]; 337 | self.columnHeights[prevCellColumn] = @(height - (CGRectGetHeight(prevCellLayoutAttributes.frame) + self.interitemSpacing)); 338 | 339 | CGSize intrinsicContentSize = CGSizeMake(floorf(CGRectGetWidth(prevCellLayoutAttributes.frame)), floorf(CGRectGetHeight(prevCellLayoutAttributes.frame) + CGRectGetHeight(prevCellLayoutAttributes.frame))); 340 | [self prepareItemWithIntrinsicContentSize:intrinsicContentSize withStyle:prevCellStyle atColumn:prevCellColumn atIndexPath:prevIndexPath]; 341 | } 342 | } 343 | } 344 | 345 | } else if (prevCellStyle == CellStyleLargeVertical) { 346 | if (indexPath.item > 1) { 347 | NSIndexPath *prevIndexPath2 = [NSIndexPath indexPathForItem:0 inSection:indexPath.section]; 348 | prevIndexPath2 = [NSIndexPath indexPathForItem:(indexPath.item - 2) inSection:indexPath.section]; 349 | NSDictionary *itemProperties2 = self.cellAttributes[prevIndexPath2]; 350 | BOOL isNewLayout2 = [itemProperties2[ChangeRowKey] boolValue]; 351 | 352 | if (!isNewLayout2) { 353 | NSInteger prevCellColumn2 = [itemProperties2[CellColumnIndexKey] integerValue]; 354 | CellStyle prevCellStyle2 = [itemProperties2[CellStyleKey] integerValue]; 355 | UICollectionViewLayoutAttributes* prevCellLayoutAttributes2 = cellLayoutInfo[prevIndexPath2]; 356 | 357 | if (prevCellStyle2 == CellStyleNormal) { 358 | CGFloat height = [self.columnHeights[prevCellColumn2] floatValue]; 359 | self.columnHeights[prevCellColumn2] = @(height - (CGRectGetHeight(prevCellLayoutAttributes2.frame) + self.interitemSpacing)); 360 | 361 | CGSize intrinsicContentSize = CGSizeMake(floorf(CGRectGetWidth(prevCellLayoutAttributes2.frame)), floorf(CGRectGetHeight(prevCellLayoutAttributes2.frame) + CGRectGetHeight(prevCellLayoutAttributes2.frame))); 362 | [self prepareItemWithIntrinsicContentSize:intrinsicContentSize withStyle:prevCellStyle2 atColumn:prevCellColumn2 atIndexPath:prevIndexPath2]; 363 | } 364 | } 365 | } 366 | } 367 | } 368 | } //end condition: isNewLayout 369 | } //end condition: indexPath.item > 0 370 | } 371 | } 372 | 373 | - (void)adjustLayoutByCompactingForCellStyle:(CellStyle)cellStyle atIndexPath:(NSIndexPath *)indexPath withIntrinsicSize:(CGSize)size 374 | { 375 | // Identify cell column index (Item will be put into shortest column) 376 | NSUInteger columnIndex = [self shortestColumnIndex]; 377 | 378 | if (self.cellAttributes && self.cellAttributes.count > 0) { 379 | BOOL execute = TRUE; 380 | NSIndexPath *prevIndexPath = [self previousIndexPathAtPath:indexPath]; 381 | 382 | do { 383 | if (cellStyle == CellStyleLargeVerticalAndHorizontal || cellStyle == CellStyleLargeHorizontal) { 384 | if (self.rowItem == 1) { 385 | size.width = (2 * self.minimumItemSize.width); 386 | } else { 387 | size.width = self.minimumItemSize.width; 388 | } 389 | } 390 | 391 | if (prevIndexPath.item == 0) { 392 | execute = FALSE; 393 | continue; 394 | } 395 | NSDictionary *prevItemProperties = self.cellAttributes[prevIndexPath]; 396 | execute = [prevItemProperties[ChangeRowKey] boolValue] == FALSE ? TRUE : FALSE; 397 | if (!execute) 398 | continue; 399 | 400 | CGFloat currentRowHeight = [self rowHeightForColumn:columnIndex atIndexPath:indexPath]; 401 | if (currentRowHeight < self.maxRowHeight) { 402 | 403 | 404 | float heightDiff = ABS(self.maxRowHeight - currentRowHeight) - (self.edgeInsets.top + self.edgeInsets.bottom); 405 | CGSize placeholderCellSize = CGSizeMake(self.minimumItemSize.width, heightDiff); 406 | CellStyle placeholderCellStyle = [self cellStyleForSize:placeholderCellSize]; 407 | 408 | if (size.height > placeholderCellSize.height) { 409 | if (placeholderCellStyle == CellStyleSmall && cellStyle != CellStyleSmall) { 410 | NSIndexPath *changedIndexPath = [self adjustPreviousCellInColumn:columnIndex atIndexPath:indexPath withHeight:heightDiff]; 411 | 412 | if (cellStyle != CellStyleSmall && cellStyle != CellStyleNormal) { 413 | NSDictionary *changedItemProperties = self.cellAttributes[changedIndexPath]; 414 | CellStyle changedCellStyle = [changedItemProperties[CellStyleKey] integerValue]; 415 | if (changedCellStyle == CellStyleNormal) { 416 | size = self.minimumItemSize; 417 | cellStyle = [self cellStyleForSize:size]; 418 | columnIndex = [self shortestColumnIndex]; 419 | [self prepareItemWithIntrinsicContentSize:size withStyle:cellStyle atColumn:columnIndex atIndexPath:indexPath]; 420 | return; 421 | } 422 | } 423 | 424 | } else { 425 | size = placeholderCellSize; 426 | cellStyle = placeholderCellStyle; 427 | } 428 | } 429 | } 430 | 431 | 432 | prevIndexPath = [self previousIndexPathAtPath:prevIndexPath]; 433 | 434 | } while (execute); 435 | } 436 | 437 | // ----set current cell only if not set in the above conditions---- 438 | columnIndex = [self shortestColumnIndex]; 439 | [self prepareItemWithIntrinsicContentSize:size withStyle:cellStyle atColumn:columnIndex atIndexPath:indexPath]; 440 | } 441 | 442 | 443 | 444 | - (void)prepareItemWithIntrinsicContentSize:(CGSize)intrinsicContentSize withStyle:(CellStyle)cellStyle atColumn:(NSInteger)columnIndex atIndexPath:(NSIndexPath *)indexPath 445 | { 446 | NSMutableDictionary *cellLayoutInfo = self.layoutInfo[ContentCellKind]; 447 | NSMutableDictionary *cellProperties = self.cellAttributes[indexPath]; 448 | 449 | 450 | // Re-Identify cell style 451 | cellStyle = [self cellStyleForSize:intrinsicContentSize]; 452 | 453 | // Re-Adjust cell size 454 | intrinsicContentSize = [self adjustItemSize:intrinsicContentSize forCellStyle:cellStyle]; 455 | 456 | 457 | // Set cell attributes 458 | cellProperties[CellSizeKey] = [NSValue valueWithCGSize:intrinsicContentSize]; 459 | cellProperties[CellStyleKey] = @(cellStyle); 460 | cellProperties[CellRowIndexKey] = @(self.currentRow); 461 | cellProperties[CellColumnIndexKey] = @(columnIndex); 462 | cellProperties[CellRowItemIndexKey] = @(self.rowItem); 463 | cellProperties[ChangeRowKey] = @(FALSE); 464 | 465 | 466 | // Set layout attributes 467 | UICollectionViewLayoutAttributes* cellLayoutAttributes = [UICollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; 468 | cellLayoutAttributes.frame = [self frameForCellAtColumn:columnIndex withIntinsicContentSize:intrinsicContentSize]; 469 | cellLayoutInfo[indexPath] = cellLayoutAttributes; 470 | 471 | 472 | self.columnHeights[columnIndex] = @(CGRectGetMaxY(cellLayoutAttributes.frame) + self.interitemSpacing); 473 | 474 | 475 | if (columnIndex == 0) { 476 | if (self.rowItem == 1) { 477 | if (cellStyle == CellStyleLargeVerticalAndHorizontal || cellStyle == CellStyleLargeHorizontal) { 478 | self.columnHeights[1] = @(CGRectGetMaxY(cellLayoutAttributes.frame) + self.interitemSpacing); 479 | } 480 | } 481 | } 482 | 483 | 484 | // Adjust height between 2 columns 485 | float diff = ABS(([self.columnHeights[0] floatValue]) - ([self.columnHeights[1] floatValue])); 486 | if (diff == 1 || diff == 2) { 487 | if (([self.columnHeights[0] floatValue]) > ([self.columnHeights[1] floatValue])) { 488 | self.columnHeights[0] = @([self.columnHeights[0] floatValue] - diff); 489 | } else if (([self.columnHeights[0] floatValue]) < ([self.columnHeights[1] floatValue])) { 490 | self.columnHeights[1] = @([self.columnHeights[1] floatValue] - diff); 491 | } 492 | } 493 | 494 | 495 | if (([self.columnHeights[0] floatValue]) == ([self.columnHeights[1] floatValue])) { 496 | NSIndexPath *nextIndexPath = [self lastCellIndexPathInRow:indexPath]; 497 | 498 | if (indexPath == nextIndexPath) { 499 | cellProperties[ChangeRowKey] = @(TRUE); 500 | 501 | // set rowID 502 | if ([cellProperties[ChangeRowKey] boolValue]) { 503 | self.currentRow++; 504 | self.rowItem = 0; 505 | } 506 | self.rowItem++; 507 | 508 | } else { 509 | NSMutableDictionary *nextItemProperties = self.cellAttributes[nextIndexPath]; 510 | 511 | if (nextItemProperties && nextItemProperties.allKeys.count > 0) { 512 | cellProperties[ChangeRowKey] = @(FALSE); 513 | nextItemProperties[ChangeRowKey] = @(TRUE); 514 | 515 | // set rowID 516 | if ([nextItemProperties[ChangeRowKey] boolValue]) { 517 | self.currentRow++; 518 | self.rowItem = 0; 519 | } 520 | self.rowItem++; 521 | 522 | } else { 523 | cellProperties[ChangeRowKey] = @(TRUE); 524 | 525 | // set rowID 526 | if ([cellProperties[ChangeRowKey] boolValue]) { 527 | self.currentRow++; 528 | self.rowItem = 0; 529 | } 530 | self.rowItem++; 531 | } 532 | } 533 | } else { 534 | // set rowID 535 | if ([cellProperties[ChangeRowKey] boolValue]) { 536 | self.currentRow++; 537 | self.rowItem = 0; 538 | } 539 | self.rowItem++; 540 | } 541 | } 542 | 543 | 544 | 545 | 546 | 547 | 548 | // Identify cell style 549 | - (CellStyle)cellStyleForSize:(CGSize)cellSize 550 | { 551 | CGSize constraintSize = CGSizeMake(self.minimumItemSize.width + self.edgeInsets.left + self.edgeInsets.right, self.minimumItemSize.height + self.edgeInsets.top + self.edgeInsets.bottom); 552 | 553 | if (cellSize.width <= constraintSize.width) { 554 | cellSize = [self closestSize:cellSize inSet:self.sizeSetForCellStyles]; 555 | 556 | if (cellSize.height <= constraintSize.height) { 557 | if (self.layoutStyle == LayoutStyleCompact) { 558 | if (cellSize.height <= (constraintSize.height / 2)) { 559 | return CellStyleSmall; 560 | } 561 | } 562 | return CellStyleNormal; 563 | } else { 564 | if (cellSize.height <= (2 * self.minimumItemSize.height) - (self.minimumItemSize.height / 2)) { 565 | return CellStyleLargeVerticalMini; 566 | } 567 | return CellStyleLargeVertical; 568 | } 569 | } else if (cellSize.height <= constraintSize.height) { 570 | return CellStyleLargeHorizontal; 571 | } 572 | 573 | return CellStyleLargeVerticalAndHorizontal; 574 | } 575 | 576 | // Adjust cell size 577 | - (CGSize)adjustItemSize:(CGSize)itemSize forCellStyle:(CellStyle)cellStyle 578 | { 579 | CGSize newSize = CGSizeMake(floorf(itemSize.width), floorf(itemSize.height)); 580 | 581 | if (cellStyle == CellStyleSmall) { 582 | newSize.width = floorf(self.minimumItemSize.width); 583 | newSize.height = floorf((self.minimumItemSize.height / 2) - (self.interitemSpacing / 2)); 584 | 585 | } else if (cellStyle == CellStyleNormal) { 586 | newSize = CGSizeMake(floorf(self.minimumItemSize.width), floorf(self.minimumItemSize.height)); 587 | 588 | } else if (cellStyle == CellStyleLargeVerticalMini) { 589 | newSize.width = floorf(self.minimumItemSize.width); 590 | newSize.height = floorf((2 * self.minimumItemSize.height) - (self.minimumItemSize.height / 2) + (self.interitemSpacing / 2)); 591 | 592 | } else if (cellStyle == CellStyleLargeVertical) { 593 | newSize.width = floorf(self.minimumItemSize.width); 594 | newSize.height = floorf((2 * self.minimumItemSize.height) + self.interitemSpacing); 595 | 596 | } else if (cellStyle == CellStyleLargeHorizontal) { 597 | newSize.width = floorf((2 * self.minimumItemSize.width) + self.interitemSpacing); 598 | newSize.height = floorf(MAX(self.minimumItemSize.height, newSize.height)); 599 | 600 | } else if (cellStyle == CellStyleLargeVerticalAndHorizontal) { 601 | newSize.width = floorf((2 * self.minimumItemSize.width) + self.interitemSpacing); 602 | newSize.height = floorf(MAX(self.minimumItemSize.height, newSize.height)); 603 | 604 | } 605 | 606 | return newSize; 607 | } 608 | 609 | - (CGRect)frameForCellAtColumn:(NSInteger)columnIndex withIntinsicContentSize:(CGSize)intrinsicContentSize 610 | { 611 | CGFloat xOffset = self.edgeInsets.left + (intrinsicContentSize.width + self.interitemSpacing) * columnIndex; 612 | CGFloat yOffset = floor([(self.columnHeights[columnIndex]) floatValue]); 613 | return CGRectMake(xOffset, yOffset, intrinsicContentSize.width, intrinsicContentSize.height); 614 | } 615 | 616 | 617 | // Find out shortest column. 618 | - (NSUInteger)shortestColumnIndex 619 | { 620 | __block NSUInteger index = 0; 621 | __block CGFloat shortestHeight = MAXFLOAT; 622 | 623 | [self.columnHeights enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 624 | CGFloat height = [obj floatValue]; 625 | if (height < shortestHeight) { 626 | shortestHeight = height; 627 | index = idx; 628 | } 629 | }]; 630 | 631 | return index; 632 | } 633 | 634 | // Find out longest column. 635 | - (NSUInteger)longestColumnIndex 636 | { 637 | __block NSUInteger index = 0; 638 | __block CGFloat longestHeight = 0; 639 | 640 | [self.columnHeights enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 641 | CGFloat height = [obj floatValue]; 642 | if (height > longestHeight) { 643 | longestHeight = height; 644 | index = idx; 645 | } 646 | }]; 647 | 648 | return index; 649 | } 650 | 651 | 652 | 653 | 654 | - (NSIndexPath *)lastCellIndexPathInRow:(NSIndexPath *)indexPath 655 | { 656 | NSIndexPath *lastIndexPath = indexPath; 657 | 658 | BOOL execute = TRUE; 659 | NSIndexPath *nextIndexPath = indexPath; 660 | 661 | do { 662 | if ((nextIndexPath.item + 1) < self.cellAttributes.allKeys.count - 1) { 663 | nextIndexPath = [NSIndexPath indexPathForItem:(nextIndexPath.item + 1) inSection:nextIndexPath.section]; 664 | } else { 665 | execute = FALSE; 666 | } 667 | 668 | if ((nextIndexPath.item) < self.cellAttributes.allKeys.count - 1) { 669 | nextIndexPath = [NSIndexPath indexPathForItem:(nextIndexPath.item) inSection:nextIndexPath.section]; 670 | } 671 | lastIndexPath = nextIndexPath; 672 | 673 | } while (execute); 674 | 675 | 676 | return lastIndexPath; 677 | } 678 | 679 | - (void)setLastCellRow:(NSIndexPath *)indexPath 680 | { 681 | BOOL execute = TRUE; 682 | NSIndexPath *nextIndexPath = indexPath; 683 | 684 | do { 685 | if ((nextIndexPath.item + 1) < self.cellAttributes.allKeys.count - 1) { 686 | nextIndexPath = [NSIndexPath indexPathForItem:(nextIndexPath.item + 1) inSection:nextIndexPath.section]; 687 | } else { 688 | execute = FALSE; 689 | } 690 | 691 | if ((nextIndexPath.item) < self.cellAttributes.allKeys.count - 1) { 692 | nextIndexPath = [NSIndexPath indexPathForItem:(nextIndexPath.item) inSection:nextIndexPath.section]; 693 | } 694 | self.rowItem++; 695 | 696 | } while (execute); 697 | } 698 | 699 | - (NSIndexPath *)previousIndexPathAtPath:(NSIndexPath *)indexPath 700 | { 701 | NSIndexPath *prevIndexPath = [NSIndexPath indexPathForItem:0 inSection:indexPath.section]; 702 | if (indexPath.item > 0) { 703 | prevIndexPath = [NSIndexPath indexPathForItem:(indexPath.item - 1) inSection:indexPath.section]; 704 | } 705 | return prevIndexPath; 706 | } 707 | 708 | 709 | 710 | - (NSIndexPath *)adjustPreviousCellInColumn:(NSInteger)columnIndex atIndexPath:(NSIndexPath *)indexPath withHeight:(CGFloat)heightToIncrease 711 | { 712 | NSIndexPath *changedIndexPath; 713 | BOOL execute = TRUE; 714 | NSIndexPath *prevIndexPath = [self previousIndexPathAtPath:indexPath]; 715 | 716 | do { 717 | if (prevIndexPath.item == 0) { 718 | execute = FALSE; 719 | //continue; 720 | } 721 | 722 | NSDictionary *prevItemProperties = self.cellAttributes[prevIndexPath]; 723 | execute = [prevItemProperties[ChangeRowKey] boolValue] == FALSE ? TRUE : FALSE; 724 | if (!execute) 725 | continue; 726 | 727 | NSInteger prevCellColumn = [prevItemProperties[CellColumnIndexKey] integerValue]; 728 | if (prevCellColumn == columnIndex) { 729 | execute = FALSE; 730 | self.rowItem = [prevItemProperties[CellRowItemIndexKey] integerValue]; 731 | changedIndexPath = prevIndexPath; 732 | 733 | NSMutableDictionary *cellLayoutInfo = self.layoutInfo[ContentCellKind]; 734 | UICollectionViewLayoutAttributes* prevCellLayoutAttributes = cellLayoutInfo[prevIndexPath]; 735 | 736 | NSInteger prevCellColumn = [prevItemProperties[CellColumnIndexKey] integerValue]; 737 | CellStyle prevCellStyle = [prevItemProperties[CellStyleKey] integerValue]; 738 | CGFloat height = [self.columnHeights[prevCellColumn] floatValue]; 739 | self.columnHeights[prevCellColumn] = @(height - (CGRectGetHeight(prevCellLayoutAttributes.frame) + self.interitemSpacing)); 740 | 741 | CGSize placeholderCellSize = CGSizeMake(self.minimumItemSize.width, floorf(CGRectGetHeight(prevCellLayoutAttributes.frame) + heightToIncrease)); 742 | CellStyle placeholderCellStyle = [self cellStyleForSize:placeholderCellSize]; 743 | if (prevCellStyle == CellStyleLargeVertical || prevCellStyle == CellStyleLargeVerticalMini) { 744 | placeholderCellSize = CGSizeMake(self.minimumItemSize.width, floorf(self.minimumItemSize.height)); 745 | placeholderCellStyle = [self cellStyleForSize:placeholderCellSize]; 746 | } 747 | [self prepareItemWithIntrinsicContentSize:placeholderCellSize withStyle:placeholderCellStyle atColumn:prevCellColumn atIndexPath:prevIndexPath]; 748 | } 749 | 750 | prevIndexPath = [self previousIndexPathAtPath:prevIndexPath]; 751 | 752 | } while (execute); 753 | 754 | [self setLastCellRow:indexPath]; 755 | return changedIndexPath; 756 | } 757 | 758 | - (CGFloat)rowHeightForColumn:(NSInteger)columnIndex atIndexPath:(NSIndexPath *)indexPath 759 | { 760 | CGFloat rowHeight = 0; 761 | BOOL execute = TRUE; 762 | NSIndexPath *prevIndexPath = [self previousIndexPathAtPath:indexPath]; 763 | 764 | do { 765 | NSDictionary *prevItemProperties = self.cellAttributes[prevIndexPath]; 766 | execute = [prevItemProperties[ChangeRowKey] boolValue] == FALSE ? TRUE : FALSE; 767 | if (!execute) 768 | continue; 769 | 770 | if (prevIndexPath.item == 0) { 771 | execute = FALSE; 772 | } 773 | 774 | NSMutableDictionary *cellLayoutInfo = self.layoutInfo[ContentCellKind]; 775 | UICollectionViewLayoutAttributes* prevCellLayoutAttributes = cellLayoutInfo[prevIndexPath]; 776 | if ([prevItemProperties[CellColumnIndexKey] integerValue] == columnIndex) { 777 | rowHeight += prevCellLayoutAttributes.size.height; 778 | } 779 | 780 | prevIndexPath = [self previousIndexPathAtPath:prevIndexPath]; 781 | } while (execute); 782 | 783 | return rowHeight; 784 | } 785 | 786 | 787 | 788 | - (CGSize)closestSize:(CGSize)size inSet:(NSArray*)set 789 | { 790 | CGSize closest = [[set objectAtIndex:0] CGSizeValue]; 791 | CGFloat prev = ABS(closest.height - size.height); 792 | 793 | for (int i = 1; i < set.count; i++) { 794 | CGSize temp = [[set objectAtIndex:i] CGSizeValue]; 795 | CGFloat diff = ABS(temp.height - size.height); 796 | 797 | if (diff < prev) { 798 | prev = diff; 799 | closest = temp; 800 | } 801 | } 802 | 803 | return closest; 804 | } 805 | 806 | 807 | 808 | - (void)setup 809 | { 810 | // set default values for all properties 811 | self.edgeInsets = UIEdgeInsetsMake(10.0f, 10.0f, 10.0f, 10.0f); 812 | self.interitemSpacing = 15.0f; 813 | self.interSectionSpacing = 15.0f; 814 | self.numberOfColumns = 2; 815 | self.minimumItemSize = CGSizeMake(350.0f, 350.0f); 816 | self.cellAttributes = [NSMutableDictionary new]; 817 | self.layoutStyle = LayoutStyleCompact; 818 | } 819 | 820 | 821 | #pragma mark - Public Methods 822 | 823 | - (CellStyle)cellStyleForIndexPath:(NSIndexPath *)indexPath 824 | { 825 | if (self.cellAttributes && self.cellAttributes.count > 0) { 826 | NSDictionary *itemProperties = self.cellAttributes[indexPath]; 827 | CellStyle cellStyle = [itemProperties[CellStyleKey] integerValue]; 828 | return cellStyle; 829 | } 830 | 831 | return CellStyleNormal; 832 | } 833 | 834 | 835 | @end 836 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLSectionView.h: -------------------------------------------------------------------------------- 1 | // 2 | // GLSectionView.h 3 | // GooglePlusLikeLayout 4 | // 5 | // Created by Gautam Lodhiya on 05/05/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface GLSectionView : UICollectionReusableView 12 | @property (nonatomic, strong) UILabel *displayLabel; 13 | @property (nonatomic, copy) NSString *displayString; 14 | @end 15 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GLSectionView.m: -------------------------------------------------------------------------------- 1 | // 2 | // GLSectionView.m 3 | // GooglePlusLikeLayout 4 | // 5 | // Created by Gautam Lodhiya on 05/05/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import "GLSectionView.h" 10 | 11 | @interface GLSectionView () 12 | 13 | @end 14 | 15 | @implementation GLSectionView 16 | 17 | #pragma mark - Accessors 18 | - (UILabel *)displayLabel 19 | { 20 | if (!_displayLabel) { 21 | _displayLabel = [[UILabel alloc] initWithFrame:self.bounds]; 22 | _displayLabel.font = [UIFont boldSystemFontOfSize:14]; 23 | _displayLabel.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; 24 | _displayLabel.backgroundColor = [UIColor clearColor]; 25 | _displayLabel.textColor = [UIColor grayColor]; 26 | _displayLabel.textAlignment = NSTextAlignmentCenter; 27 | } 28 | return _displayLabel; 29 | } 30 | 31 | - (void)setDisplayString:(NSString *)displayString 32 | { 33 | if (![_displayString isEqualToString:displayString]) { 34 | _displayString = [displayString copy]; 35 | self.displayLabel.text = _displayString; 36 | } 37 | } 38 | 39 | #pragma mark - Life Cycle 40 | - (void)dealloc 41 | { 42 | [_displayLabel removeFromSuperview]; 43 | _displayLabel = nil; 44 | } 45 | 46 | - (id)initWithFrame:(CGRect)frame 47 | { 48 | self = [super initWithFrame:frame]; 49 | if (self) { 50 | // Initialization code 51 | [self addSubview:self.displayLabel]; 52 | self.backgroundColor = [UIColor clearColor]; 53 | } 54 | return self; 55 | } 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GooglePlusLikeLayout-Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ${PRODUCT_NAME} 9 | CFBundleExecutable 10 | ${EXECUTABLE_NAME} 11 | CFBundleIdentifier 12 | org.me.${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 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations~ipad 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationPortraitUpsideDown 35 | UIInterfaceOrientationLandscapeLeft 36 | UIInterfaceOrientationLandscapeRight 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/GooglePlusLikeLayout-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'GooglePlusLikeLayout' target in the 'GooglePlusLikeLayout' project 3 | // 4 | 5 | #import 6 | 7 | #ifndef __IPHONE_3_0 8 | #warning "This project uses features only available in iOS SDK 3.0 and later." 9 | #endif 10 | 11 | #ifdef __OBJC__ 12 | #import 13 | #import 14 | #endif 15 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/SVPullToRefresh/SVPullToRefresh.h: -------------------------------------------------------------------------------- 1 | // 2 | // SVPullToRefresh.h 3 | // SVPullToRefreshDemo 4 | // 5 | // Created by Sam Vermette on 23.04.12. 6 | // Copyright (c) 2012 samvermette.com. All rights reserved. 7 | // 8 | // https://github.com/samvermette/SVPullToRefresh 9 | // 10 | 11 | // this header file is provided for backwards compatibility and will be removed in the future 12 | // here's how you should import SVPullToRefresh now: 13 | 14 | #import "UIScrollView+SVPullToRefresh.h" 15 | #import "UIScrollView+SVInfiniteScrolling.h" 16 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/SVPullToRefresh/UIScrollView+SVInfiniteScrolling.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+SVInfiniteScrolling.h 3 | // 4 | // Created by Sam Vermette on 23.04.12. 5 | // Copyright (c) 2012 samvermette.com. All rights reserved. 6 | // 7 | // https://github.com/samvermette/SVPullToRefresh 8 | // 9 | 10 | #import 11 | 12 | @class SVInfiniteScrollingView; 13 | 14 | @interface UIScrollView (SVInfiniteScrolling) 15 | 16 | - (void)addInfiniteScrollingWithActionHandler:(void (^)(void))actionHandler; 17 | - (void)triggerInfiniteScrolling; 18 | 19 | @property (nonatomic, strong, readonly) SVInfiniteScrollingView *infiniteScrollingView; 20 | @property (nonatomic, assign) BOOL showsInfiniteScrolling; 21 | 22 | @end 23 | 24 | 25 | enum { 26 | SVInfiniteScrollingStateStopped = 0, 27 | SVInfiniteScrollingStateTriggered, 28 | SVInfiniteScrollingStateLoading, 29 | SVInfiniteScrollingStateAll = 10 30 | }; 31 | 32 | typedef NSUInteger SVInfiniteScrollingState; 33 | 34 | @interface SVInfiniteScrollingView : UIView 35 | 36 | @property (nonatomic, readwrite) UIActivityIndicatorViewStyle activityIndicatorViewStyle; 37 | @property (nonatomic, readonly) SVInfiniteScrollingState state; 38 | @property (nonatomic, readwrite) BOOL enabled; 39 | 40 | - (void)setCustomView:(UIView *)view forState:(SVInfiniteScrollingState)state; 41 | 42 | - (void)startAnimating; 43 | - (void)stopAnimating; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/SVPullToRefresh/UIScrollView+SVInfiniteScrolling.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+SVInfiniteScrolling.m 3 | // 4 | // Created by Sam Vermette on 23.04.12. 5 | // Copyright (c) 2012 samvermette.com. All rights reserved. 6 | // 7 | // https://github.com/samvermette/SVPullToRefresh 8 | // 9 | 10 | #import 11 | #import "UIScrollView+SVInfiniteScrolling.h" 12 | 13 | 14 | static CGFloat const SVInfiniteScrollingViewHeight = 60; 15 | 16 | @interface SVInfiniteScrollingDotView : UIView 17 | 18 | @property (nonatomic, strong) UIColor *arrowColor; 19 | 20 | @end 21 | 22 | 23 | 24 | @interface SVInfiniteScrollingView () 25 | 26 | @property (nonatomic, copy) void (^infiniteScrollingHandler)(void); 27 | 28 | @property (nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; 29 | @property (nonatomic, readwrite) SVInfiniteScrollingState state; 30 | @property (nonatomic, strong) NSMutableArray *viewForState; 31 | @property (nonatomic, weak) UIScrollView *scrollView; 32 | @property (nonatomic, readwrite) CGFloat originalBottomInset; 33 | @property (nonatomic, assign) BOOL wasTriggeredByUser; 34 | @property (nonatomic, assign) BOOL isObserving; 35 | 36 | - (void)resetScrollViewContentInset; 37 | - (void)setScrollViewContentInsetForInfiniteScrolling; 38 | - (void)setScrollViewContentInset:(UIEdgeInsets)insets; 39 | 40 | @end 41 | 42 | 43 | 44 | #pragma mark - UIScrollView (SVInfiniteScrollingView) 45 | #import 46 | 47 | static char UIScrollViewInfiniteScrollingView; 48 | UIEdgeInsets scrollViewOriginalContentInsets; 49 | 50 | @implementation UIScrollView (SVInfiniteScrolling) 51 | 52 | @dynamic infiniteScrollingView; 53 | 54 | - (void)addInfiniteScrollingWithActionHandler:(void (^)(void))actionHandler { 55 | 56 | if(!self.infiniteScrollingView) { 57 | SVInfiniteScrollingView *view = [[SVInfiniteScrollingView alloc] initWithFrame:CGRectMake(0, self.contentSize.height, self.bounds.size.width, SVInfiniteScrollingViewHeight)]; 58 | view.infiniteScrollingHandler = actionHandler; 59 | view.scrollView = self; 60 | [self addSubview:view]; 61 | 62 | view.originalBottomInset = self.contentInset.bottom; 63 | self.infiniteScrollingView = view; 64 | self.showsInfiniteScrolling = YES; 65 | } 66 | } 67 | 68 | - (void)triggerInfiniteScrolling { 69 | self.infiniteScrollingView.state = SVInfiniteScrollingStateTriggered; 70 | [self.infiniteScrollingView startAnimating]; 71 | } 72 | 73 | - (void)setInfiniteScrollingView:(SVInfiniteScrollingView *)infiniteScrollingView { 74 | [self willChangeValueForKey:@"UIScrollViewInfiniteScrollingView"]; 75 | objc_setAssociatedObject(self, &UIScrollViewInfiniteScrollingView, 76 | infiniteScrollingView, 77 | OBJC_ASSOCIATION_ASSIGN); 78 | [self didChangeValueForKey:@"UIScrollViewInfiniteScrollingView"]; 79 | } 80 | 81 | - (SVInfiniteScrollingView *)infiniteScrollingView { 82 | return objc_getAssociatedObject(self, &UIScrollViewInfiniteScrollingView); 83 | } 84 | 85 | - (void)setShowsInfiniteScrolling:(BOOL)showsInfiniteScrolling { 86 | self.infiniteScrollingView.hidden = !showsInfiniteScrolling; 87 | 88 | if(!showsInfiniteScrolling) { 89 | if (self.infiniteScrollingView.isObserving) { 90 | [self removeObserver:self.infiniteScrollingView forKeyPath:@"contentOffset"]; 91 | [self removeObserver:self.infiniteScrollingView forKeyPath:@"contentSize"]; 92 | [self.infiniteScrollingView resetScrollViewContentInset]; 93 | self.infiniteScrollingView.isObserving = NO; 94 | } 95 | } 96 | else { 97 | if (!self.infiniteScrollingView.isObserving) { 98 | [self addObserver:self.infiniteScrollingView forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; 99 | [self addObserver:self.infiniteScrollingView forKeyPath:@"contentSize" options:NSKeyValueObservingOptionNew context:nil]; 100 | [self.infiniteScrollingView setScrollViewContentInsetForInfiniteScrolling]; 101 | self.infiniteScrollingView.isObserving = YES; 102 | 103 | [self.infiniteScrollingView setNeedsLayout]; 104 | self.infiniteScrollingView.frame = CGRectMake(0, self.contentSize.height, self.infiniteScrollingView.bounds.size.width, SVInfiniteScrollingViewHeight); 105 | } 106 | } 107 | } 108 | 109 | - (BOOL)showsInfiniteScrolling { 110 | return !self.infiniteScrollingView.hidden; 111 | } 112 | 113 | @end 114 | 115 | 116 | #pragma mark - SVInfiniteScrollingView 117 | @implementation SVInfiniteScrollingView 118 | 119 | // public properties 120 | @synthesize infiniteScrollingHandler, activityIndicatorViewStyle; 121 | 122 | @synthesize state = _state; 123 | @synthesize scrollView = _scrollView; 124 | @synthesize activityIndicatorView = _activityIndicatorView; 125 | 126 | 127 | - (id)initWithFrame:(CGRect)frame { 128 | if(self = [super initWithFrame:frame]) { 129 | 130 | // default styling values 131 | self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 132 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth; 133 | self.state = SVInfiniteScrollingStateStopped; 134 | self.enabled = YES; 135 | 136 | self.viewForState = [NSMutableArray arrayWithObjects:@"", @"", @"", @"", nil]; 137 | } 138 | 139 | return self; 140 | } 141 | 142 | - (void)willMoveToSuperview:(UIView *)newSuperview { 143 | if (self.superview && newSuperview == nil) { 144 | UIScrollView *scrollView = (UIScrollView *)self.superview; 145 | if (scrollView.showsInfiniteScrolling) { 146 | if (self.isObserving) { 147 | [scrollView removeObserver:self forKeyPath:@"contentOffset"]; 148 | [scrollView removeObserver:self forKeyPath:@"contentSize"]; 149 | self.isObserving = NO; 150 | } 151 | } 152 | } 153 | } 154 | 155 | - (void)layoutSubviews { 156 | self.activityIndicatorView.center = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2); 157 | } 158 | 159 | #pragma mark - Scroll View 160 | 161 | - (void)resetScrollViewContentInset { 162 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 163 | currentInsets.bottom = self.originalBottomInset; 164 | [self setScrollViewContentInset:currentInsets]; 165 | } 166 | 167 | - (void)setScrollViewContentInsetForInfiniteScrolling { 168 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 169 | currentInsets.bottom = self.originalBottomInset + SVInfiniteScrollingViewHeight; 170 | [self setScrollViewContentInset:currentInsets]; 171 | } 172 | 173 | - (void)setScrollViewContentInset:(UIEdgeInsets)contentInset { 174 | [UIView animateWithDuration:0.3 175 | delay:0 176 | options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState 177 | animations:^{ 178 | self.scrollView.contentInset = contentInset; 179 | } 180 | completion:NULL]; 181 | } 182 | 183 | #pragma mark - Observing 184 | 185 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 186 | if([keyPath isEqualToString:@"contentOffset"]) 187 | [self scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]]; 188 | else if([keyPath isEqualToString:@"contentSize"]) { 189 | [self layoutSubviews]; 190 | self.frame = CGRectMake(0, self.scrollView.contentSize.height, self.bounds.size.width, SVInfiniteScrollingViewHeight); 191 | } 192 | } 193 | 194 | - (void)scrollViewDidScroll:(CGPoint)contentOffset { 195 | if(self.state != SVInfiniteScrollingStateLoading && self.enabled) { 196 | CGFloat scrollViewContentHeight = self.scrollView.contentSize.height; 197 | CGFloat scrollOffsetThreshold = scrollViewContentHeight-self.scrollView.bounds.size.height; 198 | 199 | if(!self.scrollView.isDragging && self.state == SVInfiniteScrollingStateTriggered) 200 | self.state = SVInfiniteScrollingStateLoading; 201 | else if(contentOffset.y > scrollOffsetThreshold && self.state == SVInfiniteScrollingStateStopped && self.scrollView.isDragging) 202 | self.state = SVInfiniteScrollingStateTriggered; 203 | else if(contentOffset.y < scrollOffsetThreshold && self.state != SVInfiniteScrollingStateStopped) 204 | self.state = SVInfiniteScrollingStateStopped; 205 | } 206 | } 207 | 208 | #pragma mark - Getters 209 | 210 | - (UIActivityIndicatorView *)activityIndicatorView { 211 | if(!_activityIndicatorView) { 212 | _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 213 | _activityIndicatorView.hidesWhenStopped = YES; 214 | [self addSubview:_activityIndicatorView]; 215 | } 216 | return _activityIndicatorView; 217 | } 218 | 219 | - (UIActivityIndicatorViewStyle)activityIndicatorViewStyle { 220 | return self.activityIndicatorView.activityIndicatorViewStyle; 221 | } 222 | 223 | #pragma mark - Setters 224 | 225 | - (void)setCustomView:(UIView *)view forState:(SVInfiniteScrollingState)state { 226 | id viewPlaceholder = view; 227 | 228 | if(!viewPlaceholder) 229 | viewPlaceholder = @""; 230 | 231 | if(state == SVInfiniteScrollingStateAll) 232 | [self.viewForState replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[viewPlaceholder, viewPlaceholder, viewPlaceholder]]; 233 | else 234 | [self.viewForState replaceObjectAtIndex:state withObject:viewPlaceholder]; 235 | 236 | self.state = self.state; 237 | } 238 | 239 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)viewStyle { 240 | self.activityIndicatorView.activityIndicatorViewStyle = viewStyle; 241 | } 242 | 243 | #pragma mark - 244 | 245 | - (void)triggerRefresh { 246 | self.state = SVInfiniteScrollingStateTriggered; 247 | self.state = SVInfiniteScrollingStateLoading; 248 | } 249 | 250 | - (void)startAnimating{ 251 | self.state = SVInfiniteScrollingStateLoading; 252 | } 253 | 254 | - (void)stopAnimating { 255 | self.state = SVInfiniteScrollingStateStopped; 256 | } 257 | 258 | - (void)setState:(SVInfiniteScrollingState)newState { 259 | 260 | if(_state == newState) 261 | return; 262 | 263 | SVInfiniteScrollingState previousState = _state; 264 | _state = newState; 265 | 266 | for(id otherView in self.viewForState) { 267 | if([otherView isKindOfClass:[UIView class]]) 268 | [otherView removeFromSuperview]; 269 | } 270 | 271 | id customView = [self.viewForState objectAtIndex:newState]; 272 | BOOL hasCustomView = [customView isKindOfClass:[UIView class]]; 273 | 274 | if(hasCustomView) { 275 | [self addSubview:customView]; 276 | CGRect viewBounds = [customView bounds]; 277 | CGPoint origin = CGPointMake(roundf((self.bounds.size.width-viewBounds.size.width)/2), roundf((self.bounds.size.height-viewBounds.size.height)/2)); 278 | [customView setFrame:CGRectMake(origin.x, origin.y, viewBounds.size.width, viewBounds.size.height)]; 279 | } 280 | else { 281 | CGRect viewBounds = [self.activityIndicatorView bounds]; 282 | CGPoint origin = CGPointMake(roundf((self.bounds.size.width-viewBounds.size.width)/2), roundf((self.bounds.size.height-viewBounds.size.height)/2)); 283 | [self.activityIndicatorView setFrame:CGRectMake(origin.x, origin.y, viewBounds.size.width, viewBounds.size.height)]; 284 | 285 | switch (newState) { 286 | case SVInfiniteScrollingStateStopped: 287 | [self.activityIndicatorView stopAnimating]; 288 | break; 289 | 290 | case SVInfiniteScrollingStateTriggered: 291 | break; 292 | 293 | case SVInfiniteScrollingStateLoading: 294 | [self.activityIndicatorView startAnimating]; 295 | break; 296 | } 297 | } 298 | 299 | if(previousState == SVInfiniteScrollingStateTriggered && newState == SVInfiniteScrollingStateLoading && self.infiniteScrollingHandler && self.enabled) 300 | self.infiniteScrollingHandler(); 301 | } 302 | 303 | @end 304 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/SVPullToRefresh/UIScrollView+SVPullToRefresh.h: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+SVPullToRefresh.h 3 | // 4 | // Created by Sam Vermette on 23.04.12. 5 | // Copyright (c) 2012 samvermette.com. All rights reserved. 6 | // 7 | // https://github.com/samvermette/SVPullToRefresh 8 | // 9 | 10 | #import 11 | #import 12 | 13 | 14 | @class SVPullToRefreshView; 15 | 16 | @interface UIScrollView (SVPullToRefresh) 17 | 18 | - (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler; 19 | - (void)triggerPullToRefresh; 20 | 21 | @property (nonatomic, strong, readonly) SVPullToRefreshView *pullToRefreshView; 22 | @property (nonatomic, assign) BOOL showsPullToRefresh; 23 | 24 | @end 25 | 26 | 27 | enum { 28 | SVPullToRefreshStateStopped = 0, 29 | SVPullToRefreshStateTriggered, 30 | SVPullToRefreshStateLoading, 31 | SVPullToRefreshStateAll = 10 32 | }; 33 | 34 | typedef NSUInteger SVPullToRefreshState; 35 | 36 | @interface SVPullToRefreshView : UIView 37 | 38 | @property (nonatomic, strong) UIColor *arrowColor; 39 | @property (nonatomic, strong) UIColor *textColor; 40 | @property (nonatomic, strong, readonly) UILabel *titleLabel; 41 | @property (nonatomic, strong, readonly) UILabel *subtitleLabel; 42 | @property (nonatomic, readwrite) UIActivityIndicatorViewStyle activityIndicatorViewStyle; 43 | 44 | @property (nonatomic, readonly) SVPullToRefreshState state; 45 | 46 | - (void)setTitle:(NSString *)title forState:(SVPullToRefreshState)state; 47 | - (void)setSubtitle:(NSString *)subtitle forState:(SVPullToRefreshState)state; 48 | - (void)setCustomView:(UIView *)view forState:(SVPullToRefreshState)state; 49 | 50 | - (void)startAnimating; 51 | - (void)stopAnimating; 52 | 53 | // deprecated; use setSubtitle:forState: instead 54 | @property (nonatomic, strong, readonly) UILabel *dateLabel DEPRECATED_ATTRIBUTE; 55 | @property (nonatomic, strong) NSDate *lastUpdatedDate DEPRECATED_ATTRIBUTE; 56 | @property (nonatomic, strong) NSDateFormatter *dateFormatter DEPRECATED_ATTRIBUTE; 57 | 58 | // deprecated; use [self.scrollView triggerPullToRefresh] instead 59 | - (void)triggerRefresh DEPRECATED_ATTRIBUTE; 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/SVPullToRefresh/UIScrollView+SVPullToRefresh.m: -------------------------------------------------------------------------------- 1 | // 2 | // UIScrollView+SVPullToRefresh.m 3 | // 4 | // Created by Sam Vermette on 23.04.12. 5 | // Copyright (c) 2012 samvermette.com. All rights reserved. 6 | // 7 | // https://github.com/samvermette/SVPullToRefresh 8 | // 9 | 10 | #import 11 | #import "UIScrollView+SVPullToRefresh.h" 12 | 13 | //fequalzro() from http://stackoverflow.com/a/1614761/184130 14 | #define fequalzero(a) (fabs(a) < FLT_EPSILON) 15 | 16 | static CGFloat const SVPullToRefreshViewHeight = 60; 17 | 18 | @interface SVPullToRefreshArrow : UIView 19 | 20 | @property (nonatomic, strong) UIColor *arrowColor; 21 | 22 | @end 23 | 24 | 25 | @interface SVPullToRefreshView () 26 | 27 | @property (nonatomic, copy) void (^pullToRefreshActionHandler)(void); 28 | 29 | @property (nonatomic, strong) SVPullToRefreshArrow *arrow; 30 | @property (nonatomic, strong) UIActivityIndicatorView *activityIndicatorView; 31 | @property (nonatomic, strong, readwrite) UILabel *titleLabel; 32 | @property (nonatomic, strong, readwrite) UILabel *subtitleLabel; 33 | @property (nonatomic, readwrite) SVPullToRefreshState state; 34 | 35 | @property (nonatomic, strong) NSMutableArray *titles; 36 | @property (nonatomic, strong) NSMutableArray *subtitles; 37 | @property (nonatomic, strong) NSMutableArray *viewForState; 38 | 39 | @property (nonatomic, weak) UIScrollView *scrollView; 40 | @property (nonatomic, readwrite) CGFloat originalTopInset; 41 | 42 | @property (nonatomic, assign) BOOL wasTriggeredByUser; 43 | @property (nonatomic, assign) BOOL showsPullToRefresh; 44 | @property (nonatomic, assign) BOOL showsDateLabel; 45 | @property(nonatomic, assign) BOOL isObserving; 46 | 47 | - (void)resetScrollViewContentInset; 48 | - (void)setScrollViewContentInsetForLoading; 49 | - (void)setScrollViewContentInset:(UIEdgeInsets)insets; 50 | - (void)rotateArrow:(float)degrees hide:(BOOL)hide; 51 | 52 | @end 53 | 54 | 55 | 56 | #pragma mark - UIScrollView (SVPullToRefresh) 57 | #import 58 | 59 | static char UIScrollViewPullToRefreshView; 60 | 61 | @implementation UIScrollView (SVPullToRefresh) 62 | 63 | @dynamic pullToRefreshView, showsPullToRefresh; 64 | 65 | - (void)addPullToRefreshWithActionHandler:(void (^)(void))actionHandler { 66 | 67 | if(!self.pullToRefreshView) { 68 | SVPullToRefreshView *view = [[SVPullToRefreshView alloc] initWithFrame:CGRectMake(0, -SVPullToRefreshViewHeight, self.bounds.size.width, SVPullToRefreshViewHeight)]; 69 | view.pullToRefreshActionHandler = actionHandler; 70 | view.scrollView = self; 71 | [self addSubview:view]; 72 | 73 | view.originalTopInset = self.contentInset.top; 74 | self.pullToRefreshView = view; 75 | self.showsPullToRefresh = YES; 76 | } 77 | } 78 | 79 | - (void)triggerPullToRefresh { 80 | self.pullToRefreshView.state = SVPullToRefreshStateTriggered; 81 | [self.pullToRefreshView startAnimating]; 82 | } 83 | 84 | - (void)setPullToRefreshView:(SVPullToRefreshView *)pullToRefreshView { 85 | [self willChangeValueForKey:@"SVPullToRefreshView"]; 86 | objc_setAssociatedObject(self, &UIScrollViewPullToRefreshView, 87 | pullToRefreshView, 88 | OBJC_ASSOCIATION_ASSIGN); 89 | [self didChangeValueForKey:@"SVPullToRefreshView"]; 90 | } 91 | 92 | - (SVPullToRefreshView *)pullToRefreshView { 93 | return objc_getAssociatedObject(self, &UIScrollViewPullToRefreshView); 94 | } 95 | 96 | - (void)setShowsPullToRefresh:(BOOL)showsPullToRefresh { 97 | self.pullToRefreshView.hidden = !showsPullToRefresh; 98 | 99 | if(!showsPullToRefresh) { 100 | if (self.pullToRefreshView.isObserving) { 101 | [self removeObserver:self.pullToRefreshView forKeyPath:@"contentOffset"]; 102 | [self removeObserver:self.pullToRefreshView forKeyPath:@"frame"]; 103 | [self.pullToRefreshView resetScrollViewContentInset]; 104 | self.pullToRefreshView.isObserving = NO; 105 | } 106 | } 107 | else { 108 | if (!self.pullToRefreshView.isObserving) { 109 | [self addObserver:self.pullToRefreshView forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; 110 | [self addObserver:self.pullToRefreshView forKeyPath:@"frame" options:NSKeyValueObservingOptionNew context:nil]; 111 | self.pullToRefreshView.isObserving = YES; 112 | } 113 | } 114 | } 115 | 116 | - (BOOL)showsPullToRefresh { 117 | return !self.pullToRefreshView.hidden; 118 | } 119 | 120 | @end 121 | 122 | #pragma mark - SVPullToRefresh 123 | @implementation SVPullToRefreshView 124 | 125 | // public properties 126 | @synthesize pullToRefreshActionHandler, arrowColor, textColor, activityIndicatorViewStyle, lastUpdatedDate, dateFormatter; 127 | 128 | @synthesize state = _state; 129 | @synthesize scrollView = _scrollView; 130 | @synthesize showsPullToRefresh = _showsPullToRefresh; 131 | @synthesize arrow = _arrow; 132 | @synthesize activityIndicatorView = _activityIndicatorView; 133 | 134 | @synthesize titleLabel = _titleLabel; 135 | @synthesize dateLabel = _dateLabel; 136 | 137 | 138 | - (id)initWithFrame:(CGRect)frame { 139 | if(self = [super initWithFrame:frame]) { 140 | 141 | // default styling values 142 | self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 143 | self.textColor = [UIColor darkGrayColor]; 144 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth; 145 | self.state = SVPullToRefreshStateStopped; 146 | self.showsDateLabel = NO; 147 | 148 | self.titles = [NSMutableArray arrayWithObjects:NSLocalizedString(@"Pull to refresh...",), 149 | NSLocalizedString(@"Release to refresh...",), 150 | NSLocalizedString(@"Loading...",), 151 | nil]; 152 | 153 | self.subtitles = [NSMutableArray arrayWithObjects:@"", @"", @"", @"", nil]; 154 | self.viewForState = [NSMutableArray arrayWithObjects:@"", @"", @"", @"", nil]; 155 | } 156 | 157 | return self; 158 | } 159 | 160 | - (void)willMoveToSuperview:(UIView *)newSuperview { 161 | if (self.superview && newSuperview == nil) { 162 | //use self.superview, not self.scrollView. Why self.scrollView == nil here? 163 | UIScrollView *scrollView = (UIScrollView *)self.superview; 164 | if (scrollView.showsPullToRefresh) { 165 | if (self.isObserving) { 166 | //If enter this branch, it is the moment just before "SVPullToRefreshView's dealloc", so remove observer here 167 | [scrollView removeObserver:self forKeyPath:@"contentOffset"]; 168 | [scrollView removeObserver:self forKeyPath:@"frame"]; 169 | self.isObserving = NO; 170 | } 171 | } 172 | } 173 | } 174 | 175 | - (void)layoutSubviews { 176 | 177 | for(id otherView in self.viewForState) { 178 | if([otherView isKindOfClass:[UIView class]]) 179 | [otherView removeFromSuperview]; 180 | } 181 | 182 | id customView = [self.viewForState objectAtIndex:self.state]; 183 | BOOL hasCustomView = [customView isKindOfClass:[UIView class]]; 184 | 185 | self.titleLabel.hidden = hasCustomView; 186 | self.subtitleLabel.hidden = hasCustomView; 187 | self.arrow.hidden = hasCustomView; 188 | 189 | if(hasCustomView) { 190 | [self addSubview:customView]; 191 | CGRect viewBounds = [customView bounds]; 192 | CGPoint origin = CGPointMake(roundf((self.bounds.size.width-viewBounds.size.width)/2), roundf((self.bounds.size.height-viewBounds.size.height)/2)); 193 | [customView setFrame:CGRectMake(origin.x, origin.y, viewBounds.size.width, viewBounds.size.height)]; 194 | } 195 | else { 196 | switch (self.state) { 197 | case SVPullToRefreshStateStopped: 198 | self.arrow.alpha = 1; 199 | [self.activityIndicatorView stopAnimating]; 200 | [self rotateArrow:0 hide:NO]; 201 | break; 202 | 203 | case SVPullToRefreshStateTriggered: 204 | [self rotateArrow:(float)M_PI hide:NO]; 205 | break; 206 | 207 | case SVPullToRefreshStateLoading: 208 | [self.activityIndicatorView startAnimating]; 209 | [self rotateArrow:0 hide:YES]; 210 | break; 211 | } 212 | 213 | CGFloat leftViewWidth = MAX(self.arrow.bounds.size.width,self.activityIndicatorView.bounds.size.width); 214 | 215 | CGFloat margin = 10; 216 | CGFloat marginY = 2; 217 | CGFloat labelMaxWidth = self.bounds.size.width - margin - leftViewWidth; 218 | 219 | self.titleLabel.text = [self.titles objectAtIndex:self.state]; 220 | 221 | NSString *subtitle = [self.subtitles objectAtIndex:self.state]; 222 | self.subtitleLabel.text = subtitle.length > 0 ? subtitle : nil; 223 | 224 | 225 | CGSize titleSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font 226 | constrainedToSize:CGSizeMake(labelMaxWidth,self.titleLabel.font.lineHeight) 227 | lineBreakMode:self.titleLabel.lineBreakMode]; 228 | 229 | 230 | CGSize subtitleSize = [self.subtitleLabel.text sizeWithFont:self.subtitleLabel.font 231 | constrainedToSize:CGSizeMake(labelMaxWidth,self.subtitleLabel.font.lineHeight) 232 | lineBreakMode:self.subtitleLabel.lineBreakMode]; 233 | 234 | CGFloat maxLabelWidth = MAX(titleSize.width,subtitleSize.width); 235 | CGFloat totalMaxWidth = leftViewWidth + margin + maxLabelWidth; 236 | CGFloat labelX = (self.bounds.size.width / 2) - (totalMaxWidth / 2) + leftViewWidth + margin; 237 | 238 | if(subtitleSize.height > 0){ 239 | CGFloat totalHeight = titleSize.height + subtitleSize.height + marginY; 240 | CGFloat minY = (self.bounds.size.height / 2) - (totalHeight / 2); 241 | 242 | CGFloat titleY = minY; 243 | self.titleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY, titleSize.width, titleSize.height)); 244 | self.subtitleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY + titleSize.height + marginY, subtitleSize.width, subtitleSize.height)); 245 | }else{ 246 | CGFloat totalHeight = titleSize.height; 247 | CGFloat minY = (self.bounds.size.height / 2) - (totalHeight / 2); 248 | 249 | CGFloat titleY = minY; 250 | self.titleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY, titleSize.width, titleSize.height)); 251 | self.subtitleLabel.frame = CGRectIntegral(CGRectMake(labelX, titleY + titleSize.height + marginY, subtitleSize.width, subtitleSize.height)); 252 | } 253 | 254 | CGFloat arrowX = (self.bounds.size.width / 2) - (totalMaxWidth / 2) + (leftViewWidth - self.arrow.bounds.size.width) / 2; 255 | self.arrow.frame = CGRectMake(arrowX, 256 | (self.bounds.size.height / 2) - (self.arrow.bounds.size.height / 2), 257 | self.arrow.bounds.size.width, 258 | self.arrow.bounds.size.height); 259 | self.activityIndicatorView.center = self.arrow.center; 260 | } 261 | } 262 | 263 | #pragma mark - Scroll View 264 | 265 | - (void)resetScrollViewContentInset { 266 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 267 | currentInsets.top = self.originalTopInset; 268 | [self setScrollViewContentInset:currentInsets]; 269 | } 270 | 271 | - (void)setScrollViewContentInsetForLoading { 272 | CGFloat offset = MAX(self.scrollView.contentOffset.y * -1, 0); 273 | UIEdgeInsets currentInsets = self.scrollView.contentInset; 274 | currentInsets.top = MIN(offset, self.originalTopInset + self.bounds.size.height); 275 | [self setScrollViewContentInset:currentInsets]; 276 | } 277 | 278 | - (void)setScrollViewContentInset:(UIEdgeInsets)contentInset { 279 | [UIView animateWithDuration:0.3 280 | delay:0 281 | options:UIViewAnimationOptionAllowUserInteraction|UIViewAnimationOptionBeginFromCurrentState 282 | animations:^{ 283 | self.scrollView.contentInset = contentInset; 284 | } 285 | completion:NULL]; 286 | } 287 | 288 | #pragma mark - Observing 289 | 290 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 291 | if([keyPath isEqualToString:@"contentOffset"]) 292 | [self scrollViewDidScroll:[[change valueForKey:NSKeyValueChangeNewKey] CGPointValue]]; 293 | else if([keyPath isEqualToString:@"frame"]) 294 | [self layoutSubviews]; 295 | } 296 | 297 | - (void)scrollViewDidScroll:(CGPoint)contentOffset { 298 | if(self.state != SVPullToRefreshStateLoading) { 299 | CGFloat scrollOffsetThreshold = self.frame.origin.y-self.originalTopInset; 300 | 301 | if(!self.scrollView.isDragging && self.state == SVPullToRefreshStateTriggered) 302 | self.state = SVPullToRefreshStateLoading; 303 | else if(contentOffset.y < scrollOffsetThreshold && self.scrollView.isDragging && self.state == SVPullToRefreshStateStopped) 304 | self.state = SVPullToRefreshStateTriggered; 305 | else if(contentOffset.y >= scrollOffsetThreshold && self.state != SVPullToRefreshStateStopped) 306 | self.state = SVPullToRefreshStateStopped; 307 | } else { 308 | CGFloat offset = MAX(self.scrollView.contentOffset.y * -1, 0.0f); 309 | offset = MIN(offset, self.originalTopInset + self.bounds.size.height); 310 | UIEdgeInsets contentInset = self.scrollView.contentInset; 311 | self.scrollView.contentInset = UIEdgeInsetsMake(offset, contentInset.left, contentInset.bottom, contentInset.right); 312 | } 313 | } 314 | 315 | #pragma mark - Getters 316 | 317 | - (SVPullToRefreshArrow *)arrow { 318 | if(!_arrow) { 319 | _arrow = [[SVPullToRefreshArrow alloc]initWithFrame:CGRectMake(0, self.bounds.size.height-54, 22, 48)]; 320 | _arrow.backgroundColor = [UIColor clearColor]; 321 | [self addSubview:_arrow]; 322 | } 323 | return _arrow; 324 | } 325 | 326 | - (UIActivityIndicatorView *)activityIndicatorView { 327 | if(!_activityIndicatorView) { 328 | _activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite]; 329 | _activityIndicatorView.hidesWhenStopped = YES; 330 | [self addSubview:_activityIndicatorView]; 331 | } 332 | return _activityIndicatorView; 333 | } 334 | 335 | - (UILabel *)titleLabel { 336 | if(!_titleLabel) { 337 | _titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 210, 20)]; 338 | _titleLabel.text = NSLocalizedString(@"Pull to refresh...",); 339 | _titleLabel.font = [UIFont boldSystemFontOfSize:14]; 340 | _titleLabel.backgroundColor = [UIColor clearColor]; 341 | _titleLabel.textColor = textColor; 342 | [self addSubview:_titleLabel]; 343 | } 344 | return _titleLabel; 345 | } 346 | 347 | - (UILabel *)subtitleLabel { 348 | if(!_subtitleLabel) { 349 | _subtitleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 210, 20)]; 350 | _subtitleLabel.font = [UIFont systemFontOfSize:12]; 351 | _subtitleLabel.backgroundColor = [UIColor clearColor]; 352 | _subtitleLabel.textColor = textColor; 353 | [self addSubview:_subtitleLabel]; 354 | } 355 | return _subtitleLabel; 356 | } 357 | 358 | - (UILabel *)dateLabel { 359 | return self.showsDateLabel ? self.subtitleLabel : nil; 360 | } 361 | 362 | - (NSDateFormatter *)dateFormatter { 363 | if(!dateFormatter) { 364 | dateFormatter = [[NSDateFormatter alloc] init]; 365 | [dateFormatter setDateStyle:NSDateFormatterShortStyle]; 366 | [dateFormatter setTimeStyle:NSDateFormatterShortStyle]; 367 | dateFormatter.locale = [NSLocale currentLocale]; 368 | } 369 | return dateFormatter; 370 | } 371 | 372 | - (UIColor *)arrowColor { 373 | return self.arrow.arrowColor; // pass through 374 | } 375 | 376 | - (UIColor *)textColor { 377 | return self.titleLabel.textColor; 378 | } 379 | 380 | - (UIActivityIndicatorViewStyle)activityIndicatorViewStyle { 381 | return self.activityIndicatorView.activityIndicatorViewStyle; 382 | } 383 | 384 | #pragma mark - Setters 385 | 386 | - (void)setArrowColor:(UIColor *)newArrowColor { 387 | self.arrow.arrowColor = newArrowColor; // pass through 388 | [self.arrow setNeedsDisplay]; 389 | } 390 | 391 | - (void)setTitle:(NSString *)title forState:(SVPullToRefreshState)state { 392 | if(!title) 393 | title = @""; 394 | 395 | if(state == SVPullToRefreshStateAll) 396 | [self.titles replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[title, title, title]]; 397 | else 398 | [self.titles replaceObjectAtIndex:state withObject:title]; 399 | 400 | [self setNeedsLayout]; 401 | } 402 | 403 | - (void)setSubtitle:(NSString *)subtitle forState:(SVPullToRefreshState)state { 404 | if(!subtitle) 405 | subtitle = @""; 406 | 407 | if(state == SVPullToRefreshStateAll) 408 | [self.subtitles replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[subtitle, subtitle, subtitle]]; 409 | else 410 | [self.subtitles replaceObjectAtIndex:state withObject:subtitle]; 411 | 412 | [self setNeedsLayout]; 413 | } 414 | 415 | - (void)setCustomView:(UIView *)view forState:(SVPullToRefreshState)state { 416 | id viewPlaceholder = view; 417 | 418 | if(!viewPlaceholder) 419 | viewPlaceholder = @""; 420 | 421 | if(state == SVPullToRefreshStateAll) 422 | [self.viewForState replaceObjectsInRange:NSMakeRange(0, 3) withObjectsFromArray:@[viewPlaceholder, viewPlaceholder, viewPlaceholder]]; 423 | else 424 | [self.viewForState replaceObjectAtIndex:state withObject:viewPlaceholder]; 425 | 426 | [self setNeedsLayout]; 427 | } 428 | 429 | - (void)setTextColor:(UIColor *)newTextColor { 430 | textColor = newTextColor; 431 | self.titleLabel.textColor = newTextColor; 432 | self.subtitleLabel.textColor = newTextColor; 433 | } 434 | 435 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)viewStyle { 436 | self.activityIndicatorView.activityIndicatorViewStyle = viewStyle; 437 | } 438 | 439 | - (void)setLastUpdatedDate:(NSDate *)newLastUpdatedDate { 440 | self.showsDateLabel = YES; 441 | self.dateLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Last Updated: %@",), newLastUpdatedDate?[self.dateFormatter stringFromDate:newLastUpdatedDate]:NSLocalizedString(@"Never",)]; 442 | } 443 | 444 | - (void)setDateFormatter:(NSDateFormatter *)newDateFormatter { 445 | dateFormatter = newDateFormatter; 446 | self.dateLabel.text = [NSString stringWithFormat:NSLocalizedString(@"Last Updated: %@",), self.lastUpdatedDate?[newDateFormatter stringFromDate:self.lastUpdatedDate]:NSLocalizedString(@"Never",)]; 447 | } 448 | 449 | #pragma mark - 450 | 451 | - (void)triggerRefresh { 452 | [self.scrollView triggerPullToRefresh]; 453 | } 454 | 455 | - (void)startAnimating{ 456 | if(fequalzero(self.scrollView.contentOffset.y)) { 457 | [self.scrollView setContentOffset:CGPointMake(self.scrollView.contentOffset.x, -self.frame.size.height) animated:YES]; 458 | self.wasTriggeredByUser = NO; 459 | } 460 | else 461 | self.wasTriggeredByUser = YES; 462 | 463 | self.state = SVPullToRefreshStateLoading; 464 | } 465 | 466 | - (void)stopAnimating { 467 | self.state = SVPullToRefreshStateStopped; 468 | 469 | if(!self.wasTriggeredByUser && self.scrollView.contentOffset.y < -self.originalTopInset) 470 | [self.scrollView setContentOffset:CGPointMake(self.scrollView.contentOffset.x, -self.originalTopInset) animated:YES]; 471 | } 472 | 473 | - (void)setState:(SVPullToRefreshState)newState { 474 | 475 | if(_state == newState) 476 | return; 477 | 478 | SVPullToRefreshState previousState = _state; 479 | _state = newState; 480 | 481 | [self setNeedsLayout]; 482 | 483 | switch (newState) { 484 | case SVPullToRefreshStateStopped: 485 | [self resetScrollViewContentInset]; 486 | break; 487 | 488 | case SVPullToRefreshStateTriggered: 489 | break; 490 | 491 | case SVPullToRefreshStateLoading: 492 | [self setScrollViewContentInsetForLoading]; 493 | 494 | if(previousState == SVPullToRefreshStateTriggered && pullToRefreshActionHandler) 495 | pullToRefreshActionHandler(); 496 | 497 | break; 498 | } 499 | } 500 | 501 | - (void)rotateArrow:(float)degrees hide:(BOOL)hide { 502 | [UIView animateWithDuration:0.2 delay:0 options:UIViewAnimationOptionAllowUserInteraction animations:^{ 503 | self.arrow.layer.transform = CATransform3DMakeRotation(degrees, 0, 0, 1); 504 | self.arrow.layer.opacity = !hide; 505 | //[self.arrow setNeedsDisplay];//ios 4 506 | } completion:NULL]; 507 | } 508 | 509 | @end 510 | 511 | 512 | #pragma mark - SVPullToRefreshArrow 513 | 514 | @implementation SVPullToRefreshArrow 515 | @synthesize arrowColor; 516 | 517 | - (UIColor *)arrowColor { 518 | if (arrowColor) return arrowColor; 519 | return [UIColor grayColor]; // default Color 520 | } 521 | 522 | - (void)drawRect:(CGRect)rect { 523 | CGContextRef c = UIGraphicsGetCurrentContext(); 524 | 525 | // the rects above the arrow 526 | CGContextAddRect(c, CGRectMake(5, 0, 12, 4)); // to-do: use dynamic points 527 | CGContextAddRect(c, CGRectMake(5, 6, 12, 4)); // currently fixed size: 22 x 48pt 528 | CGContextAddRect(c, CGRectMake(5, 12, 12, 4)); 529 | CGContextAddRect(c, CGRectMake(5, 18, 12, 4)); 530 | CGContextAddRect(c, CGRectMake(5, 24, 12, 4)); 531 | CGContextAddRect(c, CGRectMake(5, 30, 12, 4)); 532 | 533 | // the arrow 534 | CGContextMoveToPoint(c, 0, 34); 535 | CGContextAddLineToPoint(c, 11, 48); 536 | CGContextAddLineToPoint(c, 22, 34); 537 | CGContextAddLineToPoint(c, 0, 34); 538 | CGContextClosePath(c); 539 | 540 | CGContextSaveGState(c); 541 | CGContextClip(c); 542 | 543 | // Gradient Declaration 544 | CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); 545 | CGFloat alphaGradientLocations[] = {0, 0.8f}; 546 | 547 | CGGradientRef alphaGradient = nil; 548 | if([[[UIDevice currentDevice] systemVersion]floatValue] >= 5){ 549 | NSArray* alphaGradientColors = [NSArray arrayWithObjects: 550 | (id)[self.arrowColor colorWithAlphaComponent:0].CGColor, 551 | (id)[self.arrowColor colorWithAlphaComponent:1].CGColor, 552 | nil]; 553 | alphaGradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)alphaGradientColors, alphaGradientLocations); 554 | }else{ 555 | const CGFloat * components = CGColorGetComponents([self.arrowColor CGColor]); 556 | int numComponents = (int)CGColorGetNumberOfComponents([self.arrowColor CGColor]); 557 | CGFloat colors[8]; 558 | switch(numComponents){ 559 | case 2:{ 560 | colors[0] = colors[4] = components[0]; 561 | colors[1] = colors[5] = components[0]; 562 | colors[2] = colors[6] = components[0]; 563 | break; 564 | } 565 | case 4:{ 566 | colors[0] = colors[4] = components[0]; 567 | colors[1] = colors[5] = components[1]; 568 | colors[2] = colors[6] = components[2]; 569 | break; 570 | } 571 | } 572 | colors[3] = 0; 573 | colors[7] = 1; 574 | alphaGradient = CGGradientCreateWithColorComponents(colorSpace,colors,alphaGradientLocations,2); 575 | } 576 | 577 | 578 | CGContextDrawLinearGradient(c, alphaGradient, CGPointZero, CGPointMake(0, rect.size.height), 0); 579 | 580 | CGContextRestoreGState(c); 581 | 582 | CGGradientRelease(alphaGradient); 583 | CGColorSpaceRelease(colorSpace); 584 | } 585 | @end 586 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /GooglePlusLikeLayout/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // GooglePlusLikeLayout 4 | // 5 | // Created by Gautam Lodhiya on 05/05/13. 6 | // Copyright (c) 2013 Gautam Lodhiya. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "GLAppDelegate.h" 12 | 13 | int main(int argc, char *argv[]) 14 | { 15 | @autoreleasepool { 16 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([GLAppDelegate class])); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GLGooglePlusLikeLayout 2 | ====================== 3 | This layout is inspired by GooglePlus. 4 | 5 | GLGooglePlusLikeLayout is custom layout based on UICollectionView which can be used to show the data based on their content-size and by maintaining the row linear, unlike waterflow style layout. 6 | 7 | Note 8 | ---------- 9 | - To support older versions of iOS you can use open source 100% API compatible replacement of UICollectionView for iOS4.3+ ie. [PSTUICollectionView][1], just add PS on any UICollectionView* class 10 | - GLGooglePlusLikeLayout is just for demo purpose. 11 | 12 | Usage 13 | ---------- 14 | Refer GLDemoViewController for the demo. 15 | 16 | Limitation 17 | ---------- 18 | - No supplementary footer view and decoration view. 19 | - Variable column(s) support (Currently only 2 default columns are supported). 20 | 21 | Screen Shots 22 | ------------ 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | ### License 32 | http://gautam.mit-license.org/ 33 | 34 | 35 | 36 | [1]: https://github.com/steipete/PSTCollectionView 37 | -------------------------------------------------------------------------------- /Screen Shot 1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/Screen Shot 1.png -------------------------------------------------------------------------------- /Screen Shot 2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/Screen Shot 2.png -------------------------------------------------------------------------------- /Screen Shot 3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/Screen Shot 3.png -------------------------------------------------------------------------------- /Screen Shot 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/Screen Shot 4.png -------------------------------------------------------------------------------- /Screen Shot 5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/Screen Shot 5.png -------------------------------------------------------------------------------- /Screen Shot 6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RATTLESNAKE-VIPER/GLGooglePlusLikeLayout/f82776ebcb10fe8f286b35bbb3abdbdd8e447be7/Screen Shot 6.png --------------------------------------------------------------------------------