├── .gitignore ├── CellLayout.xcodeproj ├── project.pbxproj └── project.xcworkspace │ └── contents.xcworkspacedata ├── CellLayout ├── AppDelegate.swift ├── ContentTableCell.swift ├── ContentTableCell.xib ├── Images.xcassets │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── LaunchImage.launchimage │ │ └── Contents.json ├── Info.plist ├── Label.swift ├── TableViewController.swift ├── UIDeviceExtension.swift ├── UITableViewCellExtension.swift └── UITableViewExtension.swift ├── CellLayoutTests ├── CellLayoutTests.swift └── Info.plist ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | *.xcworkspace 4 | xcuserdata 5 | *.swp 6 | -------------------------------------------------------------------------------- /CellLayout.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0B94A2721B193651005B33E0 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B94A2711B193651005B33E0 /* AppDelegate.swift */; }; 11 | 0B94A2741B193651005B33E0 /* TableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B94A2731B193651005B33E0 /* TableViewController.swift */; }; 12 | 0B94A2791B193651005B33E0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0B94A2781B193651005B33E0 /* Images.xcassets */; }; 13 | 0B94A2881B193651005B33E0 /* CellLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B94A2871B193651005B33E0 /* CellLayoutTests.swift */; }; 14 | 0B94A2921B1938EF005B33E0 /* ContentTableCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 0B94A2911B1938EF005B33E0 /* ContentTableCell.xib */; }; 15 | 0B94A2941B193908005B33E0 /* ContentTableCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B94A2931B193908005B33E0 /* ContentTableCell.swift */; }; 16 | 0BE5CE761B2B963000242C99 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BE5CE751B2B963000242C99 /* Label.swift */; }; 17 | 0BE5CE781B2BBF1800242C99 /* UITableViewCellExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BE5CE771B2BBF1800242C99 /* UITableViewCellExtension.swift */; }; 18 | 0BE5CE7A1B2BC51500242C99 /* UITableViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BE5CE791B2BC51500242C99 /* UITableViewExtension.swift */; }; 19 | 0BFA3D5E1B2B736500A30556 /* UIDeviceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0BFA3D5D1B2B736500A30556 /* UIDeviceExtension.swift */; }; 20 | /* End PBXBuildFile section */ 21 | 22 | /* Begin PBXContainerItemProxy section */ 23 | 0B94A2821B193651005B33E0 /* PBXContainerItemProxy */ = { 24 | isa = PBXContainerItemProxy; 25 | containerPortal = 0B94A2641B193651005B33E0 /* Project object */; 26 | proxyType = 1; 27 | remoteGlobalIDString = 0B94A26B1B193651005B33E0; 28 | remoteInfo = CellLayout; 29 | }; 30 | /* End PBXContainerItemProxy section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 0B94A26C1B193651005B33E0 /* CellLayout.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CellLayout.app; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 0B94A2701B193651005B33E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 35 | 0B94A2711B193651005B33E0 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 36 | 0B94A2731B193651005B33E0 /* TableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableViewController.swift; sourceTree = ""; }; 37 | 0B94A2781B193651005B33E0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 38 | 0B94A2811B193651005B33E0 /* CellLayoutTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CellLayoutTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | 0B94A2861B193651005B33E0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 0B94A2871B193651005B33E0 /* CellLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellLayoutTests.swift; sourceTree = ""; }; 41 | 0B94A2911B1938EF005B33E0 /* ContentTableCell.xib */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.xib; path = ContentTableCell.xib; sourceTree = ""; }; 42 | 0B94A2931B193908005B33E0 /* ContentTableCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContentTableCell.swift; sourceTree = ""; }; 43 | 0BE5CE751B2B963000242C99 /* Label.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = ""; }; 44 | 0BE5CE771B2BBF1800242C99 /* UITableViewCellExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableViewCellExtension.swift; sourceTree = ""; }; 45 | 0BE5CE791B2BC51500242C99 /* UITableViewExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UITableViewExtension.swift; sourceTree = ""; }; 46 | 0BFA3D5D1B2B736500A30556 /* UIDeviceExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIDeviceExtension.swift; sourceTree = ""; }; 47 | /* End PBXFileReference section */ 48 | 49 | /* Begin PBXFrameworksBuildPhase section */ 50 | 0B94A2691B193651005B33E0 /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | 0B94A27E1B193651005B33E0 /* Frameworks */ = { 58 | isa = PBXFrameworksBuildPhase; 59 | buildActionMask = 2147483647; 60 | files = ( 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 0B94A2631B193651005B33E0 = { 68 | isa = PBXGroup; 69 | children = ( 70 | 0B94A26E1B193651005B33E0 /* CellLayout */, 71 | 0B94A2841B193651005B33E0 /* CellLayoutTests */, 72 | 0B94A26D1B193651005B33E0 /* Products */, 73 | ); 74 | sourceTree = ""; 75 | }; 76 | 0B94A26D1B193651005B33E0 /* Products */ = { 77 | isa = PBXGroup; 78 | children = ( 79 | 0B94A26C1B193651005B33E0 /* CellLayout.app */, 80 | 0B94A2811B193651005B33E0 /* CellLayoutTests.xctest */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | 0B94A26E1B193651005B33E0 /* CellLayout */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 0B94A2711B193651005B33E0 /* AppDelegate.swift */, 89 | 0B94A2931B193908005B33E0 /* ContentTableCell.swift */, 90 | 0B94A2911B1938EF005B33E0 /* ContentTableCell.xib */, 91 | 0BE5CE751B2B963000242C99 /* Label.swift */, 92 | 0B94A26F1B193651005B33E0 /* Supporting Files */, 93 | 0B94A2731B193651005B33E0 /* TableViewController.swift */, 94 | 0BFA3D5D1B2B736500A30556 /* UIDeviceExtension.swift */, 95 | 0BE5CE771B2BBF1800242C99 /* UITableViewCellExtension.swift */, 96 | 0BE5CE791B2BC51500242C99 /* UITableViewExtension.swift */, 97 | ); 98 | path = CellLayout; 99 | sourceTree = ""; 100 | }; 101 | 0B94A26F1B193651005B33E0 /* Supporting Files */ = { 102 | isa = PBXGroup; 103 | children = ( 104 | 0B94A2781B193651005B33E0 /* Images.xcassets */, 105 | 0B94A2701B193651005B33E0 /* Info.plist */, 106 | ); 107 | name = "Supporting Files"; 108 | sourceTree = ""; 109 | }; 110 | 0B94A2841B193651005B33E0 /* CellLayoutTests */ = { 111 | isa = PBXGroup; 112 | children = ( 113 | 0B94A2871B193651005B33E0 /* CellLayoutTests.swift */, 114 | 0B94A2851B193651005B33E0 /* Supporting Files */, 115 | ); 116 | path = CellLayoutTests; 117 | sourceTree = ""; 118 | }; 119 | 0B94A2851B193651005B33E0 /* Supporting Files */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 0B94A2861B193651005B33E0 /* Info.plist */, 123 | ); 124 | name = "Supporting Files"; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | 0B94A26B1B193651005B33E0 /* CellLayout */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = 0B94A28B1B193651005B33E0 /* Build configuration list for PBXNativeTarget "CellLayout" */; 133 | buildPhases = ( 134 | 0B94A2681B193651005B33E0 /* Sources */, 135 | 0B94A2691B193651005B33E0 /* Frameworks */, 136 | 0B94A26A1B193651005B33E0 /* Resources */, 137 | ); 138 | buildRules = ( 139 | ); 140 | dependencies = ( 141 | ); 142 | name = CellLayout; 143 | productName = CellLayout; 144 | productReference = 0B94A26C1B193651005B33E0 /* CellLayout.app */; 145 | productType = "com.apple.product-type.application"; 146 | }; 147 | 0B94A2801B193651005B33E0 /* CellLayoutTests */ = { 148 | isa = PBXNativeTarget; 149 | buildConfigurationList = 0B94A28E1B193651005B33E0 /* Build configuration list for PBXNativeTarget "CellLayoutTests" */; 150 | buildPhases = ( 151 | 0B94A27D1B193651005B33E0 /* Sources */, 152 | 0B94A27E1B193651005B33E0 /* Frameworks */, 153 | 0B94A27F1B193651005B33E0 /* Resources */, 154 | ); 155 | buildRules = ( 156 | ); 157 | dependencies = ( 158 | 0B94A2831B193651005B33E0 /* PBXTargetDependency */, 159 | ); 160 | name = CellLayoutTests; 161 | productName = CellLayoutTests; 162 | productReference = 0B94A2811B193651005B33E0 /* CellLayoutTests.xctest */; 163 | productType = "com.apple.product-type.bundle.unit-test"; 164 | }; 165 | /* End PBXNativeTarget section */ 166 | 167 | /* Begin PBXProject section */ 168 | 0B94A2641B193651005B33E0 /* Project object */ = { 169 | isa = PBXProject; 170 | attributes = { 171 | LastUpgradeCheck = 0630; 172 | ORGANIZATIONNAME = Example; 173 | TargetAttributes = { 174 | 0B94A26B1B193651005B33E0 = { 175 | CreatedOnToolsVersion = 6.3.2; 176 | }; 177 | 0B94A2801B193651005B33E0 = { 178 | CreatedOnToolsVersion = 6.3.2; 179 | TestTargetID = 0B94A26B1B193651005B33E0; 180 | }; 181 | }; 182 | }; 183 | buildConfigurationList = 0B94A2671B193651005B33E0 /* Build configuration list for PBXProject "CellLayout" */; 184 | compatibilityVersion = "Xcode 3.2"; 185 | developmentRegion = English; 186 | hasScannedForEncodings = 0; 187 | knownRegions = ( 188 | en, 189 | Base, 190 | ); 191 | mainGroup = 0B94A2631B193651005B33E0; 192 | productRefGroup = 0B94A26D1B193651005B33E0 /* Products */; 193 | projectDirPath = ""; 194 | projectRoot = ""; 195 | targets = ( 196 | 0B94A26B1B193651005B33E0 /* CellLayout */, 197 | 0B94A2801B193651005B33E0 /* CellLayoutTests */, 198 | ); 199 | }; 200 | /* End PBXProject section */ 201 | 202 | /* Begin PBXResourcesBuildPhase section */ 203 | 0B94A26A1B193651005B33E0 /* Resources */ = { 204 | isa = PBXResourcesBuildPhase; 205 | buildActionMask = 2147483647; 206 | files = ( 207 | 0B94A2921B1938EF005B33E0 /* ContentTableCell.xib in Resources */, 208 | 0B94A2791B193651005B33E0 /* Images.xcassets in Resources */, 209 | ); 210 | runOnlyForDeploymentPostprocessing = 0; 211 | }; 212 | 0B94A27F1B193651005B33E0 /* Resources */ = { 213 | isa = PBXResourcesBuildPhase; 214 | buildActionMask = 2147483647; 215 | files = ( 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | }; 219 | /* End PBXResourcesBuildPhase section */ 220 | 221 | /* Begin PBXSourcesBuildPhase section */ 222 | 0B94A2681B193651005B33E0 /* Sources */ = { 223 | isa = PBXSourcesBuildPhase; 224 | buildActionMask = 2147483647; 225 | files = ( 226 | 0BFA3D5E1B2B736500A30556 /* UIDeviceExtension.swift in Sources */, 227 | 0B94A2941B193908005B33E0 /* ContentTableCell.swift in Sources */, 228 | 0B94A2741B193651005B33E0 /* TableViewController.swift in Sources */, 229 | 0BE5CE781B2BBF1800242C99 /* UITableViewCellExtension.swift in Sources */, 230 | 0BE5CE761B2B963000242C99 /* Label.swift in Sources */, 231 | 0B94A2721B193651005B33E0 /* AppDelegate.swift in Sources */, 232 | 0BE5CE7A1B2BC51500242C99 /* UITableViewExtension.swift in Sources */, 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | 0B94A27D1B193651005B33E0 /* Sources */ = { 237 | isa = PBXSourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | 0B94A2881B193651005B33E0 /* CellLayoutTests.swift in Sources */, 241 | ); 242 | runOnlyForDeploymentPostprocessing = 0; 243 | }; 244 | /* End PBXSourcesBuildPhase section */ 245 | 246 | /* Begin PBXTargetDependency section */ 247 | 0B94A2831B193651005B33E0 /* PBXTargetDependency */ = { 248 | isa = PBXTargetDependency; 249 | target = 0B94A26B1B193651005B33E0 /* CellLayout */; 250 | targetProxy = 0B94A2821B193651005B33E0 /* PBXContainerItemProxy */; 251 | }; 252 | /* End PBXTargetDependency section */ 253 | 254 | /* Begin XCBuildConfiguration section */ 255 | 0B94A2891B193651005B33E0 /* Debug */ = { 256 | isa = XCBuildConfiguration; 257 | buildSettings = { 258 | ALWAYS_SEARCH_USER_PATHS = NO; 259 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 260 | CLANG_CXX_LIBRARY = "libc++"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_CONSTANT_CONVERSION = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 266 | CLANG_WARN_EMPTY_BODY = YES; 267 | CLANG_WARN_ENUM_CONVERSION = YES; 268 | CLANG_WARN_INT_CONVERSION = YES; 269 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 270 | CLANG_WARN_UNREACHABLE_CODE = YES; 271 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 272 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 273 | COPY_PHASE_STRIP = NO; 274 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 275 | ENABLE_STRICT_OBJC_MSGSEND = YES; 276 | GCC_C_LANGUAGE_STANDARD = gnu99; 277 | GCC_DYNAMIC_NO_PIC = NO; 278 | GCC_NO_COMMON_BLOCKS = YES; 279 | GCC_OPTIMIZATION_LEVEL = 0; 280 | GCC_PREPROCESSOR_DEFINITIONS = ( 281 | "DEBUG=1", 282 | "$(inherited)", 283 | ); 284 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 285 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 286 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 287 | GCC_WARN_UNDECLARED_SELECTOR = YES; 288 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 289 | GCC_WARN_UNUSED_FUNCTION = YES; 290 | GCC_WARN_UNUSED_VARIABLE = YES; 291 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 292 | MTL_ENABLE_DEBUG_INFO = YES; 293 | ONLY_ACTIVE_ARCH = YES; 294 | SDKROOT = iphoneos; 295 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 296 | TARGETED_DEVICE_FAMILY = "1,2"; 297 | }; 298 | name = Debug; 299 | }; 300 | 0B94A28A1B193651005B33E0 /* Release */ = { 301 | isa = XCBuildConfiguration; 302 | buildSettings = { 303 | ALWAYS_SEARCH_USER_PATHS = NO; 304 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 305 | CLANG_CXX_LIBRARY = "libc++"; 306 | CLANG_ENABLE_MODULES = YES; 307 | CLANG_ENABLE_OBJC_ARC = YES; 308 | CLANG_WARN_BOOL_CONVERSION = YES; 309 | CLANG_WARN_CONSTANT_CONVERSION = YES; 310 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 311 | CLANG_WARN_EMPTY_BODY = YES; 312 | CLANG_WARN_ENUM_CONVERSION = YES; 313 | CLANG_WARN_INT_CONVERSION = YES; 314 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 315 | CLANG_WARN_UNREACHABLE_CODE = YES; 316 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 317 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 318 | COPY_PHASE_STRIP = NO; 319 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 320 | ENABLE_NS_ASSERTIONS = NO; 321 | ENABLE_STRICT_OBJC_MSGSEND = YES; 322 | GCC_C_LANGUAGE_STANDARD = gnu99; 323 | GCC_NO_COMMON_BLOCKS = YES; 324 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 325 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 326 | GCC_WARN_UNDECLARED_SELECTOR = YES; 327 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 328 | GCC_WARN_UNUSED_FUNCTION = YES; 329 | GCC_WARN_UNUSED_VARIABLE = YES; 330 | IPHONEOS_DEPLOYMENT_TARGET = 7.1; 331 | MTL_ENABLE_DEBUG_INFO = NO; 332 | SDKROOT = iphoneos; 333 | TARGETED_DEVICE_FAMILY = "1,2"; 334 | VALIDATE_PRODUCT = YES; 335 | }; 336 | name = Release; 337 | }; 338 | 0B94A28C1B193651005B33E0 /* Debug */ = { 339 | isa = XCBuildConfiguration; 340 | buildSettings = { 341 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 342 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 343 | INFOPLIST_FILE = CellLayout/Info.plist; 344 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 345 | PRODUCT_NAME = "$(TARGET_NAME)"; 346 | }; 347 | name = Debug; 348 | }; 349 | 0B94A28D1B193651005B33E0 /* Release */ = { 350 | isa = XCBuildConfiguration; 351 | buildSettings = { 352 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 353 | ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; 354 | INFOPLIST_FILE = CellLayout/Info.plist; 355 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 356 | PRODUCT_NAME = "$(TARGET_NAME)"; 357 | }; 358 | name = Release; 359 | }; 360 | 0B94A28F1B193651005B33E0 /* Debug */ = { 361 | isa = XCBuildConfiguration; 362 | buildSettings = { 363 | BUNDLE_LOADER = "$(TEST_HOST)"; 364 | FRAMEWORK_SEARCH_PATHS = ( 365 | "$(SDKROOT)/Developer/Library/Frameworks", 366 | "$(inherited)", 367 | ); 368 | GCC_PREPROCESSOR_DEFINITIONS = ( 369 | "DEBUG=1", 370 | "$(inherited)", 371 | ); 372 | INFOPLIST_FILE = CellLayoutTests/Info.plist; 373 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 374 | PRODUCT_NAME = "$(TARGET_NAME)"; 375 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CellLayout.app/CellLayout"; 376 | }; 377 | name = Debug; 378 | }; 379 | 0B94A2901B193651005B33E0 /* Release */ = { 380 | isa = XCBuildConfiguration; 381 | buildSettings = { 382 | BUNDLE_LOADER = "$(TEST_HOST)"; 383 | FRAMEWORK_SEARCH_PATHS = ( 384 | "$(SDKROOT)/Developer/Library/Frameworks", 385 | "$(inherited)", 386 | ); 387 | INFOPLIST_FILE = CellLayoutTests/Info.plist; 388 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 389 | PRODUCT_NAME = "$(TARGET_NAME)"; 390 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/CellLayout.app/CellLayout"; 391 | }; 392 | name = Release; 393 | }; 394 | /* End XCBuildConfiguration section */ 395 | 396 | /* Begin XCConfigurationList section */ 397 | 0B94A2671B193651005B33E0 /* Build configuration list for PBXProject "CellLayout" */ = { 398 | isa = XCConfigurationList; 399 | buildConfigurations = ( 400 | 0B94A2891B193651005B33E0 /* Debug */, 401 | 0B94A28A1B193651005B33E0 /* Release */, 402 | ); 403 | defaultConfigurationIsVisible = 0; 404 | defaultConfigurationName = Release; 405 | }; 406 | 0B94A28B1B193651005B33E0 /* Build configuration list for PBXNativeTarget "CellLayout" */ = { 407 | isa = XCConfigurationList; 408 | buildConfigurations = ( 409 | 0B94A28C1B193651005B33E0 /* Debug */, 410 | 0B94A28D1B193651005B33E0 /* Release */, 411 | ); 412 | defaultConfigurationIsVisible = 0; 413 | defaultConfigurationName = Release; 414 | }; 415 | 0B94A28E1B193651005B33E0 /* Build configuration list for PBXNativeTarget "CellLayoutTests" */ = { 416 | isa = XCConfigurationList; 417 | buildConfigurations = ( 418 | 0B94A28F1B193651005B33E0 /* Debug */, 419 | 0B94A2901B193651005B33E0 /* Release */, 420 | ); 421 | defaultConfigurationIsVisible = 0; 422 | defaultConfigurationName = Release; 423 | }; 424 | /* End XCConfigurationList section */ 425 | }; 426 | rootObject = 0B94A2641B193651005B33E0 /* Project object */; 427 | } 428 | -------------------------------------------------------------------------------- /CellLayout.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CellLayout/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nick Snyder on 5/29/15. 3 | // Copyright (c) 2015 Example. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | @UIApplicationMain 9 | class AppDelegate: UIResponder, UIApplicationDelegate { 10 | 11 | var window: UIWindow? 12 | 13 | func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { 14 | self.window = UIWindow(frame: UIScreen.mainScreen().bounds) 15 | self.window?.backgroundColor = UIColor.redColor() 16 | self.window?.rootViewController = UINavigationController(rootViewController: TableViewController()) 17 | self.window?.makeKeyAndVisible() 18 | return true 19 | } 20 | } -------------------------------------------------------------------------------- /CellLayout/ContentTableCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nick Snyder on 5/29/15. 3 | // Copyright (c) 2015 Example. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | class ContentTableCell: UITableViewCell { 9 | @IBOutlet private var titleLabel: UILabel! 10 | @IBOutlet private var bodyLabel: UILabel! 11 | 12 | private static let nibName = "ContentTableCell" 13 | 14 | static let sharedCell: ContentTableCell = NSBundle.mainBundle().loadNibNamed(nibName, owner: nil, options: nil).first as! ContentTableCell 15 | 16 | class func registerForTableView(tableView: UITableView) { 17 | tableView.registerNib(UINib(nibName: nibName, bundle: nil), forCellReuseIdentifier: nibName) 18 | } 19 | 20 | class func dequeueForTableView(tableView: UITableView, indexPath: NSIndexPath) -> ContentTableCell { 21 | return tableView.dequeueReusableCellWithIdentifier(nibName, forIndexPath: indexPath) as! ContentTableCell 22 | } 23 | 24 | func bind(content: Content) -> Self { 25 | titleLabel.text = content.title 26 | bodyLabel.text = content.body 27 | return self 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CellLayout/ContentTableCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | 32 | 33 | 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 | -------------------------------------------------------------------------------- /CellLayout/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 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /CellLayout/Images.xcassets/LaunchImage.launchimage/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "orientation" : "portrait", 5 | "idiom" : "iphone", 6 | "extent" : "full-screen", 7 | "minimum-system-version" : "8.0", 8 | "subtype" : "736h", 9 | "scale" : "3x" 10 | }, 11 | { 12 | "orientation" : "landscape", 13 | "idiom" : "iphone", 14 | "extent" : "full-screen", 15 | "minimum-system-version" : "8.0", 16 | "subtype" : "736h", 17 | "scale" : "3x" 18 | }, 19 | { 20 | "orientation" : "portrait", 21 | "idiom" : "iphone", 22 | "extent" : "full-screen", 23 | "minimum-system-version" : "8.0", 24 | "subtype" : "667h", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "orientation" : "portrait", 29 | "idiom" : "iphone", 30 | "extent" : "full-screen", 31 | "minimum-system-version" : "7.0", 32 | "scale" : "2x" 33 | }, 34 | { 35 | "orientation" : "portrait", 36 | "idiom" : "iphone", 37 | "extent" : "full-screen", 38 | "minimum-system-version" : "7.0", 39 | "subtype" : "retina4", 40 | "scale" : "2x" 41 | }, 42 | { 43 | "orientation" : "portrait", 44 | "idiom" : "ipad", 45 | "extent" : "full-screen", 46 | "minimum-system-version" : "7.0", 47 | "scale" : "1x" 48 | }, 49 | { 50 | "orientation" : "landscape", 51 | "idiom" : "ipad", 52 | "extent" : "full-screen", 53 | "minimum-system-version" : "7.0", 54 | "scale" : "1x" 55 | }, 56 | { 57 | "orientation" : "portrait", 58 | "idiom" : "ipad", 59 | "extent" : "full-screen", 60 | "minimum-system-version" : "7.0", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "orientation" : "landscape", 65 | "idiom" : "ipad", 66 | "extent" : "full-screen", 67 | "minimum-system-version" : "7.0", 68 | "scale" : "2x" 69 | } 70 | ], 71 | "info" : { 72 | "version" : 1, 73 | "author" : "xcode" 74 | } 75 | } -------------------------------------------------------------------------------- /CellLayout/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.example.$(PRODUCT_NAME:rfc1034identifier) 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 | UIRequiredDeviceCapabilities 26 | 27 | armv7 28 | 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /CellLayout/Label.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nick Snyder on 6/12/15. 3 | // Copyright (c) 2015 Example. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | /// Label updates preferredMaxLayoutWidth any time its bounds or frame changes. 9 | /// You do not need to use Label if you are targeting iOS 8+ (use UILabel instead). 10 | /// 11 | /// If you are creating a Label or UILabel in a .xib, make sure to 12 | /// leave the explicit preferred width box UNCHECKED no matter what platforms you are targeting. 13 | /// This will cause a warning if you are targeting iOS 7, but that is fine because in that case 14 | /// you should be using this Label class which manages the preferredMaxLayoutWidth for you. 15 | /// 16 | /// I tried putting this logic in a UILabel extension, but it didn't work (unsure why). 17 | class Label: UILabel { 18 | 19 | override var bounds: CGRect { 20 | didSet { 21 | updatePreferredMaxLayoutWidth() 22 | } 23 | } 24 | 25 | override var frame: CGRect { 26 | didSet { 27 | updatePreferredMaxLayoutWidth() 28 | } 29 | } 30 | 31 | private func updatePreferredMaxLayoutWidth() { 32 | // Rely on automatic preferred max layout with in iOS 8+ 33 | if (preferredMaxLayoutWidth != bounds.width && !UIDevice.hasMinimumSystemVersion("8.0")) { 34 | NSLog("updating preferredMaxLayoutWidth from \(preferredMaxLayoutWidth) to \(bounds.width)") 35 | preferredMaxLayoutWidth = bounds.width 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CellLayout/TableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nick Snyder on 5/29/15. 3 | // Copyright (c) 2015 Example. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | class TableViewController: UITableViewController { 9 | 10 | static let oneLine = "one one one one one one one" 11 | static let twoLines = "two two two two two two two two two two two two two two" 12 | static let threeLines = "three three three three three three three three three three three three three three three" 13 | 14 | static let twoLinesIpad = "two ipad two ipad two ipad two ipad two ipad two ipad two ipad two ipad two ipad two ipad two ipad two ipad two ipad two ipad two ipad two ipad two ipad" 15 | static let threeLinesIpad = "three ipad three ipad three ipad three ipad three ipad three ipad three ipad three ipad three ipad three ipad three ipad three ipad three ipad three ipad three ipad three ipad three ipad" 16 | 17 | let contents = [ 18 | 19 | // iPhone tests 20 | 21 | Content(title: oneLine, body: oneLine), 22 | Content(title: oneLine, body: twoLines), 23 | Content(title: oneLine, body: threeLines), 24 | 25 | Content(title: twoLines, body: oneLine), 26 | Content(title: twoLines, body: twoLines), 27 | Content(title: twoLines, body: threeLines), 28 | 29 | Content(title: threeLines, body: oneLine), 30 | Content(title: threeLines, body: twoLines), 31 | Content(title: threeLines, body: threeLines), 32 | 33 | // iPad tests 34 | 35 | Content(title: oneLine, body: oneLine), 36 | Content(title: oneLine, body: twoLinesIpad), 37 | Content(title: oneLine, body: threeLinesIpad), 38 | 39 | Content(title: twoLinesIpad, body: oneLine), 40 | Content(title: twoLinesIpad, body: twoLinesIpad), 41 | Content(title: twoLinesIpad, body: threeLinesIpad), 42 | 43 | Content(title: threeLinesIpad, body: oneLine), 44 | Content(title: threeLinesIpad, body: twoLinesIpad), 45 | Content(title: threeLinesIpad, body: threeLinesIpad), 46 | ] 47 | 48 | override func viewDidLoad() { 49 | super.viewDidLoad() 50 | ContentTableCell.registerForTableView(tableView) 51 | tableView.separatorStyle = UITableViewCellSeparatorStyle.SingleLine 52 | 53 | // It is necessary to provide an estimated row height for automatic table cell sizing to work in iOS 8+ 54 | tableView.estimatedRowHeight = 100 55 | } 56 | 57 | // MARK - UITableViewDataSource 58 | 59 | override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 60 | return contents.count; 61 | } 62 | 63 | override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { 64 | NSLog("cellForRowAtIndexPath \(indexPath.item)") 65 | let content = contents[indexPath.item] 66 | let cell = ContentTableCell.dequeueForTableView(tableView, indexPath: indexPath) 67 | cell.bind(content) 68 | if (UIDevice.hasMajorSystemVersion(8)) { 69 | // There is a bug in iOS 8 automatic cell height that causes layout to be incorrect in certain cases. 70 | // This bug is fixed in iOS 9 (as of WWDC 2015 Xcode 7 beta build 7a120f). 71 | cell.layoutIfNeeded() 72 | } 73 | return cell 74 | } 75 | 76 | override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { 77 | if (UIDevice.hasMinimumSystemVersion("8.0")) { 78 | return UITableViewAutomaticDimension 79 | } 80 | let content = contents[indexPath.item] 81 | let height = ContentTableCell.sharedCell.bind(content).heightInTableView(tableView) 82 | NSLog("heightForRowAtIndexPath \(indexPath.item) height \(height)") 83 | return height 84 | } 85 | } 86 | 87 | struct Content { 88 | let title: String 89 | let body: String 90 | } -------------------------------------------------------------------------------- /CellLayout/UIDeviceExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nick Snyder on 6/12/15. 3 | // Copyright (c) 2015 Example. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | extension UIDevice { 9 | class func hasMinimumSystemVersion(version: String) -> Bool { 10 | let systemVersion = UIDevice.currentDevice().systemVersion 11 | let compareResult = systemVersion.compare(version, options: .NumericSearch) 12 | return compareResult == .OrderedSame || compareResult == .OrderedDescending 13 | } 14 | 15 | class func hasMajorSystemVersion(version: Int) -> Bool { 16 | let systemVersion = UIDevice.currentDevice().systemVersion 17 | if let majorVersion = systemVersion.substringToIndex(advance(systemVersion.startIndex, 1)).toInt() { 18 | return version == majorVersion 19 | } 20 | return false 21 | } 22 | } -------------------------------------------------------------------------------- /CellLayout/UITableViewCellExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nick Snyder on 6/12/15. 3 | // Copyright (c) 2015 Example. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | extension UITableViewCell { 9 | 10 | // This method only needs to be used on iOS 7. 11 | // On iOS 8+ use automatic row heights. 12 | func heightInTableView(tableView: UITableView) -> CGFloat { 13 | let width = tableView.bounds.width 14 | 15 | // Give the cell the correct width and perform a layout pass. 16 | // The layout pass will give UILabels the correct width. 17 | // Since we have subclassed UILabel, the preferredMaxLayoutWith will get set during the layout pass. 18 | bounds = CGRect(origin: CGPointZero, size: CGSize(width: width, height: 0)) 19 | setNeedsLayout() 20 | layoutIfNeeded() 21 | 22 | // Now that our Labels have the correct preferredMaxLayoutWidth, we can run autolayout to get the correct size. 23 | let size = contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) 24 | 25 | // The separator height isn't included in the size of the content view so we have to account for that here. 26 | let separatorHeightAdjustment: CGFloat = tableView.separatorStyle == .None ? 0 : 0.5 27 | return size.height + separatorHeightAdjustment 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /CellLayout/UITableViewExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Nick Snyder on 6/12/15. 3 | // Copyright (c) 2015 Example. All rights reserved. 4 | // 5 | 6 | import UIKit 7 | 8 | extension UITableView { 9 | public override func layoutMarginsDidChange() { 10 | super.layoutMarginsDidChange() 11 | if UIDevice.hasMajorSystemVersion(8) { 12 | // on iPad iOS 8.x if default layout margins are used (8, 8, 8, 8) the horizontal layout margin components 13 | // get 7px added to each side AFTER the table cell heights are calculated. 14 | // 15 | // This is a problem because the cell layout has less width than it expected (14px to be exact) 16 | // so its actual height may be greater than the table view thinks it will be. 17 | // 18 | // Any time the layout margins change, we have to force a layout. 19 | // This bug is fixed in iOS 9 (as of WWDC 2015 Xcode 7 beta build 7a120f). 20 | NSLog("performing manual layout after layoutMargins changed") 21 | layoutIfNeeded() 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CellLayoutTests/CellLayoutTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CellLayoutTests.swift 3 | // CellLayoutTests 4 | // 5 | // Created by Nick Snyder on 5/29/15. 6 | // Copyright (c) 2015 Example. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | import XCTest 11 | 12 | class CellLayoutTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | XCTAssert(true, "Pass") 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measureBlock() { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /CellLayoutTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.example.$(PRODUCT_NAME:rfc1034identifier) 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 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Nick Snyder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## About 2 | 3 | How to correcly compute dynamic UITableViewCell heights on iOS 7,8,9. 4 | 5 | - On iOS 7 heights are computed by manually running auto layout. 6 | - On iOS 8 heights are automatically computed by the table view with some workarounds for bugs in UIKit. 7 | - On iOS 9 heights are automatically computed by the table view. 8 | 9 | ## Requirements 10 | 11 | Xcode 6.3.2 12 | 13 | ## Tested 14 | 15 | iPhone and iPad simulator (iOS 7.1, 8.1, 8.2, 8.3, 9.0) 16 | --------------------------------------------------------------------------------