├── DynamicHorizontalScrollingShelves.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── charleschandler.xcuserdatad │ ├── xcdebugger │ └── Breakpoints_v2.xcbkptlist │ └── xcschemes │ └── xcschememanagement.plist └── DynamicHorizontalScrollingShelves ├── AppDelegate.swift ├── Assets.xcassets └── AppIcon.appiconset │ └── Contents.json ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard ├── Constants.swift ├── DynamicHeightCalculable.swift ├── ExampleCell.swift ├── ExampleCell.xib ├── ExampleData.swift ├── ExampleModel.swift ├── ExampleViewModel.swift ├── Info.plist └── ViewController.swift /DynamicHorizontalScrollingShelves.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 089A6A54203B1C4A0028525F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089A6A53203B1C4A0028525F /* AppDelegate.swift */; }; 11 | 089A6A56203B1C4A0028525F /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089A6A55203B1C4A0028525F /* ViewController.swift */; }; 12 | 089A6A59203B1C4A0028525F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 089A6A57203B1C4A0028525F /* Main.storyboard */; }; 13 | 089A6A5B203B1C4A0028525F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 089A6A5A203B1C4A0028525F /* Assets.xcassets */; }; 14 | 089A6A5E203B1C4A0028525F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 089A6A5C203B1C4A0028525F /* LaunchScreen.storyboard */; }; 15 | 089A6A66203B1CDF0028525F /* ExampleCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = 089A6A65203B1CDF0028525F /* ExampleCell.xib */; }; 16 | 089A6A68203B1CEC0028525F /* ExampleCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089A6A67203B1CEC0028525F /* ExampleCell.swift */; }; 17 | 089A6A6A203B1D350028525F /* ExampleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089A6A69203B1D350028525F /* ExampleModel.swift */; }; 18 | 089A6A6C203B1FA50028525F /* ExampleViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089A6A6B203B1FA50028525F /* ExampleViewModel.swift */; }; 19 | 089A6A6E203B208B0028525F /* ExampleData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089A6A6D203B208B0028525F /* ExampleData.swift */; }; 20 | 089A6A70203B252A0028525F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089A6A6F203B252A0028525F /* Constants.swift */; }; 21 | 089A6A72203B36F50028525F /* DynamicHeightCalculable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 089A6A71203B36F50028525F /* DynamicHeightCalculable.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXFileReference section */ 25 | 089A6A50203B1C4A0028525F /* DynamicHorizontalScrollingShelves.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DynamicHorizontalScrollingShelves.app; sourceTree = BUILT_PRODUCTS_DIR; }; 26 | 089A6A53203B1C4A0028525F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 27 | 089A6A55203B1C4A0028525F /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 28 | 089A6A58203B1C4A0028525F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 29 | 089A6A5A203B1C4A0028525F /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 30 | 089A6A5D203B1C4A0028525F /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 31 | 089A6A5F203B1C4A0028525F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 32 | 089A6A65203B1CDF0028525F /* ExampleCell.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExampleCell.xib; sourceTree = ""; }; 33 | 089A6A67203B1CEC0028525F /* ExampleCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleCell.swift; sourceTree = ""; }; 34 | 089A6A69203B1D350028525F /* ExampleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleModel.swift; sourceTree = ""; }; 35 | 089A6A6B203B1FA50028525F /* ExampleViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleViewModel.swift; sourceTree = ""; }; 36 | 089A6A6D203B208B0028525F /* ExampleData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleData.swift; sourceTree = ""; }; 37 | 089A6A6F203B252A0028525F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; 38 | 089A6A71203B36F50028525F /* DynamicHeightCalculable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicHeightCalculable.swift; sourceTree = ""; }; 39 | /* End PBXFileReference section */ 40 | 41 | /* Begin PBXFrameworksBuildPhase section */ 42 | 089A6A4D203B1C4A0028525F /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | ); 47 | runOnlyForDeploymentPostprocessing = 0; 48 | }; 49 | /* End PBXFrameworksBuildPhase section */ 50 | 51 | /* Begin PBXGroup section */ 52 | 089A6A47203B1C4A0028525F = { 53 | isa = PBXGroup; 54 | children = ( 55 | 089A6A52203B1C4A0028525F /* DynamicHorizontalScrollingShelves */, 56 | 089A6A51203B1C4A0028525F /* Products */, 57 | ); 58 | sourceTree = ""; 59 | }; 60 | 089A6A51203B1C4A0028525F /* Products */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | 089A6A50203B1C4A0028525F /* DynamicHorizontalScrollingShelves.app */, 64 | ); 65 | name = Products; 66 | sourceTree = ""; 67 | }; 68 | 089A6A52203B1C4A0028525F /* DynamicHorizontalScrollingShelves */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | 089A6A53203B1C4A0028525F /* AppDelegate.swift */, 72 | 089A6A55203B1C4A0028525F /* ViewController.swift */, 73 | 089A6A57203B1C4A0028525F /* Main.storyboard */, 74 | 089A6A5A203B1C4A0028525F /* Assets.xcassets */, 75 | 089A6A5C203B1C4A0028525F /* LaunchScreen.storyboard */, 76 | 089A6A5F203B1C4A0028525F /* Info.plist */, 77 | 089A6A65203B1CDF0028525F /* ExampleCell.xib */, 78 | 089A6A67203B1CEC0028525F /* ExampleCell.swift */, 79 | 089A6A69203B1D350028525F /* ExampleModel.swift */, 80 | 089A6A6B203B1FA50028525F /* ExampleViewModel.swift */, 81 | 089A6A6D203B208B0028525F /* ExampleData.swift */, 82 | 089A6A6F203B252A0028525F /* Constants.swift */, 83 | 089A6A71203B36F50028525F /* DynamicHeightCalculable.swift */, 84 | ); 85 | path = DynamicHorizontalScrollingShelves; 86 | sourceTree = ""; 87 | }; 88 | /* End PBXGroup section */ 89 | 90 | /* Begin PBXNativeTarget section */ 91 | 089A6A4F203B1C4A0028525F /* DynamicHorizontalScrollingShelves */ = { 92 | isa = PBXNativeTarget; 93 | buildConfigurationList = 089A6A62203B1C4A0028525F /* Build configuration list for PBXNativeTarget "DynamicHorizontalScrollingShelves" */; 94 | buildPhases = ( 95 | 089A6A4C203B1C4A0028525F /* Sources */, 96 | 089A6A4D203B1C4A0028525F /* Frameworks */, 97 | 089A6A4E203B1C4A0028525F /* Resources */, 98 | ); 99 | buildRules = ( 100 | ); 101 | dependencies = ( 102 | ); 103 | name = DynamicHorizontalScrollingShelves; 104 | productName = DynamicHorizontalScrollingShelves; 105 | productReference = 089A6A50203B1C4A0028525F /* DynamicHorizontalScrollingShelves.app */; 106 | productType = "com.apple.product-type.application"; 107 | }; 108 | /* End PBXNativeTarget section */ 109 | 110 | /* Begin PBXProject section */ 111 | 089A6A48203B1C4A0028525F /* Project object */ = { 112 | isa = PBXProject; 113 | attributes = { 114 | LastSwiftUpdateCheck = 0920; 115 | LastUpgradeCheck = 0920; 116 | ORGANIZATIONNAME = "Charles Chandler"; 117 | TargetAttributes = { 118 | 089A6A4F203B1C4A0028525F = { 119 | CreatedOnToolsVersion = 9.2; 120 | ProvisioningStyle = Automatic; 121 | }; 122 | }; 123 | }; 124 | buildConfigurationList = 089A6A4B203B1C4A0028525F /* Build configuration list for PBXProject "DynamicHorizontalScrollingShelves" */; 125 | compatibilityVersion = "Xcode 8.0"; 126 | developmentRegion = en; 127 | hasScannedForEncodings = 0; 128 | knownRegions = ( 129 | en, 130 | Base, 131 | ); 132 | mainGroup = 089A6A47203B1C4A0028525F; 133 | productRefGroup = 089A6A51203B1C4A0028525F /* Products */; 134 | projectDirPath = ""; 135 | projectRoot = ""; 136 | targets = ( 137 | 089A6A4F203B1C4A0028525F /* DynamicHorizontalScrollingShelves */, 138 | ); 139 | }; 140 | /* End PBXProject section */ 141 | 142 | /* Begin PBXResourcesBuildPhase section */ 143 | 089A6A4E203B1C4A0028525F /* Resources */ = { 144 | isa = PBXResourcesBuildPhase; 145 | buildActionMask = 2147483647; 146 | files = ( 147 | 089A6A66203B1CDF0028525F /* ExampleCell.xib in Resources */, 148 | 089A6A5E203B1C4A0028525F /* LaunchScreen.storyboard in Resources */, 149 | 089A6A5B203B1C4A0028525F /* Assets.xcassets in Resources */, 150 | 089A6A59203B1C4A0028525F /* Main.storyboard in Resources */, 151 | ); 152 | runOnlyForDeploymentPostprocessing = 0; 153 | }; 154 | /* End PBXResourcesBuildPhase section */ 155 | 156 | /* Begin PBXSourcesBuildPhase section */ 157 | 089A6A4C203B1C4A0028525F /* Sources */ = { 158 | isa = PBXSourcesBuildPhase; 159 | buildActionMask = 2147483647; 160 | files = ( 161 | 089A6A6C203B1FA50028525F /* ExampleViewModel.swift in Sources */, 162 | 089A6A6A203B1D350028525F /* ExampleModel.swift in Sources */, 163 | 089A6A6E203B208B0028525F /* ExampleData.swift in Sources */, 164 | 089A6A68203B1CEC0028525F /* ExampleCell.swift in Sources */, 165 | 089A6A70203B252A0028525F /* Constants.swift in Sources */, 166 | 089A6A56203B1C4A0028525F /* ViewController.swift in Sources */, 167 | 089A6A72203B36F50028525F /* DynamicHeightCalculable.swift in Sources */, 168 | 089A6A54203B1C4A0028525F /* AppDelegate.swift in Sources */, 169 | ); 170 | runOnlyForDeploymentPostprocessing = 0; 171 | }; 172 | /* End PBXSourcesBuildPhase section */ 173 | 174 | /* Begin PBXVariantGroup section */ 175 | 089A6A57203B1C4A0028525F /* Main.storyboard */ = { 176 | isa = PBXVariantGroup; 177 | children = ( 178 | 089A6A58203B1C4A0028525F /* Base */, 179 | ); 180 | name = Main.storyboard; 181 | sourceTree = ""; 182 | }; 183 | 089A6A5C203B1C4A0028525F /* LaunchScreen.storyboard */ = { 184 | isa = PBXVariantGroup; 185 | children = ( 186 | 089A6A5D203B1C4A0028525F /* Base */, 187 | ); 188 | name = LaunchScreen.storyboard; 189 | sourceTree = ""; 190 | }; 191 | /* End PBXVariantGroup section */ 192 | 193 | /* Begin XCBuildConfiguration section */ 194 | 089A6A60203B1C4A0028525F /* Debug */ = { 195 | isa = XCBuildConfiguration; 196 | buildSettings = { 197 | ALWAYS_SEARCH_USER_PATHS = NO; 198 | CLANG_ANALYZER_NONNULL = YES; 199 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 200 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 201 | CLANG_CXX_LIBRARY = "libc++"; 202 | CLANG_ENABLE_MODULES = YES; 203 | CLANG_ENABLE_OBJC_ARC = YES; 204 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 205 | CLANG_WARN_BOOL_CONVERSION = YES; 206 | CLANG_WARN_COMMA = YES; 207 | CLANG_WARN_CONSTANT_CONVERSION = YES; 208 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 209 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 210 | CLANG_WARN_EMPTY_BODY = YES; 211 | CLANG_WARN_ENUM_CONVERSION = YES; 212 | CLANG_WARN_INFINITE_RECURSION = YES; 213 | CLANG_WARN_INT_CONVERSION = YES; 214 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 215 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 216 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 217 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 218 | CLANG_WARN_STRICT_PROTOTYPES = YES; 219 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 220 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 221 | CLANG_WARN_UNREACHABLE_CODE = YES; 222 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 223 | CODE_SIGN_IDENTITY = "iPhone Developer"; 224 | COPY_PHASE_STRIP = NO; 225 | DEBUG_INFORMATION_FORMAT = dwarf; 226 | ENABLE_STRICT_OBJC_MSGSEND = YES; 227 | ENABLE_TESTABILITY = YES; 228 | GCC_C_LANGUAGE_STANDARD = gnu11; 229 | GCC_DYNAMIC_NO_PIC = NO; 230 | GCC_NO_COMMON_BLOCKS = YES; 231 | GCC_OPTIMIZATION_LEVEL = 0; 232 | GCC_PREPROCESSOR_DEFINITIONS = ( 233 | "DEBUG=1", 234 | "$(inherited)", 235 | ); 236 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 237 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 238 | GCC_WARN_UNDECLARED_SELECTOR = YES; 239 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 240 | GCC_WARN_UNUSED_FUNCTION = YES; 241 | GCC_WARN_UNUSED_VARIABLE = YES; 242 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 243 | MTL_ENABLE_DEBUG_INFO = YES; 244 | ONLY_ACTIVE_ARCH = YES; 245 | SDKROOT = iphoneos; 246 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 247 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 248 | }; 249 | name = Debug; 250 | }; 251 | 089A6A61203B1C4A0028525F /* Release */ = { 252 | isa = XCBuildConfiguration; 253 | buildSettings = { 254 | ALWAYS_SEARCH_USER_PATHS = NO; 255 | CLANG_ANALYZER_NONNULL = YES; 256 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 257 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 258 | CLANG_CXX_LIBRARY = "libc++"; 259 | CLANG_ENABLE_MODULES = YES; 260 | CLANG_ENABLE_OBJC_ARC = YES; 261 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 262 | CLANG_WARN_BOOL_CONVERSION = YES; 263 | CLANG_WARN_COMMA = YES; 264 | CLANG_WARN_CONSTANT_CONVERSION = YES; 265 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 266 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 267 | CLANG_WARN_EMPTY_BODY = YES; 268 | CLANG_WARN_ENUM_CONVERSION = YES; 269 | CLANG_WARN_INFINITE_RECURSION = YES; 270 | CLANG_WARN_INT_CONVERSION = YES; 271 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 273 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 274 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 275 | CLANG_WARN_STRICT_PROTOTYPES = YES; 276 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 277 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 278 | CLANG_WARN_UNREACHABLE_CODE = YES; 279 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 280 | CODE_SIGN_IDENTITY = "iPhone Developer"; 281 | COPY_PHASE_STRIP = NO; 282 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 283 | ENABLE_NS_ASSERTIONS = NO; 284 | ENABLE_STRICT_OBJC_MSGSEND = YES; 285 | GCC_C_LANGUAGE_STANDARD = gnu11; 286 | GCC_NO_COMMON_BLOCKS = YES; 287 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 288 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 289 | GCC_WARN_UNDECLARED_SELECTOR = YES; 290 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 291 | GCC_WARN_UNUSED_FUNCTION = YES; 292 | GCC_WARN_UNUSED_VARIABLE = YES; 293 | IPHONEOS_DEPLOYMENT_TARGET = 11.2; 294 | MTL_ENABLE_DEBUG_INFO = NO; 295 | SDKROOT = iphoneos; 296 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 297 | VALIDATE_PRODUCT = YES; 298 | }; 299 | name = Release; 300 | }; 301 | 089A6A63203B1C4A0028525F /* Debug */ = { 302 | isa = XCBuildConfiguration; 303 | buildSettings = { 304 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 305 | CODE_SIGN_STYLE = Automatic; 306 | DEVELOPMENT_TEAM = ZWE7Y5MFJ2; 307 | INFOPLIST_FILE = DynamicHorizontalScrollingShelves/Info.plist; 308 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 309 | PRODUCT_BUNDLE_IDENTIFIER = com.chandler.charles.DynamicHorizontalScrollingShelves; 310 | PRODUCT_NAME = "$(TARGET_NAME)"; 311 | SWIFT_VERSION = 4.0; 312 | TARGETED_DEVICE_FAMILY = "1,2"; 313 | }; 314 | name = Debug; 315 | }; 316 | 089A6A64203B1C4A0028525F /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 320 | CODE_SIGN_STYLE = Automatic; 321 | DEVELOPMENT_TEAM = ZWE7Y5MFJ2; 322 | INFOPLIST_FILE = DynamicHorizontalScrollingShelves/Info.plist; 323 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 324 | PRODUCT_BUNDLE_IDENTIFIER = com.chandler.charles.DynamicHorizontalScrollingShelves; 325 | PRODUCT_NAME = "$(TARGET_NAME)"; 326 | SWIFT_VERSION = 4.0; 327 | TARGETED_DEVICE_FAMILY = "1,2"; 328 | }; 329 | name = Release; 330 | }; 331 | /* End XCBuildConfiguration section */ 332 | 333 | /* Begin XCConfigurationList section */ 334 | 089A6A4B203B1C4A0028525F /* Build configuration list for PBXProject "DynamicHorizontalScrollingShelves" */ = { 335 | isa = XCConfigurationList; 336 | buildConfigurations = ( 337 | 089A6A60203B1C4A0028525F /* Debug */, 338 | 089A6A61203B1C4A0028525F /* Release */, 339 | ); 340 | defaultConfigurationIsVisible = 0; 341 | defaultConfigurationName = Release; 342 | }; 343 | 089A6A62203B1C4A0028525F /* Build configuration list for PBXNativeTarget "DynamicHorizontalScrollingShelves" */ = { 344 | isa = XCConfigurationList; 345 | buildConfigurations = ( 346 | 089A6A63203B1C4A0028525F /* Debug */, 347 | 089A6A64203B1C4A0028525F /* Release */, 348 | ); 349 | defaultConfigurationIsVisible = 0; 350 | defaultConfigurationName = Release; 351 | }; 352 | /* End XCConfigurationList section */ 353 | }; 354 | rootObject = 089A6A48203B1C4A0028525F /* Project object */; 355 | } 356 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves.xcodeproj/xcuserdata/charleschandler.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves.xcodeproj/xcuserdata/charleschandler.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DynamicHorizontalScrollingShelves.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // DynamicHorizontalScrollingShelves 4 | // 5 | // Created by Charles Chandler on 2/19/18. 6 | // Copyright © 2018 Charles Chandler. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | @UIApplicationMain 12 | class AppDelegate: UIResponder, UIApplicationDelegate { 13 | 14 | var window: UIWindow? 15 | 16 | 17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { 18 | // Override point for customization after application launch. 19 | return true 20 | } 21 | 22 | func applicationWillResignActive(_ application: UIApplication) { 23 | // 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. 24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game. 25 | } 26 | 27 | func applicationDidEnterBackground(_ application: UIApplication) { 28 | // 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. 29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 30 | } 31 | 32 | func applicationWillEnterForeground(_ application: UIApplication) { 33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background. 34 | } 35 | 36 | func applicationDidBecomeActive(_ application: UIApplication) { 37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 38 | } 39 | 40 | func applicationWillTerminate(_ application: UIApplication) { 41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 42 | } 43 | 44 | 45 | } 46 | 47 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | } 93 | ], 94 | "info" : { 95 | "version" : 1, 96 | "author" : "xcode" 97 | } 98 | } -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 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 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/Constants.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Constants.swift 3 | // DynamicHorizontalScrollingShelves 4 | // 5 | // Created by Charles Chandler on 2/19/18. 6 | // Copyright © 2018 Charles Chandler. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct Constants { 12 | 13 | static let exampleCellReuseIdentifier: String = "ExampleCell" 14 | static let cardWidth: CGFloat = 176.0 15 | static let maximumCardHeight: CGFloat = 272.0 16 | 17 | } 18 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/DynamicHeightCalculable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DynamicHeightCalculable.swift 3 | // DynamicHorizontalScrollingShelves 4 | // 5 | // Created by Charles Chandler on 2/19/18. 6 | // Copyright © 2018 Charles Chandler. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol DynamicHeightCalculable { 12 | func height(forWidth: CGFloat) -> CGFloat 13 | } 14 | 15 | func calculateHeighest(with viewModels: [T], forWidth width: CGFloat) -> T? { 16 | var largestViewModel = viewModels.first 17 | var largestHeight: CGFloat = 0 18 | 19 | for viewModel in viewModels { 20 | let height = viewModel.height(forWidth: width) 21 | 22 | if height > largestHeight { 23 | largestHeight = height 24 | largestViewModel = viewModel 25 | } 26 | } 27 | 28 | return largestViewModel 29 | } 30 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/ExampleCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleCell.swift 3 | // DynamicHorizontalScrollingShelves 4 | // 5 | // Created by Charles Chandler on 2/19/18. 6 | // Copyright © 2018 Charles Chandler. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | final class ExampleCell: UICollectionViewCell { 12 | 13 | @IBOutlet var title: UILabel! 14 | @IBOutlet var body: UILabel! 15 | 16 | private static let sizingCell = UINib(nibName: Constants.exampleCellReuseIdentifier, bundle: nil).instantiate(withOwner: nil, options: nil).first! as! ExampleCell 17 | 18 | public func configure(with viewModel: ExampleViewModel, isSizing: Bool = false) { 19 | body.text = viewModel.body 20 | 21 | guard !isSizing else { 22 | return 23 | } 24 | 25 | title.text = viewModel.title 26 | 27 | layer.cornerRadius = 2.0 28 | } 29 | 30 | public static func height(for viewModel: ExampleViewModel, forWidth width: CGFloat) -> CGFloat { 31 | sizingCell.prepareForReuse() 32 | sizingCell.configure(with: viewModel, isSizing: true) 33 | sizingCell.layoutIfNeeded() 34 | var fittingSize = UILayoutFittingCompressedSize 35 | fittingSize.width = width 36 | let size = sizingCell.contentView.systemLayoutSizeFitting(fittingSize, 37 | withHorizontalFittingPriority: .required, 38 | verticalFittingPriority: .defaultLow) 39 | 40 | guard size.height < Constants.maximumCardHeight else { 41 | return Constants.maximumCardHeight 42 | } 43 | 44 | return size.height 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/ExampleCell.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/ExampleData.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleData.swift 3 | // DynamicHorizontalScrollingShelves 4 | // 5 | // Created by Charles Chandler on 2/19/18. 6 | // Copyright © 2018 Charles Chandler. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ExampleData { 12 | 13 | static let dataSet1 = [ 14 | ExampleModel(title: "Lorem ipsum", body: "dolor sit amet, consectetur adipiscing elit,"), 15 | ExampleModel(title: "sed do eiusmod", body: "tempor incididunt"), 16 | ExampleModel(title: "Duis aute", body: "irure dolor in reprehenderit "), 17 | ExampleModel(title: "in voluptate", body: nil), 18 | ExampleModel(title: "velit esse cillum dolore eu fugiat nulla pariatur.", body: "Excepteur sint"), 19 | ExampleModel(title: "occaecat cupidatat", body: "non proident,") 20 | ] 21 | 22 | static let dataSet2 = [ 23 | ExampleModel(title: "Lorem ipsum", body: "dolor sit amet, consectetur adipiscing elit,"), 24 | ExampleModel(title: "sed do eiusmod", body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."), 25 | ExampleModel(title: "Duis aute", body: "irure dolor in reprehenderit "), 26 | ExampleModel(title: "in voluptate", body: nil), 27 | ExampleModel(title: "velit esse cillum dolore eu fugiat nulla pariatur.", body: "Excepteur sint"), 28 | ExampleModel(title: "occaecat cupidatat", body: "non proident,") 29 | ] 30 | 31 | static let dataSet3 = [ 32 | ExampleModel(title: "Lorem ipsum", body: nil), 33 | ExampleModel(title: "sed do eiusmod", body: nil), 34 | ExampleModel(title: "Duis aute", body: nil), 35 | ExampleModel(title: "in voluptate", body: nil), 36 | ExampleModel(title: "velit esse cillum dolore eu fugiat nulla pariatur.", body: nil), 37 | ExampleModel(title: "occaecat cupidatat", body: nil) 38 | ] 39 | 40 | } 41 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/ExampleModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Example.swift 3 | // DynamicHorizontalScrollingShelves 4 | // 5 | // Created by Charles Chandler on 2/19/18. 6 | // Copyright © 2018 Charles Chandler. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | struct ExampleModel { 12 | 13 | let title: String 14 | let body: String? 15 | 16 | } 17 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/ExampleViewModel.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ExampleViewModel.swift 3 | // DynamicHorizontalScrollingShelves 4 | // 5 | // Created by Charles Chandler on 2/19/18. 6 | // Copyright © 2018 Charles Chandler. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | struct ExampleViewModel: DynamicHeightCalculable { 12 | 13 | let title: String 14 | let body: String? 15 | 16 | init(example: ExampleModel) { 17 | title = example.title 18 | body = example.body 19 | } 20 | 21 | public func height(forWidth width: CGFloat) -> CGFloat { 22 | let sizingLabel = UILabel() 23 | sizingLabel.numberOfLines = 0 24 | sizingLabel.font = UIFont.systemFont(ofSize: 14.0, weight: .regular) 25 | sizingLabel.lineBreakMode = .byTruncatingTail 26 | sizingLabel.text = body 27 | 28 | let maxSize = CGSize(width: width, height: .greatestFiniteMagnitude) 29 | let size = sizingLabel.sizeThatFits(maxSize) 30 | 31 | return size.height 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | CFBundleVersion 20 | 1 21 | LSRequiresIPhoneOS 22 | 23 | UILaunchStoryboardName 24 | LaunchScreen 25 | UIMainStoryboardFile 26 | Main 27 | UIRequiredDeviceCapabilities 28 | 29 | armv7 30 | 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /DynamicHorizontalScrollingShelves/ViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.swift 3 | // DynamicHorizontalScrollingShelves 4 | // 5 | // Created by Charles Chandler on 2/19/18. 6 | // Copyright © 2018 Charles Chandler. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class ViewController: UIViewController, UICollectionViewDataSource { 12 | 13 | @IBOutlet var collectionView: UICollectionView! 14 | @IBOutlet var flowLayout: UICollectionViewFlowLayout! 15 | @IBOutlet var collectionViewHeightConstraint: NSLayoutConstraint! 16 | 17 | private var data: [ExampleModel] = ExampleData.dataSet1 18 | 19 | private enum Segment: Int { 20 | case dataSet1 = 0, dataSet2, dataSet3 21 | } 22 | 23 | override func viewDidLoad() { 24 | super.viewDidLoad() 25 | 26 | setupCollectioView() 27 | } 28 | 29 | private func setupCollectioView() { 30 | let nib = UINib(nibName: Constants.exampleCellReuseIdentifier, bundle: nil) 31 | collectionView.register(nib, forCellWithReuseIdentifier: Constants.exampleCellReuseIdentifier) 32 | 33 | let edgeInsets = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0) 34 | flowLayout.sectionInset = edgeInsets 35 | 36 | setCollectionViewHeight(with: data, edgeInsets: flowLayout.sectionInset) 37 | } 38 | 39 | private func setCollectionViewHeight(with data: [ExampleModel], edgeInsets: UIEdgeInsets) { 40 | let viewModels = data.flatMap { ExampleViewModel(example: $0) } 41 | 42 | guard let viewModel = calculateHeighest(with: viewModels, forWidth: Constants.cardWidth) else { 43 | return 44 | } 45 | 46 | let height = ExampleCell.height(for: viewModel, forWidth: Constants.cardWidth) 47 | 48 | flowLayout.itemSize = CGSize(width: Constants.cardWidth, height: height) 49 | 50 | collectionViewHeightConstraint.constant = height + edgeInsets.top + edgeInsets.bottom 51 | } 52 | 53 | // MARK: - UICollectionViewDatasource 54 | 55 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { 56 | return data.count 57 | } 58 | 59 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 60 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constants.exampleCellReuseIdentifier, for: indexPath) as! ExampleCell 61 | let example = data[indexPath.item] 62 | let viewModel = ExampleViewModel(example: example) 63 | 64 | cell.configure(with: viewModel) 65 | 66 | return cell 67 | } 68 | 69 | // MARK: - Segmented Control 70 | 71 | @IBAction func valueChanged(_ sender: UISegmentedControl) { 72 | guard let segment = Segment(rawValue: sender.selectedSegmentIndex) else { 73 | fatalError("Invalid value returned from segmented control") 74 | } 75 | 76 | switch segment { 77 | case .dataSet1: 78 | data = ExampleData.dataSet1 79 | case .dataSet2: 80 | data = ExampleData.dataSet2 81 | case .dataSet3: 82 | data = ExampleData.dataSet3 83 | } 84 | 85 | setCollectionViewHeight(with: data, edgeInsets: flowLayout.sectionInset) 86 | 87 | flowLayout.invalidateLayout() 88 | collectionView.reloadData() 89 | } 90 | 91 | } 92 | 93 | --------------------------------------------------------------------------------