├── README.md ├── UICollectionView-Collapsible-Section-Demo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── sebvidal.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist └── UICollectionView-Collapsible-Section-Demo ├── Base ├── AppDelegate │ └── AppDelegate.swift └── SceneDelegate │ └── SceneDelegate.swift ├── Resources ├── Assets │ └── Assets.xcassets │ │ ├── AccentColor.colorset │ │ └── Contents.json │ │ ├── AppIcon.appiconset │ │ └── Contents.json │ │ └── Contents.json ├── Extensions │ └── UICollectionViewLayout │ │ └── UICollectionViewLayout+SectionedListLayout.swift ├── Storyboards │ └── Base.lproj │ │ └── LaunchScreen.storyboard └── Supporting Files │ └── Info.plist └── View Controllers └── CollectionViewController ├── CollectionViewController+DiffableDataSource.swift └── CollectionViewController.swift /README.md: -------------------------------------------------------------------------------- 1 | # UICollectionView-Collapsible-Section-Demo 2 | A project demonstrating how to create collapsible list sections using [UICollectionView](https://developer.apple.com/documentation/uikit/uicollectionview) and [NSDiffableDataSourceSectionSnapshot](https://developer.apple.com/documentation/uikit/NSDiffableDataSourceSectionSnapshot). 3 | 4 | The key components of this project are in `CollectionViewController.swift`, `CollectionViewController+DiffableDataSource.swift` and `UICollectionViewLayout+SectionedListLayout.swift`. 5 | 6 | 7 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | C207C6EC2B80C151002D0C4C /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C207C6EB2B80C151002D0C4C /* AppDelegate.swift */; }; 11 | C207C6EE2B80C151002D0C4C /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C207C6ED2B80C151002D0C4C /* SceneDelegate.swift */; }; 12 | C207C6F02B80C151002D0C4C /* CollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C207C6EF2B80C151002D0C4C /* CollectionViewController.swift */; }; 13 | C207C6F52B80C152002D0C4C /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C207C6F42B80C152002D0C4C /* Assets.xcassets */; }; 14 | C207C6F82B80C152002D0C4C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C207C6F62B80C152002D0C4C /* LaunchScreen.storyboard */; }; 15 | C207C71C2B80C1C2002D0C4C /* CollectionViewController+DiffableDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = C207C71B2B80C1C2002D0C4C /* CollectionViewController+DiffableDataSource.swift */; }; 16 | C207C7262B80C218002D0C4C /* UICollectionViewLayout+SectionedListLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = C207C7252B80C218002D0C4C /* UICollectionViewLayout+SectionedListLayout.swift */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXFileReference section */ 20 | C207C6E82B80C151002D0C4C /* UICollectionView-Collapsible-Section-Demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "UICollectionView-Collapsible-Section-Demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 21 | C207C6EB2B80C151002D0C4C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 22 | C207C6ED2B80C151002D0C4C /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; 23 | C207C6EF2B80C151002D0C4C /* CollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewController.swift; sourceTree = ""; }; 24 | C207C6F42B80C152002D0C4C /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 25 | C207C6F72B80C152002D0C4C /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 26 | C207C6F92B80C152002D0C4C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 27 | C207C71B2B80C1C2002D0C4C /* CollectionViewController+DiffableDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CollectionViewController+DiffableDataSource.swift"; sourceTree = ""; }; 28 | C207C7252B80C218002D0C4C /* UICollectionViewLayout+SectionedListLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UICollectionViewLayout+SectionedListLayout.swift"; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | C207C6E52B80C151002D0C4C /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | C207C6DF2B80C151002D0C4C = { 43 | isa = PBXGroup; 44 | children = ( 45 | C207C6EA2B80C151002D0C4C /* UICollectionView-Collapsible-Section-Demo */, 46 | C207C6E92B80C151002D0C4C /* Products */, 47 | ); 48 | sourceTree = ""; 49 | }; 50 | C207C6E92B80C151002D0C4C /* Products */ = { 51 | isa = PBXGroup; 52 | children = ( 53 | C207C6E82B80C151002D0C4C /* UICollectionView-Collapsible-Section-Demo.app */, 54 | ); 55 | name = Products; 56 | sourceTree = ""; 57 | }; 58 | C207C6EA2B80C151002D0C4C /* UICollectionView-Collapsible-Section-Demo */ = { 59 | isa = PBXGroup; 60 | children = ( 61 | C207C71E2B80C1D9002D0C4C /* Base */, 62 | C207C7292B80C236002D0C4C /* View Controllers */, 63 | C207C7212B80C1EB002D0C4C /* Resources */, 64 | ); 65 | path = "UICollectionView-Collapsible-Section-Demo"; 66 | sourceTree = ""; 67 | }; 68 | C207C71D2B80C1D3002D0C4C /* CollectionViewController */ = { 69 | isa = PBXGroup; 70 | children = ( 71 | C207C6EF2B80C151002D0C4C /* CollectionViewController.swift */, 72 | C207C71B2B80C1C2002D0C4C /* CollectionViewController+DiffableDataSource.swift */, 73 | ); 74 | path = CollectionViewController; 75 | sourceTree = ""; 76 | }; 77 | C207C71E2B80C1D9002D0C4C /* Base */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | C207C71F2B80C1DD002D0C4C /* AppDelegate */, 81 | C207C7202B80C1E2002D0C4C /* SceneDelegate */, 82 | ); 83 | path = Base; 84 | sourceTree = ""; 85 | }; 86 | C207C71F2B80C1DD002D0C4C /* AppDelegate */ = { 87 | isa = PBXGroup; 88 | children = ( 89 | C207C6EB2B80C151002D0C4C /* AppDelegate.swift */, 90 | ); 91 | path = AppDelegate; 92 | sourceTree = ""; 93 | }; 94 | C207C7202B80C1E2002D0C4C /* SceneDelegate */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | C207C6ED2B80C151002D0C4C /* SceneDelegate.swift */, 98 | ); 99 | path = SceneDelegate; 100 | sourceTree = ""; 101 | }; 102 | C207C7212B80C1EB002D0C4C /* Resources */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | C207C7222B80C1F2002D0C4C /* Assets */, 106 | C207C7282B80C228002D0C4C /* Extensions */, 107 | C207C7232B80C1F6002D0C4C /* Storyboards */, 108 | C207C7242B80C202002D0C4C /* Supporting Files */, 109 | ); 110 | path = Resources; 111 | sourceTree = ""; 112 | }; 113 | C207C7222B80C1F2002D0C4C /* Assets */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | C207C6F42B80C152002D0C4C /* Assets.xcassets */, 117 | ); 118 | path = Assets; 119 | sourceTree = ""; 120 | }; 121 | C207C7232B80C1F6002D0C4C /* Storyboards */ = { 122 | isa = PBXGroup; 123 | children = ( 124 | C207C6F62B80C152002D0C4C /* LaunchScreen.storyboard */, 125 | ); 126 | path = Storyboards; 127 | sourceTree = ""; 128 | }; 129 | C207C7242B80C202002D0C4C /* Supporting Files */ = { 130 | isa = PBXGroup; 131 | children = ( 132 | C207C6F92B80C152002D0C4C /* Info.plist */, 133 | ); 134 | path = "Supporting Files"; 135 | sourceTree = ""; 136 | }; 137 | C207C7272B80C225002D0C4C /* UICollectionViewLayout */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | C207C7252B80C218002D0C4C /* UICollectionViewLayout+SectionedListLayout.swift */, 141 | ); 142 | path = UICollectionViewLayout; 143 | sourceTree = ""; 144 | }; 145 | C207C7282B80C228002D0C4C /* Extensions */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | C207C7272B80C225002D0C4C /* UICollectionViewLayout */, 149 | ); 150 | path = Extensions; 151 | sourceTree = ""; 152 | }; 153 | C207C7292B80C236002D0C4C /* View Controllers */ = { 154 | isa = PBXGroup; 155 | children = ( 156 | C207C71D2B80C1D3002D0C4C /* CollectionViewController */, 157 | ); 158 | path = "View Controllers"; 159 | sourceTree = ""; 160 | }; 161 | /* End PBXGroup section */ 162 | 163 | /* Begin PBXNativeTarget section */ 164 | C207C6E72B80C151002D0C4C /* UICollectionView-Collapsible-Section-Demo */ = { 165 | isa = PBXNativeTarget; 166 | buildConfigurationList = C207C7122B80C152002D0C4C /* Build configuration list for PBXNativeTarget "UICollectionView-Collapsible-Section-Demo" */; 167 | buildPhases = ( 168 | C207C6E42B80C151002D0C4C /* Sources */, 169 | C207C6E52B80C151002D0C4C /* Frameworks */, 170 | C207C6E62B80C151002D0C4C /* Resources */, 171 | ); 172 | buildRules = ( 173 | ); 174 | dependencies = ( 175 | ); 176 | name = "UICollectionView-Collapsible-Section-Demo"; 177 | productName = "UICollectionView-Collapsible-Section-Demo"; 178 | productReference = C207C6E82B80C151002D0C4C /* UICollectionView-Collapsible-Section-Demo.app */; 179 | productType = "com.apple.product-type.application"; 180 | }; 181 | /* End PBXNativeTarget section */ 182 | 183 | /* Begin PBXProject section */ 184 | C207C6E02B80C151002D0C4C /* Project object */ = { 185 | isa = PBXProject; 186 | attributes = { 187 | BuildIndependentTargetsInParallel = 1; 188 | LastSwiftUpdateCheck = 1500; 189 | LastUpgradeCheck = 1500; 190 | TargetAttributes = { 191 | C207C6E72B80C151002D0C4C = { 192 | CreatedOnToolsVersion = 15.0; 193 | }; 194 | }; 195 | }; 196 | buildConfigurationList = C207C6E32B80C151002D0C4C /* Build configuration list for PBXProject "UICollectionView-Collapsible-Section-Demo" */; 197 | compatibilityVersion = "Xcode 14.0"; 198 | developmentRegion = en; 199 | hasScannedForEncodings = 0; 200 | knownRegions = ( 201 | en, 202 | Base, 203 | ); 204 | mainGroup = C207C6DF2B80C151002D0C4C; 205 | productRefGroup = C207C6E92B80C151002D0C4C /* Products */; 206 | projectDirPath = ""; 207 | projectRoot = ""; 208 | targets = ( 209 | C207C6E72B80C151002D0C4C /* UICollectionView-Collapsible-Section-Demo */, 210 | ); 211 | }; 212 | /* End PBXProject section */ 213 | 214 | /* Begin PBXResourcesBuildPhase section */ 215 | C207C6E62B80C151002D0C4C /* Resources */ = { 216 | isa = PBXResourcesBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | C207C6F82B80C152002D0C4C /* LaunchScreen.storyboard in Resources */, 220 | C207C6F52B80C152002D0C4C /* Assets.xcassets in Resources */, 221 | ); 222 | runOnlyForDeploymentPostprocessing = 0; 223 | }; 224 | /* End PBXResourcesBuildPhase section */ 225 | 226 | /* Begin PBXSourcesBuildPhase section */ 227 | C207C6E42B80C151002D0C4C /* Sources */ = { 228 | isa = PBXSourcesBuildPhase; 229 | buildActionMask = 2147483647; 230 | files = ( 231 | C207C6F02B80C151002D0C4C /* CollectionViewController.swift in Sources */, 232 | C207C6EC2B80C151002D0C4C /* AppDelegate.swift in Sources */, 233 | C207C71C2B80C1C2002D0C4C /* CollectionViewController+DiffableDataSource.swift in Sources */, 234 | C207C6EE2B80C151002D0C4C /* SceneDelegate.swift in Sources */, 235 | C207C7262B80C218002D0C4C /* UICollectionViewLayout+SectionedListLayout.swift in Sources */, 236 | ); 237 | runOnlyForDeploymentPostprocessing = 0; 238 | }; 239 | /* End PBXSourcesBuildPhase section */ 240 | 241 | /* Begin PBXVariantGroup section */ 242 | C207C6F62B80C152002D0C4C /* LaunchScreen.storyboard */ = { 243 | isa = PBXVariantGroup; 244 | children = ( 245 | C207C6F72B80C152002D0C4C /* Base */, 246 | ); 247 | name = LaunchScreen.storyboard; 248 | sourceTree = ""; 249 | }; 250 | /* End PBXVariantGroup section */ 251 | 252 | /* Begin XCBuildConfiguration section */ 253 | C207C7102B80C152002D0C4C /* Debug */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ALWAYS_SEARCH_USER_PATHS = NO; 257 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 258 | CLANG_ANALYZER_NONNULL = YES; 259 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 260 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 261 | CLANG_ENABLE_MODULES = YES; 262 | CLANG_ENABLE_OBJC_ARC = YES; 263 | CLANG_ENABLE_OBJC_WEAK = YES; 264 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 265 | CLANG_WARN_BOOL_CONVERSION = YES; 266 | CLANG_WARN_COMMA = YES; 267 | CLANG_WARN_CONSTANT_CONVERSION = YES; 268 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 269 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 270 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 271 | CLANG_WARN_EMPTY_BODY = YES; 272 | CLANG_WARN_ENUM_CONVERSION = YES; 273 | CLANG_WARN_INFINITE_RECURSION = YES; 274 | CLANG_WARN_INT_CONVERSION = YES; 275 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 276 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 277 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 278 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 279 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 280 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 281 | CLANG_WARN_STRICT_PROTOTYPES = YES; 282 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 283 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 284 | CLANG_WARN_UNREACHABLE_CODE = YES; 285 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 286 | COPY_PHASE_STRIP = NO; 287 | DEBUG_INFORMATION_FORMAT = dwarf; 288 | ENABLE_STRICT_OBJC_MSGSEND = YES; 289 | ENABLE_TESTABILITY = YES; 290 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 291 | GCC_C_LANGUAGE_STANDARD = gnu17; 292 | GCC_DYNAMIC_NO_PIC = NO; 293 | GCC_NO_COMMON_BLOCKS = YES; 294 | GCC_OPTIMIZATION_LEVEL = 0; 295 | GCC_PREPROCESSOR_DEFINITIONS = ( 296 | "DEBUG=1", 297 | "$(inherited)", 298 | ); 299 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 300 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 301 | GCC_WARN_UNDECLARED_SELECTOR = YES; 302 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 303 | GCC_WARN_UNUSED_FUNCTION = YES; 304 | GCC_WARN_UNUSED_VARIABLE = YES; 305 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 306 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 307 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 308 | MTL_FAST_MATH = YES; 309 | ONLY_ACTIVE_ARCH = YES; 310 | SDKROOT = iphoneos; 311 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 312 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 313 | }; 314 | name = Debug; 315 | }; 316 | C207C7112B80C152002D0C4C /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ALWAYS_SEARCH_USER_PATHS = NO; 320 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 321 | CLANG_ANALYZER_NONNULL = YES; 322 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 324 | CLANG_ENABLE_MODULES = YES; 325 | CLANG_ENABLE_OBJC_ARC = YES; 326 | CLANG_ENABLE_OBJC_WEAK = YES; 327 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_COMMA = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 332 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 333 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 334 | CLANG_WARN_EMPTY_BODY = YES; 335 | CLANG_WARN_ENUM_CONVERSION = YES; 336 | CLANG_WARN_INFINITE_RECURSION = YES; 337 | CLANG_WARN_INT_CONVERSION = YES; 338 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 339 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 340 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 341 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 342 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 343 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 344 | CLANG_WARN_STRICT_PROTOTYPES = YES; 345 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 346 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 347 | CLANG_WARN_UNREACHABLE_CODE = YES; 348 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 349 | COPY_PHASE_STRIP = NO; 350 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 351 | ENABLE_NS_ASSERTIONS = NO; 352 | ENABLE_STRICT_OBJC_MSGSEND = YES; 353 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 354 | GCC_C_LANGUAGE_STANDARD = gnu17; 355 | GCC_NO_COMMON_BLOCKS = YES; 356 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 357 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 358 | GCC_WARN_UNDECLARED_SELECTOR = YES; 359 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 360 | GCC_WARN_UNUSED_FUNCTION = YES; 361 | GCC_WARN_UNUSED_VARIABLE = YES; 362 | IPHONEOS_DEPLOYMENT_TARGET = 17.0; 363 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 364 | MTL_ENABLE_DEBUG_INFO = NO; 365 | MTL_FAST_MATH = YES; 366 | SDKROOT = iphoneos; 367 | SWIFT_COMPILATION_MODE = wholemodule; 368 | VALIDATE_PRODUCT = YES; 369 | }; 370 | name = Release; 371 | }; 372 | C207C7132B80C152002D0C4C /* Debug */ = { 373 | isa = XCBuildConfiguration; 374 | buildSettings = { 375 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 376 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 377 | CODE_SIGN_STYLE = Automatic; 378 | CURRENT_PROJECT_VERSION = 1; 379 | DEVELOPMENT_TEAM = DY2GQFY855; 380 | GENERATE_INFOPLIST_FILE = YES; 381 | INFOPLIST_FILE = "UICollectionView-Collapsible-Section-Demo/Resources/Supporting Files/Info.plist"; 382 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 383 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 384 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 385 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 386 | LD_RUNPATH_SEARCH_PATHS = ( 387 | "$(inherited)", 388 | "@executable_path/Frameworks", 389 | ); 390 | MARKETING_VERSION = 1.0; 391 | PRODUCT_BUNDLE_IDENTIFIER = "com.sebvidal.UICollectionView-Collapsible-Section-Demo"; 392 | PRODUCT_NAME = "$(TARGET_NAME)"; 393 | SWIFT_EMIT_LOC_STRINGS = YES; 394 | SWIFT_VERSION = 5.0; 395 | TARGETED_DEVICE_FAMILY = "1,2"; 396 | }; 397 | name = Debug; 398 | }; 399 | C207C7142B80C152002D0C4C /* Release */ = { 400 | isa = XCBuildConfiguration; 401 | buildSettings = { 402 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 403 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 404 | CODE_SIGN_STYLE = Automatic; 405 | CURRENT_PROJECT_VERSION = 1; 406 | DEVELOPMENT_TEAM = DY2GQFY855; 407 | GENERATE_INFOPLIST_FILE = YES; 408 | INFOPLIST_FILE = "UICollectionView-Collapsible-Section-Demo/Resources/Supporting Files/Info.plist"; 409 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 410 | INFOPLIST_KEY_UILaunchStoryboardName = LaunchScreen; 411 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 412 | INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; 413 | LD_RUNPATH_SEARCH_PATHS = ( 414 | "$(inherited)", 415 | "@executable_path/Frameworks", 416 | ); 417 | MARKETING_VERSION = 1.0; 418 | PRODUCT_BUNDLE_IDENTIFIER = "com.sebvidal.UICollectionView-Collapsible-Section-Demo"; 419 | PRODUCT_NAME = "$(TARGET_NAME)"; 420 | SWIFT_EMIT_LOC_STRINGS = YES; 421 | SWIFT_VERSION = 5.0; 422 | TARGETED_DEVICE_FAMILY = "1,2"; 423 | }; 424 | name = Release; 425 | }; 426 | /* End XCBuildConfiguration section */ 427 | 428 | /* Begin XCConfigurationList section */ 429 | C207C6E32B80C151002D0C4C /* Build configuration list for PBXProject "UICollectionView-Collapsible-Section-Demo" */ = { 430 | isa = XCConfigurationList; 431 | buildConfigurations = ( 432 | C207C7102B80C152002D0C4C /* Debug */, 433 | C207C7112B80C152002D0C4C /* Release */, 434 | ); 435 | defaultConfigurationIsVisible = 0; 436 | defaultConfigurationName = Release; 437 | }; 438 | C207C7122B80C152002D0C4C /* Build configuration list for PBXNativeTarget "UICollectionView-Collapsible-Section-Demo" */ = { 439 | isa = XCConfigurationList; 440 | buildConfigurations = ( 441 | C207C7132B80C152002D0C4C /* Debug */, 442 | C207C7142B80C152002D0C4C /* Release */, 443 | ); 444 | defaultConfigurationIsVisible = 0; 445 | defaultConfigurationName = Release; 446 | }; 447 | /* End XCConfigurationList section */ 448 | }; 449 | rootObject = C207C6E02B80C151002D0C4C /* Project object */; 450 | } 451 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo.xcodeproj/xcuserdata/sebvidal.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | UICollectionView-Collapsible-Section-Demo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo/Base/AppDelegate/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // UICollectionView-Collapsible-Section-Demo 4 | // 5 | // Created by Seb Vidal on 17/02/2024. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | 13 | 14 | 15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 16 | // Override point for customization after application launch. 17 | return true 18 | } 19 | 20 | // MARK: UISceneSession Lifecycle 21 | 22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration { 23 | // Called when a new scene session is being created. 24 | // Use this method to select a configuration to create the new scene with. 25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) 26 | } 27 | 28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) { 29 | // Called when the user discards a scene session. 30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions. 31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return. 32 | } 33 | 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo/Base/SceneDelegate/SceneDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SceneDelegate.swift 3 | // UICollectionView-Collapsible-Section-Demo 4 | // 5 | // Created by Seb Vidal on 17/02/2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate { 11 | // MARK: - Public Properties 12 | var window: UIWindow? 13 | 14 | // MARK: - UIWindowSceneDelegate 15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { 16 | guard let windowScene = (scene as? UIWindowScene) else { return } 17 | 18 | let viewController = CollectionViewController() 19 | viewController.title = "Browse" 20 | viewController.navigationItem.largeTitleDisplayMode = .always 21 | viewController.navigationItem.hidesSearchBarWhenScrolling = false 22 | viewController.navigationItem.searchController = UISearchController(searchResultsController: nil) 23 | viewController.tabBarItem = UITabBarItem(title: "Browse", image: UIImage(systemName: "folder.fill"), tag: 0) 24 | 25 | let navigationController = UINavigationController(rootViewController: viewController) 26 | navigationController.navigationBar.prefersLargeTitles = true 27 | 28 | let tabBarController = UITabBarController() 29 | tabBarController.viewControllers = [navigationController] 30 | 31 | let window = UIWindow(windowScene: windowScene) 32 | window.rootViewController = tabBarController 33 | window.makeKeyAndVisible() 34 | self.window = window 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo/Resources/Assets/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo/Resources/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo/Resources/Assets/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo/Resources/Extensions/UICollectionViewLayout/UICollectionViewLayout+SectionedListLayout.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewLayout+SectionedListLayout.swift 3 | // UICollectionView-Collapsible-Section-Demo 4 | // 5 | // Created by Seb Vidal on 17/02/2024. 6 | // 7 | 8 | import UIKit 9 | 10 | extension UICollectionViewLayout { 11 | static var sectionedListLayout: UICollectionViewCompositionalLayout { 12 | UICollectionViewCompositionalLayout { section, environment in 13 | var configuration = UICollectionLayoutListConfiguration(appearance: .insetGrouped) 14 | configuration.headerMode = .firstItemInSection 15 | 16 | return NSCollectionLayoutSection.list(using: configuration, layoutEnvironment: environment) 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo/Resources/Storyboards/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 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo/Resources/Supporting Files/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UIApplicationSceneManifest 6 | 7 | UIApplicationSupportsMultipleScenes 8 | 9 | UISceneConfigurations 10 | 11 | UIWindowSceneSessionRoleApplication 12 | 13 | 14 | UISceneConfigurationName 15 | Default Configuration 16 | UISceneDelegateClassName 17 | $(PRODUCT_MODULE_NAME).SceneDelegate 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo/View Controllers/CollectionViewController/CollectionViewController+DiffableDataSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewController+DiffableDataSource.swift 3 | // UICollectionView-Collapsible-Section-Demo 4 | // 5 | // Created by Seb Vidal on 17/02/2024. 6 | // 7 | 8 | import UIKit 9 | 10 | extension CollectionViewController { 11 | enum Section: Hashable { 12 | case locations 13 | case favourites 14 | case tags 15 | } 16 | 17 | enum Item: Hashable { 18 | case header(string: String) 19 | case location(systemSymbolName: String, name: String) 20 | case tag(color: UIColor, name: String) 21 | } 22 | 23 | typealias DiffableDataSource = UICollectionViewDiffableDataSource 24 | 25 | typealias DiffableDataSourceSnapshot = NSDiffableDataSourceSnapshot 26 | 27 | typealias DiffableDataSourceSectionSnapshot = NSDiffableDataSourceSectionSnapshot 28 | } 29 | -------------------------------------------------------------------------------- /UICollectionView-Collapsible-Section-Demo/View Controllers/CollectionViewController/CollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CollectionViewController.swift 3 | // UICollectionView-Collapsible-Section-Demo 4 | // 5 | // Created by Seb Vidal on 17/02/2024. 6 | // 7 | 8 | import UIKit 9 | 10 | class CollectionViewController: UICollectionViewController { 11 | // MARK: - Private Properties 12 | private var diffableDataSource: DiffableDataSource! 13 | 14 | // MARK: - init(collectionViewLayout:) 15 | override init(collectionViewLayout layout: UICollectionViewLayout = .sectionedListLayout) { 16 | super.init(collectionViewLayout: layout) 17 | setupNavigationItem() 18 | setupDiffableDataSource() 19 | populateCollectionView() 20 | } 21 | 22 | // MARK: - init(coder:) 23 | required init?(coder: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | // MARK: - Private Methods 28 | private func setupNavigationItem() { 29 | let moreBarButtonItem = UIBarButtonItem() 30 | moreBarButtonItem.image = UIImage(systemName: "ellipsis.circle") 31 | 32 | navigationItem.rightBarButtonItem = moreBarButtonItem 33 | } 34 | 35 | private func setupDiffableDataSource() { 36 | let cellRegistration = UICollectionView.CellRegistration { cell, indexPath, itemIdentifier in 37 | switch itemIdentifier { 38 | case .header(let string): 39 | var contentConfiguration = UIListContentConfiguration.sidebarHeader() 40 | contentConfiguration.text = string 41 | 42 | cell.contentConfiguration = contentConfiguration 43 | cell.accessories = [.outlineDisclosure()] 44 | case .location(let systemSymbolName, let name): 45 | var contentConfiguration = UIListContentConfiguration.cell() 46 | contentConfiguration.image = UIImage(systemName: systemSymbolName) 47 | contentConfiguration.text = name 48 | 49 | cell.contentConfiguration = contentConfiguration 50 | cell.accessories = [.disclosureIndicator()] 51 | case .tag(let color, let name): 52 | var contentConfiguration = UIListContentConfiguration.cell() 53 | contentConfiguration.image = UIImage(systemName: "circle.fill") 54 | contentConfiguration.imageProperties.tintColor = color 55 | contentConfiguration.imageProperties.preferredSymbolConfiguration = UIImage.SymbolConfiguration(textStyle: .body) 56 | contentConfiguration.text = name 57 | 58 | cell.contentConfiguration = contentConfiguration 59 | cell.accessories = [.disclosureIndicator()] 60 | } 61 | } 62 | 63 | diffableDataSource = DiffableDataSource(collectionView: collectionView) { collectionView, indexPath, itemIdentifier in 64 | return collectionView.dequeueConfiguredReusableCell(using: cellRegistration, for: indexPath, item: itemIdentifier) 65 | } 66 | } 67 | 68 | private func populateCollectionView() { 69 | var snapshot = DiffableDataSourceSnapshot() 70 | snapshot.appendSections([.locations, .favourites, .tags]) 71 | 72 | diffableDataSource.apply(snapshot, animatingDifferences: false) 73 | 74 | let locationsSectionItem: Item = .header(string: "Locations") 75 | var locationsSectionSnapshot = DiffableDataSourceSectionSnapshot() 76 | locationsSectionSnapshot.append([locationsSectionItem]) 77 | locationsSectionSnapshot.append([.location(systemSymbolName: "iphone", name: "On My iPhone")], to: locationsSectionItem) 78 | locationsSectionSnapshot.append([.location(systemSymbolName: "trash", name: "Recently Deleted")], to: locationsSectionItem) 79 | locationsSectionSnapshot.expand([locationsSectionItem]) 80 | 81 | let favouritesSectionItem: Item = .header(string: "Favourites") 82 | var favouritesSectionSnapshot = DiffableDataSourceSectionSnapshot() 83 | favouritesSectionSnapshot.append([favouritesSectionItem]) 84 | favouritesSectionSnapshot.append([.location(systemSymbolName: "arrow.down.circle", name: "Downloads")], to: favouritesSectionItem) 85 | favouritesSectionSnapshot.expand([favouritesSectionItem]) 86 | 87 | let tagsSectionItem: Item = .header(string: "Tags") 88 | var tagsSectionSnapshot = DiffableDataSourceSectionSnapshot() 89 | tagsSectionSnapshot.append([tagsSectionItem]) 90 | tagsSectionSnapshot.append([.tag(color: .systemRed, name: "Red")], to: tagsSectionItem) 91 | tagsSectionSnapshot.append([.tag(color: .systemOrange, name: "Orange")], to: tagsSectionItem) 92 | tagsSectionSnapshot.append([.tag(color: .systemYellow, name: "Yellow")], to: tagsSectionItem) 93 | tagsSectionSnapshot.append([.tag(color: .systemGreen, name: "Green")], to: tagsSectionItem) 94 | tagsSectionSnapshot.append([.tag(color: .systemBlue, name: "Blue")], to: tagsSectionItem) 95 | tagsSectionSnapshot.append([.tag(color: .systemPurple, name: "Purple")], to: tagsSectionItem) 96 | tagsSectionSnapshot.append([.tag(color: .systemGray, name: "Gray")], to: tagsSectionItem) 97 | tagsSectionSnapshot.expand([tagsSectionItem]) 98 | 99 | diffableDataSource.apply(locationsSectionSnapshot, to: .locations) 100 | diffableDataSource.apply(favouritesSectionSnapshot, to: .favourites) 101 | diffableDataSource.apply(tagsSectionSnapshot, to: .tags) 102 | } 103 | 104 | // MARK: - UICollectionViewDelegate 105 | override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { 106 | collectionView.deselectItem(at: indexPath, animated: true) 107 | } 108 | } 109 | --------------------------------------------------------------------------------