├── 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 |
--------------------------------------------------------------------------------