├── .gitignore ├── README.md ├── SelfSizingCollectionViewCells.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── SelfSizingCollectionViewCells.xccheckout └── xcuserdata │ └── danielgalasko.xcuserdatad │ └── xcschemes │ └── SelfSizingCollectionViewCells.xcscheme ├── SelfSizingCollectionViewCells ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── CollectionViewCell.h ├── CollectionViewCell.m ├── CollectionViewController.swift ├── DynamicContentCollectionViewController.h ├── DynamicContentCollectionViewController.m ├── HomeTableViewController.swift ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── RandomStringGenerator.h ├── RandomStringGenerator.m ├── SelfSizingCollectionViewCells-Bridging-Header.h ├── SimpleCell.swift └── main.m ├── SelfSizingCollectionViewCellsTests ├── Info.plist └── SelfSizingCollectionViewCellsTests.m └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | xcuserdata/ 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DGSelfSizingCollectionViewCells 2 | =============================== 3 | 4 | Simple implementation of self sizing `UICollectionViewCells` introduced in iOS 8. This is intended to assist with those coming from [this](http://stackoverflow.com/questions/25895311/uicollectionview-self-sizing-cells-with-auto-layout/25896386#25896386) Stack Overflow post. A picture says a thousand words 5 | 6 | ![screenshot](screenshot.png "Vibrant Seperators") 7 | 8 | This project includes several examples of how self-sizing cells can be achieved. To enable self sizing cells you first need to set the estimatedItemSize property on `UICollectionViewFlowLayout`. 9 | 10 | Then you need to decide on your approach for sizing your cells. You can either let auto layout do the work for you **or** you can override `preferredLayoutAttributesFittingAttributes:` and return an appropriate size. 11 | 12 | When using Auto Layout make sure that your cells are properly constrained. 13 | 14 | When using `preferredLayoutAttributesFittingAttributes:` you will probably need to cache the size you calculate to prevent the system from calling your implementation indefinitely. 15 | If you modify the frame of the layoutAttributes object thats passed in it will most likely call your implementation again so make sure you calculate once and then just return the layoutAttributes object. 16 | 17 | Be warned, if you are seeking to have your collection view look similar to `UITableView` you will need to do a lot more work. 18 | Currently the default behaviour for flow layout is to let cells expand in a horizontal direction first. 19 | This means if you want to fix the horizontal dimension you either need to force a width constraint on the cell [as seen here](http://stackoverflow.com/questions/28670951/uicollectionviewcell-systemlayoutsizefittingsize-returns-incorrect-width) or [here](http://stackoverflow.com/questions/26143591/specifying-one-dimension-of-cells-in-uicollectionview-using-auto-layout) 20 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 42EAEAD619C9EBF600C2AEEC /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 42EAEAD519C9EBF600C2AEEC /* main.m */; }; 11 | 42EAEAD919C9EBF600C2AEEC /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 42EAEAD819C9EBF600C2AEEC /* AppDelegate.m */; }; 12 | 42EAEADC19C9EBF600C2AEEC /* DynamicContentCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 42EAEADB19C9EBF600C2AEEC /* DynamicContentCollectionViewController.m */; }; 13 | 42EAEADF19C9EBF600C2AEEC /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 42EAEADD19C9EBF600C2AEEC /* Main.storyboard */; }; 14 | 42EAEAE119C9EBF600C2AEEC /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 42EAEAE019C9EBF600C2AEEC /* Images.xcassets */; }; 15 | 42EAEAE419C9EBF600C2AEEC /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 42EAEAE219C9EBF600C2AEEC /* LaunchScreen.xib */; }; 16 | 42EAEAF019C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 42EAEAEF19C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests.m */; }; 17 | 42EAEAFE19C9F96A00C2AEEC /* CollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 42EAEAFD19C9F96A00C2AEEC /* CollectionViewCell.m */; }; 18 | 46F017D21C7386500094204D /* SimpleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F017D11C7386500094204D /* SimpleCell.swift */; }; 19 | 46F017D41C7388650094204D /* HomeTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F017D31C7388650094204D /* HomeTableViewController.swift */; }; 20 | 46F017D61C738AE20094204D /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46F017D51C738AE20094204D /* CollectionViewController.swift */; }; 21 | 46F017DE1C73A9E10094204D /* RandomStringGenerator.m in Sources */ = {isa = PBXBuildFile; fileRef = 46F017DD1C73A9E10094204D /* RandomStringGenerator.m */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 42EAEAEA19C9EBF600C2AEEC /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 42EAEAC819C9EBF600C2AEEC /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 42EAEACF19C9EBF600C2AEEC; 30 | remoteInfo = SelfSizingCollectionViewCells; 31 | }; 32 | /* End PBXContainerItemProxy section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 42EAEAD019C9EBF600C2AEEC /* SelfSizingCollectionViewCells.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SelfSizingCollectionViewCells.app; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 42EAEAD419C9EBF600C2AEEC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | 42EAEAD519C9EBF600C2AEEC /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 38 | 42EAEAD719C9EBF600C2AEEC /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 39 | 42EAEAD819C9EBF600C2AEEC /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 40 | 42EAEADA19C9EBF600C2AEEC /* DynamicContentCollectionViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DynamicContentCollectionViewController.h; sourceTree = ""; }; 41 | 42EAEADB19C9EBF600C2AEEC /* DynamicContentCollectionViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DynamicContentCollectionViewController.m; sourceTree = ""; }; 42 | 42EAEADE19C9EBF600C2AEEC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 43 | 42EAEAE019C9EBF600C2AEEC /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 44 | 42EAEAE319C9EBF600C2AEEC /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 45 | 42EAEAE919C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SelfSizingCollectionViewCellsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 46 | 42EAEAEE19C9EBF600C2AEEC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 47 | 42EAEAEF19C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SelfSizingCollectionViewCellsTests.m; sourceTree = ""; }; 48 | 42EAEAFC19C9F96A00C2AEEC /* CollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CollectionViewCell.h; sourceTree = ""; }; 49 | 42EAEAFD19C9F96A00C2AEEC /* CollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CollectionViewCell.m; sourceTree = ""; }; 50 | 46F017D01C7386500094204D /* SelfSizingCollectionViewCells-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SelfSizingCollectionViewCells-Bridging-Header.h"; sourceTree = ""; }; 51 | 46F017D11C7386500094204D /* SimpleCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleCell.swift; sourceTree = ""; }; 52 | 46F017D31C7388650094204D /* HomeTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeTableViewController.swift; sourceTree = ""; }; 53 | 46F017D51C738AE20094204D /* CollectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectionViewController.swift; sourceTree = ""; }; 54 | 46F017DC1C73A9E10094204D /* RandomStringGenerator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RandomStringGenerator.h; sourceTree = ""; }; 55 | 46F017DD1C73A9E10094204D /* RandomStringGenerator.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RandomStringGenerator.m; sourceTree = ""; }; 56 | 46F017DF1C73B8B80094204D /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 57 | /* End PBXFileReference section */ 58 | 59 | /* Begin PBXFrameworksBuildPhase section */ 60 | 42EAEACD19C9EBF600C2AEEC /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | ); 65 | runOnlyForDeploymentPostprocessing = 0; 66 | }; 67 | 42EAEAE619C9EBF600C2AEEC /* Frameworks */ = { 68 | isa = PBXFrameworksBuildPhase; 69 | buildActionMask = 2147483647; 70 | files = ( 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | /* End PBXFrameworksBuildPhase section */ 75 | 76 | /* Begin PBXGroup section */ 77 | 42EAEAC719C9EBF600C2AEEC = { 78 | isa = PBXGroup; 79 | children = ( 80 | 46F017DF1C73B8B80094204D /* README.md */, 81 | 42EAEAD219C9EBF600C2AEEC /* SelfSizingCollectionViewCells */, 82 | 42EAEAEC19C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests */, 83 | 42EAEAD119C9EBF600C2AEEC /* Products */, 84 | ); 85 | sourceTree = ""; 86 | }; 87 | 42EAEAD119C9EBF600C2AEEC /* Products */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | 42EAEAD019C9EBF600C2AEEC /* SelfSizingCollectionViewCells.app */, 91 | 42EAEAE919C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests.xctest */, 92 | ); 93 | name = Products; 94 | sourceTree = ""; 95 | }; 96 | 42EAEAD219C9EBF600C2AEEC /* SelfSizingCollectionViewCells */ = { 97 | isa = PBXGroup; 98 | children = ( 99 | 46F017D31C7388650094204D /* HomeTableViewController.swift */, 100 | 46F017D51C738AE20094204D /* CollectionViewController.swift */, 101 | 42EAEADA19C9EBF600C2AEEC /* DynamicContentCollectionViewController.h */, 102 | 42EAEADB19C9EBF600C2AEEC /* DynamicContentCollectionViewController.m */, 103 | 42EAEAFC19C9F96A00C2AEEC /* CollectionViewCell.h */, 104 | 42EAEAFD19C9F96A00C2AEEC /* CollectionViewCell.m */, 105 | 42EAEADD19C9EBF600C2AEEC /* Main.storyboard */, 106 | 42EAEAD719C9EBF600C2AEEC /* AppDelegate.h */, 107 | 42EAEAD819C9EBF600C2AEEC /* AppDelegate.m */, 108 | 46F017D11C7386500094204D /* SimpleCell.swift */, 109 | 46F017DC1C73A9E10094204D /* RandomStringGenerator.h */, 110 | 46F017DD1C73A9E10094204D /* RandomStringGenerator.m */, 111 | 42EAEAE019C9EBF600C2AEEC /* Images.xcassets */, 112 | 42EAEAE219C9EBF600C2AEEC /* LaunchScreen.xib */, 113 | 42EAEAD319C9EBF600C2AEEC /* Supporting Files */, 114 | 46F017D01C7386500094204D /* SelfSizingCollectionViewCells-Bridging-Header.h */, 115 | ); 116 | path = SelfSizingCollectionViewCells; 117 | sourceTree = ""; 118 | }; 119 | 42EAEAD319C9EBF600C2AEEC /* Supporting Files */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 42EAEAD419C9EBF600C2AEEC /* Info.plist */, 123 | 42EAEAD519C9EBF600C2AEEC /* main.m */, 124 | ); 125 | name = "Supporting Files"; 126 | sourceTree = ""; 127 | }; 128 | 42EAEAEC19C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 42EAEAEF19C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests.m */, 132 | 42EAEAED19C9EBF600C2AEEC /* Supporting Files */, 133 | ); 134 | path = SelfSizingCollectionViewCellsTests; 135 | sourceTree = ""; 136 | }; 137 | 42EAEAED19C9EBF600C2AEEC /* Supporting Files */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 42EAEAEE19C9EBF600C2AEEC /* Info.plist */, 141 | ); 142 | name = "Supporting Files"; 143 | sourceTree = ""; 144 | }; 145 | /* End PBXGroup section */ 146 | 147 | /* Begin PBXNativeTarget section */ 148 | 42EAEACF19C9EBF600C2AEEC /* SelfSizingCollectionViewCells */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = 42EAEAF319C9EBF600C2AEEC /* Build configuration list for PBXNativeTarget "SelfSizingCollectionViewCells" */; 151 | buildPhases = ( 152 | 42EAEACC19C9EBF600C2AEEC /* Sources */, 153 | 42EAEACD19C9EBF600C2AEEC /* Frameworks */, 154 | 42EAEACE19C9EBF600C2AEEC /* Resources */, 155 | ); 156 | buildRules = ( 157 | ); 158 | dependencies = ( 159 | ); 160 | name = SelfSizingCollectionViewCells; 161 | productName = SelfSizingCollectionViewCells; 162 | productReference = 42EAEAD019C9EBF600C2AEEC /* SelfSizingCollectionViewCells.app */; 163 | productType = "com.apple.product-type.application"; 164 | }; 165 | 42EAEAE819C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = 42EAEAF619C9EBF600C2AEEC /* Build configuration list for PBXNativeTarget "SelfSizingCollectionViewCellsTests" */; 168 | buildPhases = ( 169 | 42EAEAE519C9EBF600C2AEEC /* Sources */, 170 | 42EAEAE619C9EBF600C2AEEC /* Frameworks */, 171 | 42EAEAE719C9EBF600C2AEEC /* Resources */, 172 | ); 173 | buildRules = ( 174 | ); 175 | dependencies = ( 176 | 42EAEAEB19C9EBF600C2AEEC /* PBXTargetDependency */, 177 | ); 178 | name = SelfSizingCollectionViewCellsTests; 179 | productName = SelfSizingCollectionViewCellsTests; 180 | productReference = 42EAEAE919C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests.xctest */; 181 | productType = "com.apple.product-type.bundle.unit-test"; 182 | }; 183 | /* End PBXNativeTarget section */ 184 | 185 | /* Begin PBXProject section */ 186 | 42EAEAC819C9EBF600C2AEEC /* Project object */ = { 187 | isa = PBXProject; 188 | attributes = { 189 | LastSwiftUpdateCheck = 0720; 190 | LastUpgradeCheck = 0820; 191 | ORGANIZATIONNAME = Afrozaar; 192 | TargetAttributes = { 193 | 42EAEACF19C9EBF600C2AEEC = { 194 | CreatedOnToolsVersion = 6.0; 195 | LastSwiftMigration = 0820; 196 | }; 197 | 42EAEAE819C9EBF600C2AEEC = { 198 | CreatedOnToolsVersion = 6.0; 199 | TestTargetID = 42EAEACF19C9EBF600C2AEEC; 200 | }; 201 | }; 202 | }; 203 | buildConfigurationList = 42EAEACB19C9EBF600C2AEEC /* Build configuration list for PBXProject "SelfSizingCollectionViewCells" */; 204 | compatibilityVersion = "Xcode 3.2"; 205 | developmentRegion = English; 206 | hasScannedForEncodings = 0; 207 | knownRegions = ( 208 | en, 209 | Base, 210 | ); 211 | mainGroup = 42EAEAC719C9EBF600C2AEEC; 212 | productRefGroup = 42EAEAD119C9EBF600C2AEEC /* Products */; 213 | projectDirPath = ""; 214 | projectRoot = ""; 215 | targets = ( 216 | 42EAEACF19C9EBF600C2AEEC /* SelfSizingCollectionViewCells */, 217 | 42EAEAE819C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests */, 218 | ); 219 | }; 220 | /* End PBXProject section */ 221 | 222 | /* Begin PBXResourcesBuildPhase section */ 223 | 42EAEACE19C9EBF600C2AEEC /* Resources */ = { 224 | isa = PBXResourcesBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | 42EAEADF19C9EBF600C2AEEC /* Main.storyboard in Resources */, 228 | 42EAEAE419C9EBF600C2AEEC /* LaunchScreen.xib in Resources */, 229 | 42EAEAE119C9EBF600C2AEEC /* Images.xcassets in Resources */, 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | 42EAEAE719C9EBF600C2AEEC /* Resources */ = { 234 | isa = PBXResourcesBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | ); 238 | runOnlyForDeploymentPostprocessing = 0; 239 | }; 240 | /* End PBXResourcesBuildPhase section */ 241 | 242 | /* Begin PBXSourcesBuildPhase section */ 243 | 42EAEACC19C9EBF600C2AEEC /* Sources */ = { 244 | isa = PBXSourcesBuildPhase; 245 | buildActionMask = 2147483647; 246 | files = ( 247 | 46F017D41C7388650094204D /* HomeTableViewController.swift in Sources */, 248 | 46F017DE1C73A9E10094204D /* RandomStringGenerator.m in Sources */, 249 | 42EAEAFE19C9F96A00C2AEEC /* CollectionViewCell.m in Sources */, 250 | 42EAEADC19C9EBF600C2AEEC /* DynamicContentCollectionViewController.m in Sources */, 251 | 42EAEAD919C9EBF600C2AEEC /* AppDelegate.m in Sources */, 252 | 46F017D61C738AE20094204D /* CollectionViewController.swift in Sources */, 253 | 42EAEAD619C9EBF600C2AEEC /* main.m in Sources */, 254 | 46F017D21C7386500094204D /* SimpleCell.swift in Sources */, 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | 42EAEAE519C9EBF600C2AEEC /* Sources */ = { 259 | isa = PBXSourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | 42EAEAF019C9EBF600C2AEEC /* SelfSizingCollectionViewCellsTests.m in Sources */, 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXSourcesBuildPhase section */ 267 | 268 | /* Begin PBXTargetDependency section */ 269 | 42EAEAEB19C9EBF600C2AEEC /* PBXTargetDependency */ = { 270 | isa = PBXTargetDependency; 271 | target = 42EAEACF19C9EBF600C2AEEC /* SelfSizingCollectionViewCells */; 272 | targetProxy = 42EAEAEA19C9EBF600C2AEEC /* PBXContainerItemProxy */; 273 | }; 274 | /* End PBXTargetDependency section */ 275 | 276 | /* Begin PBXVariantGroup section */ 277 | 42EAEADD19C9EBF600C2AEEC /* Main.storyboard */ = { 278 | isa = PBXVariantGroup; 279 | children = ( 280 | 42EAEADE19C9EBF600C2AEEC /* Base */, 281 | ); 282 | name = Main.storyboard; 283 | sourceTree = ""; 284 | }; 285 | 42EAEAE219C9EBF600C2AEEC /* LaunchScreen.xib */ = { 286 | isa = PBXVariantGroup; 287 | children = ( 288 | 42EAEAE319C9EBF600C2AEEC /* Base */, 289 | ); 290 | name = LaunchScreen.xib; 291 | sourceTree = ""; 292 | }; 293 | /* End PBXVariantGroup section */ 294 | 295 | /* Begin XCBuildConfiguration section */ 296 | 42EAEAF119C9EBF600C2AEEC /* Debug */ = { 297 | isa = XCBuildConfiguration; 298 | buildSettings = { 299 | ALWAYS_SEARCH_USER_PATHS = NO; 300 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 301 | CLANG_CXX_LIBRARY = "libc++"; 302 | CLANG_ENABLE_MODULES = YES; 303 | CLANG_ENABLE_OBJC_ARC = YES; 304 | CLANG_WARN_BOOL_CONVERSION = YES; 305 | CLANG_WARN_CONSTANT_CONVERSION = YES; 306 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 307 | CLANG_WARN_EMPTY_BODY = YES; 308 | CLANG_WARN_ENUM_CONVERSION = YES; 309 | CLANG_WARN_INFINITE_RECURSION = YES; 310 | CLANG_WARN_INT_CONVERSION = YES; 311 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 312 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 313 | CLANG_WARN_UNREACHABLE_CODE = YES; 314 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 315 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 316 | COPY_PHASE_STRIP = NO; 317 | ENABLE_STRICT_OBJC_MSGSEND = YES; 318 | ENABLE_TESTABILITY = YES; 319 | GCC_C_LANGUAGE_STANDARD = gnu99; 320 | GCC_DYNAMIC_NO_PIC = NO; 321 | GCC_NO_COMMON_BLOCKS = YES; 322 | GCC_OPTIMIZATION_LEVEL = 0; 323 | GCC_PREPROCESSOR_DEFINITIONS = ( 324 | "DEBUG=1", 325 | "$(inherited)", 326 | ); 327 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 328 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 329 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 330 | GCC_WARN_UNDECLARED_SELECTOR = YES; 331 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 332 | GCC_WARN_UNUSED_FUNCTION = YES; 333 | GCC_WARN_UNUSED_VARIABLE = YES; 334 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 335 | MTL_ENABLE_DEBUG_INFO = YES; 336 | ONLY_ACTIVE_ARCH = YES; 337 | SDKROOT = iphoneos; 338 | }; 339 | name = Debug; 340 | }; 341 | 42EAEAF219C9EBF600C2AEEC /* Release */ = { 342 | isa = XCBuildConfiguration; 343 | buildSettings = { 344 | ALWAYS_SEARCH_USER_PATHS = NO; 345 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 346 | CLANG_CXX_LIBRARY = "libc++"; 347 | CLANG_ENABLE_MODULES = YES; 348 | CLANG_ENABLE_OBJC_ARC = YES; 349 | CLANG_WARN_BOOL_CONVERSION = YES; 350 | CLANG_WARN_CONSTANT_CONVERSION = YES; 351 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 352 | CLANG_WARN_EMPTY_BODY = YES; 353 | CLANG_WARN_ENUM_CONVERSION = YES; 354 | CLANG_WARN_INFINITE_RECURSION = YES; 355 | CLANG_WARN_INT_CONVERSION = YES; 356 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 357 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 358 | CLANG_WARN_UNREACHABLE_CODE = YES; 359 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 360 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 361 | COPY_PHASE_STRIP = YES; 362 | ENABLE_NS_ASSERTIONS = NO; 363 | ENABLE_STRICT_OBJC_MSGSEND = YES; 364 | GCC_C_LANGUAGE_STANDARD = gnu99; 365 | GCC_NO_COMMON_BLOCKS = YES; 366 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 367 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 368 | GCC_WARN_UNDECLARED_SELECTOR = YES; 369 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 370 | GCC_WARN_UNUSED_FUNCTION = YES; 371 | GCC_WARN_UNUSED_VARIABLE = YES; 372 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 373 | MTL_ENABLE_DEBUG_INFO = NO; 374 | SDKROOT = iphoneos; 375 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 376 | VALIDATE_PRODUCT = YES; 377 | }; 378 | name = Release; 379 | }; 380 | 42EAEAF419C9EBF600C2AEEC /* Debug */ = { 381 | isa = XCBuildConfiguration; 382 | buildSettings = { 383 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 384 | CLANG_ENABLE_MODULES = YES; 385 | INFOPLIST_FILE = SelfSizingCollectionViewCells/Info.plist; 386 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 387 | PRODUCT_BUNDLE_IDENTIFIER = "com.danielgalasko.$(PRODUCT_NAME:rfc1034identifier)"; 388 | PRODUCT_NAME = "$(TARGET_NAME)"; 389 | SWIFT_OBJC_BRIDGING_HEADER = "SelfSizingCollectionViewCells/SelfSizingCollectionViewCells-Bridging-Header.h"; 390 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 391 | SWIFT_VERSION = 3.0; 392 | TARGETED_DEVICE_FAMILY = "1,2"; 393 | }; 394 | name = Debug; 395 | }; 396 | 42EAEAF519C9EBF600C2AEEC /* Release */ = { 397 | isa = XCBuildConfiguration; 398 | buildSettings = { 399 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 400 | CLANG_ENABLE_MODULES = YES; 401 | INFOPLIST_FILE = SelfSizingCollectionViewCells/Info.plist; 402 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 403 | PRODUCT_BUNDLE_IDENTIFIER = "com.danielgalasko.$(PRODUCT_NAME:rfc1034identifier)"; 404 | PRODUCT_NAME = "$(TARGET_NAME)"; 405 | SWIFT_OBJC_BRIDGING_HEADER = "SelfSizingCollectionViewCells/SelfSizingCollectionViewCells-Bridging-Header.h"; 406 | SWIFT_VERSION = 3.0; 407 | TARGETED_DEVICE_FAMILY = "1,2"; 408 | }; 409 | name = Release; 410 | }; 411 | 42EAEAF719C9EBF600C2AEEC /* Debug */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | BUNDLE_LOADER = "$(TEST_HOST)"; 415 | FRAMEWORK_SEARCH_PATHS = ( 416 | "$(SDKROOT)/Developer/Library/Frameworks", 417 | "$(inherited)", 418 | ); 419 | GCC_PREPROCESSOR_DEFINITIONS = ( 420 | "DEBUG=1", 421 | "$(inherited)", 422 | ); 423 | INFOPLIST_FILE = SelfSizingCollectionViewCellsTests/Info.plist; 424 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 425 | PRODUCT_BUNDLE_IDENTIFIER = "com.galasko.$(PRODUCT_NAME:rfc1034identifier)"; 426 | PRODUCT_NAME = "$(TARGET_NAME)"; 427 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SelfSizingCollectionViewCells.app/SelfSizingCollectionViewCells"; 428 | }; 429 | name = Debug; 430 | }; 431 | 42EAEAF819C9EBF600C2AEEC /* Release */ = { 432 | isa = XCBuildConfiguration; 433 | buildSettings = { 434 | BUNDLE_LOADER = "$(TEST_HOST)"; 435 | FRAMEWORK_SEARCH_PATHS = ( 436 | "$(SDKROOT)/Developer/Library/Frameworks", 437 | "$(inherited)", 438 | ); 439 | INFOPLIST_FILE = SelfSizingCollectionViewCellsTests/Info.plist; 440 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 441 | PRODUCT_BUNDLE_IDENTIFIER = "com.galasko.$(PRODUCT_NAME:rfc1034identifier)"; 442 | PRODUCT_NAME = "$(TARGET_NAME)"; 443 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/SelfSizingCollectionViewCells.app/SelfSizingCollectionViewCells"; 444 | }; 445 | name = Release; 446 | }; 447 | /* End XCBuildConfiguration section */ 448 | 449 | /* Begin XCConfigurationList section */ 450 | 42EAEACB19C9EBF600C2AEEC /* Build configuration list for PBXProject "SelfSizingCollectionViewCells" */ = { 451 | isa = XCConfigurationList; 452 | buildConfigurations = ( 453 | 42EAEAF119C9EBF600C2AEEC /* Debug */, 454 | 42EAEAF219C9EBF600C2AEEC /* Release */, 455 | ); 456 | defaultConfigurationIsVisible = 0; 457 | defaultConfigurationName = Release; 458 | }; 459 | 42EAEAF319C9EBF600C2AEEC /* Build configuration list for PBXNativeTarget "SelfSizingCollectionViewCells" */ = { 460 | isa = XCConfigurationList; 461 | buildConfigurations = ( 462 | 42EAEAF419C9EBF600C2AEEC /* Debug */, 463 | 42EAEAF519C9EBF600C2AEEC /* Release */, 464 | ); 465 | defaultConfigurationIsVisible = 0; 466 | defaultConfigurationName = Release; 467 | }; 468 | 42EAEAF619C9EBF600C2AEEC /* Build configuration list for PBXNativeTarget "SelfSizingCollectionViewCellsTests" */ = { 469 | isa = XCConfigurationList; 470 | buildConfigurations = ( 471 | 42EAEAF719C9EBF600C2AEEC /* Debug */, 472 | 42EAEAF819C9EBF600C2AEEC /* Release */, 473 | ); 474 | defaultConfigurationIsVisible = 0; 475 | defaultConfigurationName = Release; 476 | }; 477 | /* End XCConfigurationList section */ 478 | }; 479 | rootObject = 42EAEAC819C9EBF600C2AEEC /* Project object */; 480 | } 481 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells.xcodeproj/project.xcworkspace/xcshareddata/SelfSizingCollectionViewCells.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | B5A18592-BCF8-44D3-A37E-35B7C9717186 9 | IDESourceControlProjectName 10 | SelfSizingCollectionViewCells 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | BB09B44A3412B21AB63416D1675236DED4450931 14 | https://github.com/danielgalasko/DGSelfSizingCollectionViewCells.git 15 | 16 | IDESourceControlProjectPath 17 | SelfSizingCollectionViewCells.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | BB09B44A3412B21AB63416D1675236DED4450931 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/danielgalasko/DGSelfSizingCollectionViewCells.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | BB09B44A3412B21AB63416D1675236DED4450931 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | BB09B44A3412B21AB63416D1675236DED4450931 36 | IDESourceControlWCCName 37 | DGSelfSizingCollectionViewCells 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells.xcodeproj/xcuserdata/danielgalasko.xcuserdatad/xcschemes/SelfSizingCollectionViewCells.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // SelfSizingCollectionViewCells 4 | // 5 | // Created by Daniel Galasko on 9/17/14. 6 | // Copyright (c) 2014 Galasko. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // SelfSizingCollectionViewCells 4 | // 5 | // Created by Daniel Galasko on 9/17/14. 6 | // Copyright (c) 2014 Galasko. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // 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. 25 | // 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. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // 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. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // 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. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // 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. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/CollectionViewCell.h: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionCellCollectionViewCell.h 3 | // SelfSizingCollectionViewCells 4 | // 5 | // Created by Daniel Galasko on 9/17/14. 6 | // Copyright (c) 2014 Galasko. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface CollectionViewCell : UICollectionViewCell 12 | @property (nonatomic, strong) NSString *text; 13 | @end 14 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/CollectionViewCell.m: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionCellCollectionViewCell.m 3 | // SelfSizingCollectionViewCells 4 | // 5 | // Created by Daniel Galasko on 9/17/14. 6 | // Copyright (c) 2014 Galasko. All rights reserved. 7 | // 8 | 9 | #import "CollectionViewCell.h" 10 | 11 | @interface CollectionViewCell() 12 | @property (weak, nonatomic) IBOutlet UILabel *textLabel; 13 | @property (nonatomic, assign) CGSize cachedSize; 14 | @end 15 | 16 | @implementation CollectionViewCell 17 | 18 | - (void)awakeFromNib { 19 | [super awakeFromNib]; 20 | self.cachedSize = CGSizeZero; 21 | } 22 | 23 | - (void)setText:(NSString *)text { 24 | self.cachedSize = CGSizeZero; 25 | self.textLabel.text = text; 26 | } 27 | 28 | - (NSString *)text { 29 | return self.textLabel.text; 30 | } 31 | 32 | //- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes { 33 | // 34 | // UICollectionViewLayoutAttributes *attr = [layoutAttributes copy]; 35 | // if (CGSizeEqualToSize(CGSizeZero, self.cachedSize)) { 36 | // self.frame = attr.frame; 37 | // [self setNeedsLayout]; 38 | // [self layoutIfNeeded]; 39 | // CGFloat fontSize = [UIFontDescriptor preferredFontDescriptorWithTextStyle:UIFontTextStyleHeadline].pointSize * 1.4; 40 | // self.textLabel.font = [UIFont fontWithName:[[UIFont fontNamesForFamilyName:self.textLabel.font.familyName] firstObject] size:fontSize]; 41 | // CGSize size = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; 42 | // self.cachedSize = size; 43 | // CGRect newFrame = attr.frame; 44 | // newFrame.size = size; 45 | // attr.frame = newFrame; 46 | // } 47 | // return attr; 48 | //} 49 | @end 50 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/CollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewController.swift 3 | // SelfSizingCollectionViewCells 4 | // 5 | // Created by Daniel Galasko on 2016/02/16. 6 | // Copyright © 2016 Galasko. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | private let reuseIdentifier = "Cell" 12 | 13 | extension UIViewController { 14 | func reloadCollectionViewWithFontSizeChanges(_ collectionView: UICollectionView) { 15 | NotificationCenter.default.addObserver(forName: NSNotification.Name.UIContentSizeCategoryDidChange, 16 | object: nil, 17 | queue: OperationQueue.main) { (notification) -> Void in 18 | collectionView.reloadData() 19 | } 20 | } 21 | } 22 | 23 | class CollectionViewController: UICollectionViewController { 24 | 25 | struct Configuration { 26 | 27 | enum CellType { 28 | case simpleCell 29 | case layoutAttributesCell 30 | case simpleCellWithDynamicText 31 | } 32 | 33 | let cellType: CellType 34 | 35 | } 36 | 37 | var configuration: Configuration = Configuration(cellType: .simpleCell) 38 | 39 | var flowLayout: UICollectionViewFlowLayout { 40 | return self.collectionView?.collectionViewLayout as! UICollectionViewFlowLayout 41 | } 42 | 43 | override func viewDidLoad() { 44 | super.viewDidLoad() 45 | self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100) 46 | self.collectionView!.register(SimpleCell.self, forCellWithReuseIdentifier: String(describing: SimpleCell.self)) 47 | self.collectionView!.register(SimpleCellImplementingLayoutAttributes.self, forCellWithReuseIdentifier: String(describing: SimpleCellImplementingLayoutAttributes.self)) 48 | reloadCollectionViewWithFontSizeChanges(collectionView!) 49 | 50 | switch configuration.cellType { 51 | case .simpleCell: 52 | break 53 | case .layoutAttributesCell: 54 | let widths = Array(0 ..< collectionView(collectionView!, numberOfItemsInSection: 0)).map({ ($0 % 5) * 10 + 50 }) 55 | generatedSizes = widths.map({ CGSize(width: CGFloat($0), height: 100)}) 56 | case .simpleCellWithDynamicText: 57 | let indices = Array(0 ..< collectionView(collectionView!, numberOfItemsInSection: 0)) 58 | generatedStrings = indices.map({ RandomStringGenerator.randomString(withLength: UInt(($0 % 5) + 4)) }) 59 | } 60 | } 61 | 62 | // MARK: UICollectionViewDataSource 63 | 64 | override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 65 | return 100 66 | } 67 | 68 | var generatedStrings: [String] = [] 69 | var generatedSizes: [CGSize] = [] 70 | 71 | override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 72 | switch configuration.cellType { 73 | case .simpleCell: 74 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SimpleCell.self), for: indexPath) as! SimpleCell 75 | cell.label.text = "Hello World" 76 | cell.isHeightCalculated = true 77 | return cell 78 | case .layoutAttributesCell: 79 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SimpleCellImplementingLayoutAttributes.self), for: indexPath) as! SimpleCellImplementingLayoutAttributes 80 | cell.desiredSize = generatedSizes[indexPath.item] 81 | return cell 82 | case .simpleCellWithDynamicText: 83 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: String(describing: SimpleCell.self), for: indexPath) as! SimpleCell 84 | cell.isHeightCalculated = false 85 | cell.label.text = generatedStrings[indexPath.item] 86 | return cell 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/DynamicContentCollectionViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // SelfSizingCollectionViewCells 4 | // 5 | // Created by Daniel Galasko on 9/17/14. 6 | // Copyright (c) 2014 Galasko. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DynamicContentCollectionViewController : UICollectionViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/DynamicContentCollectionViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // SelfSizingCollectionViewCells 4 | // 5 | // Created by Daniel Galasko on 9/17/14. 6 | // Copyright (c) 2014 Galasko. All rights reserved. 7 | // 8 | 9 | #import "DynamicContentCollectionViewController.h" 10 | #import "CollectionViewCell.h" 11 | #import "SelfSizingCollectionViewCells-Swift.h" 12 | #import "RandomStringGenerator.h" 13 | 14 | NSUInteger const kNumberOfCells = 100; 15 | /// Tweak these and watch it break 16 | NSUInteger const kMinStringLength = 5; 17 | NSUInteger const kMaxStringLength = 10; 18 | 19 | @interface DynamicContentCollectionViewController () 20 | @property (strong,nonatomic) NSMutableArray *array; 21 | @end 22 | 23 | @implementation DynamicContentCollectionViewController 24 | 25 | #pragma mark - View Lifecycle - 26 | 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | [self.array removeAllObjects]; 30 | for (int i = 0; i < kNumberOfCells; ++i) { 31 | [self.array addObject:[RandomStringGenerator randomStringWithLength:MAX(kMinStringLength,arc4random_uniform(kMaxStringLength))]]; 32 | } 33 | [self.collectionView registerClass:[SimpleCell class] forCellWithReuseIdentifier:NSStringFromClass([SimpleCell class])]; 34 | self.collectionView.dataSource = self; 35 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reload) name:UIContentSizeCategoryDidChangeNotification object:nil]; 36 | [self setEstimatedSizeIfNeeded]; 37 | } 38 | 39 | - (void)setEstimatedSizeIfNeeded { 40 | UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *)self.collectionView.collectionViewLayout; 41 | CGFloat estimatedWidth = 30.f; 42 | if (flowLayout.estimatedItemSize.width != estimatedWidth) { 43 | [flowLayout setEstimatedItemSize:CGSizeMake(estimatedWidth, 100)]; 44 | [flowLayout invalidateLayout]; 45 | } 46 | } 47 | 48 | - (void)viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id)coordinator { 49 | [coordinator animateAlongsideTransition:^(id _Nonnull context) { 50 | 51 | } completion:^(id _Nonnull context) { 52 | [self.collectionViewLayout invalidateLayout]; 53 | [self.collectionView reloadData]; 54 | }]; 55 | } 56 | #pragma mark - UICollectionViewDataSource - 57 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath { 58 | CollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"identifier" forIndexPath:indexPath]; 59 | cell.text = _array[indexPath.row]; 60 | return cell; 61 | } 62 | 63 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section { 64 | return _array.count; 65 | } 66 | 67 | #pragma mark - Convenience = 68 | - (void)dealloc { 69 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 70 | } 71 | 72 | - (NSMutableArray *)array { 73 | if (!_array) { 74 | _array = [NSMutableArray array]; 75 | } 76 | return _array; 77 | } 78 | 79 | - (void)reload { 80 | [self setEstimatedSizeIfNeeded]; 81 | [self.collectionView reloadData]; 82 | } 83 | 84 | - (BOOL)prefersStatusBarHidden { 85 | return YES; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/HomeTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HomeTableViewController.swift 3 | // SelfSizingCollectionViewCells 4 | // 5 | // Created by Daniel Galasko on 2016/02/16. 6 | // Copyright © 2016 Galasko. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class HomeCell: UITableViewCell { 12 | @IBOutlet weak var label: UILabel! 13 | } 14 | 15 | enum StoryboardViewControllers { 16 | case staticCellsViewController 17 | case dynamicCellsViewController 18 | case layoutAttributesCellsViewController 19 | case dynamicCellsWithLayoutAttributes 20 | 21 | func storyboardID() -> String { 22 | switch self { 23 | case .staticCellsViewController: 24 | return "CollectionViewController" 25 | case .dynamicCellsViewController: 26 | return "DynamicContentCollectionViewController" 27 | case .layoutAttributesCellsViewController: 28 | return "CollectionViewController" 29 | case .dynamicCellsWithLayoutAttributes: 30 | return "CollectionViewController" 31 | } 32 | } 33 | 34 | func createController() -> UIViewController { 35 | let storyboard = UIStoryboard(name: "Main", bundle: nil) 36 | let controller = storyboard.instantiateViewController(withIdentifier: storyboardID()) 37 | configureControllerIfNeeded(controller) 38 | return controller 39 | } 40 | 41 | func configureControllerIfNeeded(_ controller: UIViewController) { 42 | switch self { 43 | case .layoutAttributesCellsViewController: 44 | let c = controller as! CollectionViewController 45 | c.configuration = CollectionViewController.Configuration(cellType: CollectionViewController.Configuration.CellType.layoutAttributesCell) 46 | case .dynamicCellsWithLayoutAttributes: 47 | let c = controller as! CollectionViewController 48 | c.configuration = CollectionViewController.Configuration(cellType: CollectionViewController.Configuration.CellType.simpleCellWithDynamicText) 49 | default: 50 | break 51 | } 52 | } 53 | } 54 | 55 | class HomeTableViewController: UITableViewController { 56 | 57 | struct ActionableCells { 58 | let title: String 59 | let controller: StoryboardViewControllers 60 | } 61 | 62 | let cells = [ActionableCells(title: "Static Content Cells. Technically since the content never changes these cells should all be the same size. This demonstrates what the layout does when all cells have the same size", controller: StoryboardViewControllers.staticCellsViewController), 63 | ActionableCells(title: "Dynamic content cells implementing preferred layout attributes in the cell subclass with auto layout", controller: StoryboardViewControllers.dynamicCellsWithLayoutAttributes), 64 | ActionableCells(title: "Dynamic Content Cells using auto layout WITHOUT overriding preferredLayoutAttributes", controller: StoryboardViewControllers.dynamicCellsViewController), 65 | ActionableCells(title: "Cells overriding preferredLayoutAttributes with NO auto layout. We should expect to see dynamic sizing.", controller: StoryboardViewControllers.layoutAttributesCellsViewController)] 66 | 67 | override func viewDidLoad() { 68 | super.viewDidLoad() 69 | tableView.estimatedRowHeight = 50 70 | self.title = "Home" 71 | } 72 | 73 | // MARK: - Table view data source 74 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 75 | return cells.count 76 | } 77 | 78 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 79 | let cell = tableView.dequeueReusableCell(withIdentifier: "HomeCellReuseIdentifier", for: indexPath) as! HomeCell 80 | cell.accessoryType = .disclosureIndicator 81 | cell.label.text = cells[indexPath.row].title 82 | return cell 83 | } 84 | 85 | override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 86 | let controller = cells[indexPath.row].controller.createController() 87 | show(controller, sender: nil) 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/RandomStringGenerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // RandomStringGenerator.h 3 | // SelfSizingCollectionViewCells 4 | // 5 | // Created by Daniel Galasko on 2016/02/16. 6 | // Copyright © 2016 Galasko. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface RandomStringGenerator : NSObject 12 | + (NSString *)randomStringWithLength:(NSUInteger)length; 13 | @end 14 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/RandomStringGenerator.m: -------------------------------------------------------------------------------- 1 | // 2 | // RandomStringGenerator.m 3 | // SelfSizingCollectionViewCells 4 | // 5 | // Created by Daniel Galasko on 2016/02/16. 6 | // Copyright © 2016 Galasko. All rights reserved. 7 | // 8 | 9 | #import "RandomStringGenerator.h" 10 | 11 | @implementation RandomStringGenerator 12 | 13 | NSString *const RandomStringGeneratorLetters = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 14 | 15 | + (NSString *)randomStringWithLength:(NSUInteger)length { 16 | NSMutableString *randomString = [NSMutableString stringWithCapacity:length]; 17 | for (NSUInteger i=0; i Void in 19 | self?.font = UIFont.preferredFont(forTextStyle: UIFontTextStyle.body) 20 | } 21 | } 22 | } 23 | 24 | class SimpleCellImplementingLayoutAttributes: UICollectionViewCell { 25 | override init(frame: CGRect) { 26 | super.init(frame: frame) 27 | contentView.backgroundColor = .blue 28 | contentView.layer.cornerRadius = 6 29 | contentView.clipsToBounds = true 30 | } 31 | 32 | required init?(coder aDecoder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | 36 | var desiredSize: CGSize = CGSize(width: 100, height: 100) 37 | 38 | // Without caching our size for preferredLayoutAttributesFittingAttributes it will get called multiple times and crash if we keep changing the frame 39 | var cachedSize: CGSize? 40 | 41 | override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { 42 | if let cachedSize = cachedSize, cachedSize.equalTo(desiredSize) { 43 | return layoutAttributes 44 | } 45 | cachedSize = desiredSize 46 | var newFrame = layoutAttributes.frame 47 | newFrame.size = desiredSize 48 | layoutAttributes.frame = newFrame 49 | return layoutAttributes 50 | } 51 | } 52 | 53 | class SimpleCell: UICollectionViewCell { 54 | 55 | let label: UILabel 56 | 57 | override init(frame: CGRect) { 58 | label = UILabel(frame: frame) 59 | label.translatesAutoresizingMaskIntoConstraints = false 60 | label.useSystemFont() 61 | label.textAlignment = .right 62 | super.init(frame: frame) 63 | NotificationCenter.default.addObserver(forName: NSNotification.Name.UIContentSizeCategoryDidChange, object: nil, queue: OperationQueue.main) {[weak self] (_) -> Void in 64 | self?.label.useSystemFont() 65 | self?.isHeightCalculated = false 66 | } 67 | contentView.addSubview(label) 68 | let views = ["label" : label] 69 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|-0-[label(<=100)]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)) 70 | contentView.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[label(100)]-0-|", options: NSLayoutFormatOptions(rawValue: 0), metrics: nil, views: views)) 71 | 72 | contentView.backgroundColor = .white 73 | } 74 | 75 | //forces the system to do one layout pass 76 | var isHeightCalculated: Bool = false 77 | 78 | override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { 79 | //Exhibit A - We need to cache our calculation to prevent a crash. 80 | if !isHeightCalculated { 81 | setNeedsLayout() 82 | layoutIfNeeded() 83 | let size = contentView.systemLayoutSizeFitting(layoutAttributes.size) 84 | var newFrame = layoutAttributes.frame 85 | newFrame.size.width = CGFloat(ceilf(Float(size.width))) 86 | layoutAttributes.frame = newFrame 87 | isHeightCalculated = true 88 | } 89 | return layoutAttributes 90 | } 91 | 92 | required init?(coder aDecoder: NSCoder) { 93 | fatalError("init(coder:) has not been implemented") 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCells/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // SelfSizingCollectionViewCells 4 | // 5 | // Created by Daniel Galasko on 9/17/14. 6 | // Copyright (c) 2014 Galasko. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCellsTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /SelfSizingCollectionViewCellsTests/SelfSizingCollectionViewCellsTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SelfSizingCollectionViewCellsTests.m 3 | // SelfSizingCollectionViewCellsTests 4 | // 5 | // Created by Daniel Galasko on 9/17/14. 6 | // Copyright (c) 2014 Galasko. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface SelfSizingCollectionViewCellsTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation SelfSizingCollectionViewCellsTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielgalasko/DGSelfSizingCollectionViewCells/ea34eeb1be765d10f37437bc61152fb732b48c20/screenshot.png --------------------------------------------------------------------------------