├── Swift-Senpai-UICollectionView-List
├── Assets.xcassets
│ ├── Contents.json
│ ├── AppIcon.appiconset
│ │ ├── Icon-40.png
│ │ ├── Icon-58.png
│ │ ├── Icon-60.png
│ │ ├── Icon-80.png
│ │ ├── Icon-87.png
│ │ ├── Icon-1024.png
│ │ ├── Icon-120.png
│ │ ├── Icon-121.png
│ │ ├── Icon-180.png
│ │ └── Contents.json
│ └── AccentColor.colorset
│ │ └── Contents.json
├── Model.swift
├── Custom Cell - nib
│ ├── SFSymbolNameContentConfiguration.swift
│ ├── SFSymbolNameListCell.swift
│ ├── SFSymbolNameContentView.swift
│ ├── SFSymbolNameContentView.xib
│ └── NameListViewController.swift
├── Date Picker Replica
│ ├── DatePickerContentConfiguration.swift
│ ├── DatePickerCell.swift
│ ├── DatePickerContentView.swift
│ └── DatePickerReplicaViewController.swift
├── Custom Cell
│ ├── SFSymbolVerticalListCell.swift
│ ├── SFSymbolContentConfiguration.swift
│ ├── SFSymbolVerticalContentView.swift
│ └── CustomCellListViewController.swift
├── AppDelegate.swift
├── Base.lproj
│ ├── LaunchScreen.storyboard
│ └── Main.storyboard
├── Info.plist
├── SceneDelegate.swift
├── Custom Header & Footer
│ ├── InteractiveHeader.swift
│ └── CustomHeaderFooterViewController.swift
├── Reload Cell
│ ├── ReloadValueTypeViewController.swift
│ └── ReloadReferenceTypeViewController.swift
├── Expandable List
│ ├── SingleSecExpandableListViewController.swift
│ └── MultiSecExpandableListViewController.swift
├── Declarative Header & Footer
│ └── DeclarativeHeaderFooterViewController.swift
├── Basic List
│ └── BasicListViewController.swift
└── Reload Header
│ └── ReloadExpandableHeaderViewController.swift
├── Swift-Senpai-UICollectionView-List.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
└── project.pbxproj
├── LICENSE
├── README.md
└── .gitignore
/Swift-Senpai-UICollectionView-List/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-40.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-List/HEAD/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-40.png
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-58.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-List/HEAD/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-58.png
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-List/HEAD/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-60.png
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-80.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-List/HEAD/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-80.png
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-87.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-List/HEAD/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-87.png
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-1024.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-List/HEAD/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-1024.png
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-List/HEAD/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-120.png
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-121.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-List/HEAD/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-121.png
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LeeKahSeng/SwiftSenpai-UICollectionView-List/HEAD/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Icon-180.png
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/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 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Model.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Model.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 22/09/2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | enum Section {
12 | case main
13 | }
14 |
15 | enum ListItem: Hashable {
16 | case header(HeaderItem)
17 | case symbol(SFSymbolItem)
18 | }
19 |
20 | struct HeaderItem: Hashable {
21 | let title: String
22 | let symbols: [SFSymbolItem]
23 | }
24 |
25 | struct SFSymbolItem: Hashable {
26 | let name: String
27 | let image: UIImage
28 |
29 | init(name: String) {
30 | self.name = name
31 | self.image = UIImage(systemName: name)!
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Custom Cell - nib/SFSymbolNameContentConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SFSymbolNameContentConfiguration.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 17/08/2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | struct SFSymbolNameContentConfiguration: UIContentConfiguration, Hashable {
12 |
13 | var name: String?
14 |
15 | func makeContentView() -> UIView & UIContentView {
16 | // Initialize an instance of SFSymbolNameContentView
17 | return SFSymbolNameContentView(configuration: self)
18 | }
19 |
20 | func updated(for state: UIConfigurationState) -> Self {
21 | return self
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Date Picker Replica/DatePickerContentConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatePickerContentConfiguration.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 01/01/2021.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | struct DatePickerContentConfiguration: UIContentConfiguration, Hashable {
12 |
13 | var item: DatePickerItem?
14 |
15 | func makeContentView() -> UIView & UIContentView {
16 | // Initialize an instance of DatePickerContentView
17 | return DatePickerContentView(configuration: self)
18 | }
19 |
20 | func updated(for state: UIConfigurationState) -> Self {
21 | return self
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Date Picker Replica/DatePickerCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatePickerCell.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 01/01/2021.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | class DatePickerCell: UICollectionViewListCell {
12 |
13 | var item: DatePickerItem?
14 |
15 | override func updateConfiguration(using state: UICellConfigurationState) {
16 |
17 | // Create new configuration object and update it base on state
18 | var newConfiguration = DatePickerContentConfiguration().updated(for: state)
19 |
20 | // Update any configuration parameters related to data item
21 | newConfiguration.item = item
22 |
23 | // Set content configuration in order to update custom content view
24 | contentConfiguration = newConfiguration
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Custom Cell - nib/SFSymbolNameListCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SFSymbolNameListCell.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 17/08/2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | class SFSymbolNameListCell: UICollectionViewListCell {
12 |
13 | var item: SFSymbolItem?
14 |
15 | override func updateConfiguration(using state: UICellConfigurationState) {
16 |
17 | // Create new configuration object and update it base on state
18 | var newConfiguration = SFSymbolNameContentConfiguration().updated(for: state)
19 |
20 | // Update any configuration parameters related to data item
21 | newConfiguration.name = item?.name
22 |
23 | // Set content configuration in order to update custom content view
24 | contentConfiguration = newConfiguration
25 |
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Lee Kah Seng
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Custom Cell/SFSymbolVerticalListCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SFSymbolVerticalListCell.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 02/08/2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | class SFSymbolVerticalListCell: UICollectionViewListCell {
12 |
13 | var item: SFSymbolItem?
14 |
15 | override func updateConfiguration(using state: UICellConfigurationState) {
16 |
17 | // Create a new background configuration so that
18 | // the cell must always have systemBackground background color
19 | // This will remove the gray background when cell is selected
20 | var newBgConfiguration = UIBackgroundConfiguration.listGroupedCell()
21 | newBgConfiguration.backgroundColor = .systemBackground
22 | backgroundConfiguration = newBgConfiguration
23 |
24 | // Create new configuration object and update it base on state
25 | var newConfiguration = SFSymbolContentConfiguration().updated(for: state)
26 |
27 | // Update any configuration parameters related to data item
28 | newConfiguration.name = item?.name
29 | newConfiguration.symbol = item?.image
30 |
31 | // Set content configuration in order to update custom content view
32 | contentConfiguration = newConfiguration
33 |
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "Icon-40.png",
5 | "idiom" : "iphone",
6 | "scale" : "2x",
7 | "size" : "20x20"
8 | },
9 | {
10 | "filename" : "Icon-60.png",
11 | "idiom" : "iphone",
12 | "scale" : "3x",
13 | "size" : "20x20"
14 | },
15 | {
16 | "filename" : "Icon-58.png",
17 | "idiom" : "iphone",
18 | "scale" : "2x",
19 | "size" : "29x29"
20 | },
21 | {
22 | "filename" : "Icon-87.png",
23 | "idiom" : "iphone",
24 | "scale" : "3x",
25 | "size" : "29x29"
26 | },
27 | {
28 | "filename" : "Icon-80.png",
29 | "idiom" : "iphone",
30 | "scale" : "2x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "filename" : "Icon-120.png",
35 | "idiom" : "iphone",
36 | "scale" : "3x",
37 | "size" : "40x40"
38 | },
39 | {
40 | "filename" : "Icon-121.png",
41 | "idiom" : "iphone",
42 | "scale" : "2x",
43 | "size" : "60x60"
44 | },
45 | {
46 | "filename" : "Icon-180.png",
47 | "idiom" : "iphone",
48 | "scale" : "3x",
49 | "size" : "60x60"
50 | },
51 | {
52 | "filename" : "Icon-1024.png",
53 | "idiom" : "ios-marketing",
54 | "scale" : "1x",
55 | "size" : "1024x1024"
56 | }
57 | ],
58 | "info" : {
59 | "author" : "xcode",
60 | "version" : 1
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 19/07/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | @UIApplicationMain
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SwiftSenpai-UICollectionView-List
2 | - Sample project for article '[Building a List with UICollectionView in Swift](https://swiftsenpai.com/development/uicollectionview-list-basic/)'.
3 | - Sample project for article '[UICollectionView List with Custom Cell and Custom Configuration](https://swiftsenpai.com/development/uicollectionview-list-custom-cell/)'.
4 | - Sample project for article '[Designing Custom UICollectionViewListCell in Interface Builder](https://swiftsenpai.com/development/custom-uicollectionviewlistcell-in-ib/)'.
5 | - Sample project for article '[Building an Expandable List Using UICollectionView: Part 1](https://swiftsenpai.com/development/collectionview-expandable-list-part1/)'.
6 | - Sample project for article '[Building an Expandable List Using UICollectionView: Part 2](https://swiftsenpai.com/development/collectionview-expandable-list-part2/)'.
7 | - Sample project for article '[The Modern Ways to Reload Your Table and Collection View Cells](https://swiftsenpai.com/development/modern-ways-reload-cells/)'
8 | - Sample project for article '[Declarative UICollectionView List Header and Footer](https://swiftsenpai.com/development/declarative-list-header-footer/)'
9 | - Sample project for article '[UICollectionView List with Interactive Custom Header](https://swiftsenpai.com/development/list-interactive-custom-header/)'
10 | - Sample project for article '[Replicate the Expandable Date Picker Using UICollectionView List](https://swiftsenpai.com/development/expandable-date-picker-list/)'
11 | - Sample project for article '[How to Reload the Diffable Section Header](https://swiftsenpai.com/development/reload-diffable-section-header/)'
12 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/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 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Custom Cell/SFSymbolContentConfiguration.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SFSymbolContentConfiguration.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 01/08/2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | struct SFSymbolContentConfiguration: UIContentConfiguration, Hashable {
12 |
13 | var name: String?
14 | var symbol: UIImage?
15 | var nameColor: UIColor?
16 | var symbolColor: UIColor?
17 | var symbolWeight: UIImage.SymbolWeight?
18 | var fontWeight: UIFont.Weight?
19 |
20 | func makeContentView() -> UIView & UIContentView {
21 | return SFSymbolVerticalContentView(configuration: self)
22 | }
23 |
24 | func updated(for state: UIConfigurationState) -> Self {
25 |
26 | // Perform update on parameters that does not related to cell's data itesm
27 |
28 | // Make sure we are dealing with instance of UICellConfigurationState
29 | guard let state = state as? UICellConfigurationState else {
30 | return self
31 | }
32 |
33 | // Updater self based on the current state
34 | var updatedConfiguration = self
35 | if state.isSelected {
36 | // Selected state
37 | updatedConfiguration.nameColor = .systemPink
38 | updatedConfiguration.symbolColor = .systemPink
39 | updatedConfiguration.fontWeight = .heavy
40 | updatedConfiguration.symbolWeight = .heavy
41 | } else {
42 | // Other states
43 | updatedConfiguration.nameColor = .systemBlue
44 | updatedConfiguration.symbolColor = .systemBlue
45 | updatedConfiguration.fontWeight = .regular
46 | updatedConfiguration.symbolWeight = .regular
47 | }
48 |
49 | return updatedConfiguration
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/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 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UIApplicationSupportsIndirectInputEvents
43 |
44 | UILaunchStoryboardName
45 | LaunchScreen
46 | UIMainStoryboardFile
47 | Main
48 | UIRequiredDeviceCapabilities
49 |
50 | armv7
51 |
52 | UISupportedInterfaceOrientations
53 |
54 | UIInterfaceOrientationPortrait
55 | UIInterfaceOrientationLandscapeLeft
56 | UIInterfaceOrientationLandscapeRight
57 |
58 | UISupportedInterfaceOrientations~ipad
59 |
60 | UIInterfaceOrientationPortrait
61 | UIInterfaceOrientationPortraitUpsideDown
62 | UIInterfaceOrientationLandscapeLeft
63 | UIInterfaceOrientationLandscapeRight
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 19/07/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let _ = (scene as? UIWindowScene) else { return }
20 | }
21 |
22 | func sceneDidDisconnect(_ scene: UIScene) {
23 | // Called as the scene is being released by the system.
24 | // This occurs shortly after the scene enters the background, or when its session is discarded.
25 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
26 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
27 | }
28 |
29 | func sceneDidBecomeActive(_ scene: UIScene) {
30 | // Called when the scene has moved from an inactive state to an active state.
31 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
32 | }
33 |
34 | func sceneWillResignActive(_ scene: UIScene) {
35 | // Called when the scene will move from an active state to an inactive state.
36 | // This may occur due to temporary interruptions (ex. an incoming phone call).
37 | }
38 |
39 | func sceneWillEnterForeground(_ scene: UIScene) {
40 | // Called as the scene transitions from the background to the foreground.
41 | // Use this method to undo the changes made on entering the background.
42 | }
43 |
44 | func sceneDidEnterBackground(_ scene: UIScene) {
45 | // Called as the scene transitions from the foreground to the background.
46 | // Use this method to save data, release shared resources, and store enough scene-specific state information
47 | // to restore the scene back to its current state.
48 | }
49 |
50 |
51 | }
52 |
53 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Custom Header & Footer/InteractiveHeader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // InteractiveHeader.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 29/10/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class InteractiveHeader: UICollectionReusableView {
11 |
12 | let titleLabel = UILabel()
13 | let infoButton = UIButton()
14 |
15 | var infoButtonDidTappedCallback: (() -> Void)?
16 |
17 | override init(frame: CGRect) {
18 | super.init(frame: frame)
19 | configure()
20 | }
21 |
22 | required init?(coder: NSCoder) {
23 | fatalError("Not implemented")
24 | }
25 |
26 | }
27 |
28 | extension InteractiveHeader {
29 |
30 | func configure() {
31 |
32 | // Add a stack view to section container
33 | let stackView = UIStackView()
34 | stackView.axis = .horizontal
35 | stackView.distribution = .fill
36 | addSubview(stackView)
37 | stackView.translatesAutoresizingMaskIntoConstraints = false
38 |
39 | // Adjust top anchor constant & priority
40 | let topAnchor = stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor, constant: 15)
41 | topAnchor.priority = UILayoutPriority(999)
42 |
43 | // Adjust bottom anchor constant & priority
44 | let bottomAnchor = stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor, constant: -10)
45 | bottomAnchor.priority = UILayoutPriority(999)
46 |
47 | NSLayoutConstraint.activate([
48 | stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
49 | stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
50 | topAnchor,
51 | bottomAnchor,
52 | ])
53 |
54 | // Setup label and add to stack view
55 | titleLabel.font = UIFont.preferredFont(forTextStyle: .headline)
56 | stackView.addArrangedSubview(titleLabel)
57 |
58 | // Set button image
59 | let largeConfig = UIImage.SymbolConfiguration(scale: .large)
60 | let infoImage = UIImage(systemName: "info.circle.fill", withConfiguration: largeConfig)?.withTintColor(.systemPink, renderingMode: .alwaysOriginal)
61 | infoButton.setImage(infoImage, for: .normal)
62 |
63 | // Set button action
64 | infoButton.addAction(UIAction(handler: { [unowned self] (_) in
65 | // Trigger callback when button tapped
66 | self.infoButtonDidTappedCallback?()
67 | }), for: .touchUpInside)
68 |
69 | // Add button to stack view
70 | stackView.addArrangedSubview(infoButton)
71 |
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Date Picker Replica/DatePickerContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatePickerContentView.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 01/01/2021.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | class DatePickerContentView: UIView, UIContentView {
12 |
13 | private let datePicker = UIDatePicker()
14 |
15 | private var currentConfiguration: DatePickerContentConfiguration!
16 | var configuration: UIContentConfiguration {
17 | get {
18 | currentConfiguration
19 | }
20 | set {
21 | guard let newConfiguration = newValue as? DatePickerContentConfiguration else {
22 | return
23 | }
24 |
25 | apply(configuration: newConfiguration)
26 | }
27 | }
28 |
29 | init(configuration: DatePickerContentConfiguration) {
30 | super.init(frame: .zero)
31 |
32 | // Create the content view UI
33 | setupAllViews()
34 |
35 | // Apply the configuration (set data to UI elements / define custom content view appearance)
36 | apply(configuration: configuration)
37 | }
38 |
39 | required init?(coder: NSCoder) {
40 | fatalError("init(coder:) has not been implemented")
41 | }
42 |
43 | }
44 |
45 | // MARK:- Private functions
46 | private extension DatePickerContentView {
47 |
48 | private func setupAllViews() {
49 |
50 | datePicker.preferredDatePickerStyle = .wheels
51 |
52 | addSubview(datePicker)
53 | datePicker.translatesAutoresizingMaskIntoConstraints = false
54 | NSLayoutConstraint.activate([
55 | datePicker.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
56 | datePicker.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
57 | datePicker.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
58 | datePicker.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
59 | ])
60 | }
61 |
62 | private func apply(configuration: DatePickerContentConfiguration) {
63 |
64 | // Only apply configuration if new configuration and current configuration are not the same
65 | guard currentConfiguration != configuration else {
66 | return
67 | }
68 |
69 | // Replace current configuration with new configuration
70 | currentConfiguration = configuration
71 |
72 | if case let DatePickerItem.picker(date, action) = configuration.item! {
73 | // Set date picker's date
74 | datePicker.date = date
75 |
76 | // Set date picker action (value changed)
77 | datePicker.addAction(action, for: .valueChanged)
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Custom Cell - nib/SFSymbolNameContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SFSymbolNameContentView.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 16/08/2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | class SFSymbolNameContentView: UIView, UIContentView {
12 |
13 | // 1
14 | // IBOutlet to connect to interface builder
15 | @IBOutlet var containerView: UIView!
16 | @IBOutlet var nameLabel: UILabel!
17 |
18 | private var currentConfiguration: SFSymbolNameContentConfiguration!
19 | var configuration: UIContentConfiguration {
20 | get {
21 | currentConfiguration
22 | }
23 | set {
24 | // Make sure the given configuration is of type SFSymbolNameContentConfiguration
25 | guard let newConfiguration = newValue as? SFSymbolNameContentConfiguration else {
26 | return
27 | }
28 |
29 | // Set name to label
30 | apply(configuration: newConfiguration)
31 | }
32 | }
33 |
34 | init(configuration: SFSymbolNameContentConfiguration) {
35 | super.init(frame: .zero)
36 |
37 | // 2
38 | // Load SFSymbolNameContentView.xib
39 | loadNib()
40 |
41 | // Set name to label
42 | apply(configuration: configuration)
43 | }
44 |
45 | required init?(coder: NSCoder) {
46 | fatalError("init(coder:) has not been implemented")
47 | }
48 |
49 | }
50 |
51 | private extension SFSymbolNameContentView {
52 |
53 | private func loadNib() {
54 |
55 | // 3
56 | // Load SFSymbolNameContentView.xib by making self as owner of SFSymbolNameContentView.xib
57 | Bundle.main.loadNibNamed("\(SFSymbolNameContentView.self)", owner: self, options: nil)
58 |
59 | // 4
60 | // Add containerView as subview and make it cover the entire content view
61 | addSubview(containerView)
62 | containerView.translatesAutoresizingMaskIntoConstraints = false
63 | NSLayoutConstraint.activate([
64 | containerView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0.0),
65 | containerView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0.0),
66 | containerView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0.0),
67 | containerView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0.0),
68 | ])
69 |
70 | // 5
71 | // Add border & rounded corner to name label
72 | nameLabel.layer.borderWidth = 1.5
73 | nameLabel.layer.borderColor = UIColor.systemPink.cgColor
74 | nameLabel.layer.cornerRadius = 5.0
75 | }
76 |
77 | private func apply(configuration: SFSymbolNameContentConfiguration) {
78 |
79 | // Only apply configuration if new configuration and current configuration are not the same
80 | guard currentConfiguration != configuration else {
81 | return
82 | }
83 |
84 | // Replace current configuration with new configuration
85 | currentConfiguration = configuration
86 |
87 | // Set name to label
88 | nameLabel.text = configuration.name
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Custom Cell - nib/SFSymbolNameContentView.xib:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Custom Cell/SFSymbolVerticalContentView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SFSymbolVerticalContentView.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 01/08/2020.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | class SFSymbolVerticalContentView: UIView, UIContentView {
12 |
13 | let nameLabel = UILabel()
14 | let symbolImageView = UIImageView()
15 |
16 | private var currentConfiguration: SFSymbolContentConfiguration!
17 | var configuration: UIContentConfiguration {
18 | get {
19 | currentConfiguration
20 | }
21 | set {
22 | // Make sure the given configuration is of type SFSymbolContentConfiguration
23 | guard let newConfiguration = newValue as? SFSymbolContentConfiguration else {
24 | return
25 | }
26 |
27 | // Apply the new configuration to SFSymbolVerticalContentView
28 | // also update currentConfiguration to newConfiguration
29 | apply(configuration: newConfiguration)
30 | }
31 | }
32 |
33 |
34 | init(configuration: SFSymbolContentConfiguration) {
35 | super.init(frame: .zero)
36 |
37 | // Create the content view UI
38 | setupAllViews()
39 |
40 | // Apply the configuration (set data to UI elements / define custom content view appearance)
41 | apply(configuration: configuration)
42 | }
43 |
44 | required init?(coder: NSCoder) {
45 | fatalError("init(coder:) has not been implemented")
46 | }
47 |
48 | }
49 |
50 | private extension SFSymbolVerticalContentView {
51 |
52 | private func setupAllViews() {
53 |
54 | // Add stack view to content view
55 | let stackView = UIStackView()
56 | stackView.axis = .vertical
57 | stackView.alignment = .fill
58 | stackView.distribution = .fill
59 | addSubview(stackView)
60 | stackView.translatesAutoresizingMaskIntoConstraints = false
61 | NSLayoutConstraint.activate([
62 | stackView.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor),
63 | stackView.trailingAnchor.constraint(equalTo: layoutMarginsGuide.trailingAnchor),
64 | stackView.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor),
65 | stackView.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor),
66 | ])
67 |
68 | // Add image view to stack view
69 | symbolImageView.contentMode = .scaleAspectFit
70 | stackView.addArrangedSubview(symbolImageView)
71 |
72 | // Add label to stack view
73 | nameLabel.textAlignment = .center
74 | stackView.addArrangedSubview(nameLabel)
75 | }
76 |
77 | private func apply(configuration: SFSymbolContentConfiguration) {
78 |
79 | // Only apply configuration if new configuration and current configuration are not the same
80 | guard currentConfiguration != configuration else {
81 | return
82 | }
83 |
84 | // Replace current configuration with new configuration
85 | currentConfiguration = configuration
86 |
87 | // Set data to UI elements
88 | nameLabel.text = configuration.name
89 | nameLabel.textColor = configuration.nameColor
90 |
91 | // Set font weight
92 | if let fontWeight = configuration.fontWeight {
93 | nameLabel.font = UIFont.systemFont(ofSize: nameLabel.font.pointSize,
94 | weight: fontWeight)
95 | }
96 |
97 | // Set symbol color & weight
98 | if
99 | let symbolColor = configuration.symbolColor,
100 | let symbolWeight = configuration.symbolWeight {
101 |
102 | let symbolConfig = UIImage.SymbolConfiguration(weight: symbolWeight)
103 | var symbol = configuration.symbol?.withConfiguration(symbolConfig)
104 | symbol = symbol?.withTintColor(symbolColor, renderingMode: .alwaysOriginal)
105 | symbolImageView.image = symbol
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Custom Cell - nib/NameListViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NameListViewController.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 17/08/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class NameListViewController: UIViewController {
11 |
12 | var collectionView: UICollectionView!
13 | var dataSource: UICollectionViewDiffableDataSource!
14 | var snapshot: NSDiffableDataSourceSnapshot!
15 |
16 | let dataItems = [
17 | SFSymbolItem(name: "mic"),
18 | SFSymbolItem(name: "mic.fill"),
19 | SFSymbolItem(name: "message"),
20 | SFSymbolItem(name: "message.fill"),
21 | SFSymbolItem(name: "sun.min"),
22 | SFSymbolItem(name: "sun.min.fill"),
23 | SFSymbolItem(name: "sunset"),
24 | SFSymbolItem(name: "sunset.fill"),
25 | SFSymbolItem(name: "pencil"),
26 | SFSymbolItem(name: "pencil.circle"),
27 | SFSymbolItem(name: "highlighter"),
28 | SFSymbolItem(name: "pencil.and.outline"),
29 | SFSymbolItem(name: "personalhotspot"),
30 | SFSymbolItem(name: "network"),
31 | SFSymbolItem(name: "icloud"),
32 | SFSymbolItem(name: "icloud.fill"),
33 | SFSymbolItem(name: "car"),
34 | SFSymbolItem(name: "car.fill"),
35 | SFSymbolItem(name: "bus"),
36 | SFSymbolItem(name: "bus.fill"),
37 | SFSymbolItem(name: "flame"),
38 | SFSymbolItem(name: "flame.fill"),
39 | SFSymbolItem(name: "bolt"),
40 | SFSymbolItem(name: "bolt.fill")
41 | ]
42 |
43 | override func viewDidLoad() {
44 | super.viewDidLoad()
45 |
46 | self.title = "Custom Cell Using Nib File"
47 |
48 | // Create list layout
49 | let layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
50 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
51 |
52 | // Create collection view with list layout
53 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
54 | view.addSubview(collectionView)
55 |
56 | // Make collection view take up the entire view
57 | collectionView.translatesAutoresizingMaskIntoConstraints = false
58 | NSLayoutConstraint.activate([
59 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
60 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
61 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
62 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
63 | ])
64 |
65 | // Create cell registration that define how data should be shown in a cell
66 | let cellRegistration = UICollectionView.CellRegistration { (cell, indexPath, item) in
67 |
68 | // For custom cell, we just need to assign the data item to the cell.
69 | // The custom cell's updateConfiguration(using:) method will assign the
70 | // content configuration to the cell
71 | cell.item = item
72 | }
73 |
74 | // Define data source
75 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
76 | (collectionView: UICollectionView, indexPath: IndexPath, identifier: SFSymbolItem) -> UICollectionViewCell? in
77 |
78 | // Dequeue reusable cell using cell registration (Reuse identifier no longer needed)
79 | let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration,
80 | for: indexPath,
81 | item: identifier)
82 |
83 | return cell
84 | }
85 |
86 | // Create a snapshot that define the current state of data source's data
87 | snapshot = NSDiffableDataSourceSnapshot()
88 | snapshot.appendSections([.main])
89 | snapshot.appendItems(dataItems, toSection: .main)
90 |
91 | // Display data on the collection view by applying the snapshot to data source
92 | dataSource.apply(snapshot, animatingDifferences: false)
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Custom Cell/CustomCellListViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomCellListViewController.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 01/08/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class CustomCellListViewController: UIViewController {
11 |
12 | var collectionView: UICollectionView!
13 | var dataSource: UICollectionViewDiffableDataSource!
14 | var snapshot: NSDiffableDataSourceSnapshot!
15 |
16 | let dataItems = [
17 | SFSymbolItem(name: "mic"),
18 | SFSymbolItem(name: "mic.fill"),
19 | SFSymbolItem(name: "message"),
20 | SFSymbolItem(name: "message.fill"),
21 | SFSymbolItem(name: "sun.min"),
22 | SFSymbolItem(name: "sun.min.fill"),
23 | SFSymbolItem(name: "sunset"),
24 | SFSymbolItem(name: "sunset.fill"),
25 | SFSymbolItem(name: "pencil"),
26 | SFSymbolItem(name: "pencil.circle"),
27 | SFSymbolItem(name: "highlighter"),
28 | SFSymbolItem(name: "pencil.and.outline"),
29 | SFSymbolItem(name: "personalhotspot"),
30 | SFSymbolItem(name: "network"),
31 | SFSymbolItem(name: "icloud"),
32 | SFSymbolItem(name: "icloud.fill"),
33 | SFSymbolItem(name: "car"),
34 | SFSymbolItem(name: "car.fill"),
35 | SFSymbolItem(name: "bus"),
36 | SFSymbolItem(name: "bus.fill"),
37 | SFSymbolItem(name: "flame"),
38 | SFSymbolItem(name: "flame.fill"),
39 | SFSymbolItem(name: "bolt"),
40 | SFSymbolItem(name: "bolt.fill")
41 | ]
42 |
43 | override func viewDidLoad() {
44 | super.viewDidLoad()
45 |
46 | self.title = "Custom Cell & Custom Congfiguration"
47 |
48 | // Create list layout
49 | let layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
50 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
51 |
52 | // Create collection view with list layout
53 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
54 | view.addSubview(collectionView)
55 |
56 | // Make collection view take up the entire view
57 | collectionView.translatesAutoresizingMaskIntoConstraints = false
58 | NSLayoutConstraint.activate([
59 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
60 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
61 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
62 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
63 | ])
64 |
65 | // Create cell registration that define how data should be shown in a cell
66 | let cellRegistration = UICollectionView.CellRegistration { (cell, indexPath, item) in
67 |
68 | // For custom cell, we just need to assign the data item to the cell.
69 | // The custom cell's updateConfiguration(using:) method will assign the
70 | // content configuration to the cell
71 | cell.item = item
72 | }
73 |
74 | // Define data source
75 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
76 | (collectionView: UICollectionView, indexPath: IndexPath, identifier: SFSymbolItem) -> UICollectionViewCell? in
77 |
78 | // Dequeue reusable cell using cell registration (Reuse identifier no longer needed)
79 | let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration,
80 | for: indexPath,
81 | item: identifier)
82 |
83 | return cell
84 | }
85 |
86 | // Create a snapshot that define the current state of data source's data
87 | snapshot = NSDiffableDataSourceSnapshot()
88 | snapshot.appendSections([.main])
89 | snapshot.appendItems(dataItems, toSection: .main)
90 |
91 | // Display data on the collection view by applying the snapshot to data source
92 | dataSource.apply(snapshot, animatingDifferences: false)
93 | }
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Reload Cell/ReloadValueTypeViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReloadValueTypeViewController.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 30/09/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class ReloadValueTypeViewController: UIViewController {
11 |
12 | struct Superhero: Hashable {
13 | var name: String
14 | }
15 |
16 | var collectionView: UICollectionView!
17 | var dataSource: UICollectionViewDiffableDataSource!
18 |
19 | let heroArray = [
20 | Superhero(name: "Spider-Man"),
21 | Superhero(name: "Superman"),
22 | Superhero(name: "Batman"),
23 | Superhero(name: "Captain America"),
24 | Superhero(name: "Thor"),
25 | Superhero(name: "Wonder Woman"),
26 | Superhero(name: "Iron Man"),
27 | ]
28 |
29 | override func viewDidLoad() {
30 | super.viewDidLoad()
31 |
32 | self.title = "Reload Value Type"
33 |
34 | // MARK: Setup list layout
35 | let layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
36 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
37 |
38 | // MARK: Setup collection view
39 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
40 | collectionView.delegate = self
41 | view.addSubview(collectionView)
42 |
43 | // MARK: Make collection view take up the entire view
44 | collectionView.translatesAutoresizingMaskIntoConstraints = false
45 | NSLayoutConstraint.activate([
46 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
47 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
48 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
49 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
50 | ])
51 |
52 | // MARK: Cell registration
53 | let cellRegistration = UICollectionView.CellRegistration { (cell, indexPath, item) in
54 |
55 | // Define how data should be shown using content configuration
56 | var content = cell.defaultContentConfiguration()
57 | content.text = item.name
58 |
59 | // Assign content configuration to cell
60 | cell.contentConfiguration = content
61 | }
62 |
63 | // MARK: Setup data source
64 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { (collectionView, indexPath, hero) -> UICollectionViewCell? in
65 |
66 | let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration,
67 | for: indexPath,
68 | item: hero)
69 |
70 | return cell
71 | })
72 |
73 | // MARK: Data source snapshot
74 | var snapshot = NSDiffableDataSourceSnapshot()
75 | snapshot.appendSections([.main])
76 | snapshot.appendItems(heroArray, toSection: .main)
77 | dataSource.apply(snapshot, animatingDifferences: false)
78 | }
79 |
80 | }
81 |
82 | extension ReloadValueTypeViewController: UICollectionViewDelegate {
83 |
84 | func collectionView(_ collectionView: UICollectionView,
85 | didSelectItemAt indexPath: IndexPath) {
86 |
87 | // Get selected hero using index path
88 | guard let selectedHero = dataSource.itemIdentifier(for: indexPath) else {
89 | collectionView.deselectItem(at: indexPath, animated: true)
90 | return
91 | }
92 |
93 | // Create a new copy of selectedHero & update it
94 | var updatedHero = selectedHero
95 | updatedHero.name = updatedHero.name.appending(" ★")
96 |
97 | // Create a new copy of data source snapshot for modification
98 | var newSnapshot = dataSource.snapshot()
99 |
100 | // Replacing selectedHero with updatedHero
101 | newSnapshot.insertItems([updatedHero], beforeItem: selectedHero)
102 | newSnapshot.deleteItems([selectedHero])
103 |
104 | // Apply snapshot changes to data source
105 | dataSource.apply(newSnapshot)
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Reload Cell/ReloadReferenceTypeViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReloadReferenceTypeViewController.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 30/09/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class ReloadReferenceTypeViewController: UIViewController {
11 |
12 | class Superhero: Hashable {
13 |
14 | var name: String
15 |
16 | init(name: String) {
17 | self.name = name
18 | }
19 |
20 | // MARK: Hashable
21 | func hash(into hasher: inout Hasher) {
22 | hasher.combine(name)
23 | }
24 |
25 | static func == (lhs: ReloadReferenceTypeViewController.Superhero,
26 | rhs: ReloadReferenceTypeViewController.Superhero) -> Bool {
27 | lhs.name == rhs.name
28 | }
29 | }
30 |
31 | var collectionView: UICollectionView!
32 | var dataSource: UICollectionViewDiffableDataSource!
33 |
34 | let heroArray = [
35 | Superhero(name: "Spider-Man"),
36 | Superhero(name: "Superman"),
37 | Superhero(name: "Batman"),
38 | Superhero(name: "Captain America"),
39 | Superhero(name: "Thor"),
40 | Superhero(name: "Wonder Woman"),
41 | Superhero(name: "Iron Man"),
42 | ]
43 |
44 | override func viewDidLoad() {
45 | super.viewDidLoad()
46 |
47 | self.title = "Reload Reference Type"
48 |
49 | // MARK: Setup list layout
50 | let layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
51 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
52 |
53 | // MARK: Setup collection view
54 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
55 | collectionView.delegate = self
56 | view.addSubview(collectionView)
57 |
58 | // MARK: Make collection view take up the entire view
59 | collectionView.translatesAutoresizingMaskIntoConstraints = false
60 | NSLayoutConstraint.activate([
61 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
62 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
63 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
64 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
65 | ])
66 |
67 | // MARK: Cell registration
68 | let cellRegistration = UICollectionView.CellRegistration { (cell, indexPath, item) in
69 |
70 | // Define how data should be shown using content configuration
71 | var content = cell.defaultContentConfiguration()
72 | content.text = item.name
73 |
74 | // Assign content configuration to cell
75 | cell.contentConfiguration = content
76 | }
77 |
78 | // MARK: Setup data source
79 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { (collectionView, indexPath, hero) -> UICollectionViewCell? in
80 |
81 | let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration,
82 | for: indexPath,
83 | item: hero)
84 |
85 | return cell
86 | })
87 |
88 | // MARK: Data source snapshot
89 | var snapshot = NSDiffableDataSourceSnapshot()
90 | snapshot.appendSections([.main])
91 | snapshot.appendItems(heroArray, toSection: .main)
92 | dataSource.apply(snapshot, animatingDifferences: false)
93 | }
94 |
95 | }
96 |
97 | extension ReloadReferenceTypeViewController: UICollectionViewDelegate {
98 |
99 | func collectionView(_ collectionView: UICollectionView,
100 | didSelectItemAt indexPath: IndexPath) {
101 |
102 | // Get selected hero using index path
103 | guard let selectedHero = dataSource.itemIdentifier(for: indexPath) else {
104 | collectionView.deselectItem(at: indexPath, animated: true)
105 | return
106 | }
107 |
108 | // Update selectedHero
109 | selectedHero.name = selectedHero.name.appending(" ★")
110 |
111 | // Create a new copy of data source snapshot for modification
112 | var newSnapshot = dataSource.snapshot()
113 |
114 | // Reload selectedHero in newSnapshot
115 | newSnapshot.reloadItems([selectedHero])
116 |
117 | // Apply snapshot changes to data source
118 | dataSource.apply(newSnapshot)
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Custom Header & Footer/CustomHeaderFooterViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CustomHeaderFooterViewController.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 27/10/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class CustomHeaderFooterViewController: UIViewController {
11 |
12 | let modelObjects = [
13 |
14 | HeaderItem(title: "Devices", symbols: [
15 | SFSymbolItem(name: "iphone.homebutton"),
16 | SFSymbolItem(name: "pc"),
17 | SFSymbolItem(name: "headphones"),
18 | ]),
19 |
20 | HeaderItem(title: "Weather", symbols: [
21 | SFSymbolItem(name: "sun.min"),
22 | SFSymbolItem(name: "sunset.fill"),
23 | ]),
24 |
25 | HeaderItem(title: "Nature", symbols: [
26 | SFSymbolItem(name: "drop.fill"),
27 | SFSymbolItem(name: "flame"),
28 | SFSymbolItem(name: "bolt.circle.fill"),
29 | SFSymbolItem(name: "tortoise.fill"),
30 | ]),
31 | ]
32 |
33 | var collectionView: UICollectionView!
34 | var dataSource: UICollectionViewDiffableDataSource!
35 |
36 | override func viewDidLoad() {
37 | super.viewDidLoad()
38 |
39 | self.title = "Custom Header & Footer"
40 |
41 | // MARK: Create list layout
42 | var layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
43 | layoutConfig.headerMode = .supplementary
44 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
45 |
46 | // MARK: Configure collection view
47 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
48 | view.addSubview(collectionView)
49 |
50 | // Make collection view take up the entire view
51 | collectionView.translatesAutoresizingMaskIntoConstraints = false
52 | NSLayoutConstraint.activate([
53 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
54 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
55 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
56 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
57 | ])
58 |
59 | // MARK: Cell registration
60 | let symbolCellRegistration = UICollectionView.CellRegistration {
61 | (cell, indexPath, symbolItem) in
62 |
63 | // Configure cell content
64 | var configuration = cell.defaultContentConfiguration()
65 | configuration.image = symbolItem.image
66 | configuration.text = symbolItem.name
67 | cell.contentConfiguration = configuration
68 | }
69 |
70 | // MARK: Initialize data source
71 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
72 | (collectionView, indexPath, symbolItem) -> UICollectionViewCell? in
73 |
74 | // Dequeue symbol cell
75 | let cell = collectionView.dequeueConfiguredReusableCell(using: symbolCellRegistration,
76 | for: indexPath,
77 | item: symbolItem)
78 | return cell
79 | }
80 |
81 | // MARK: Supplementary view registration
82 | let headerRegistration = UICollectionView.SupplementaryRegistration
83 | (elementKind: UICollectionView.elementKindSectionHeader) {
84 | [unowned self] (headerView, elementKind, indexPath) in
85 |
86 | // Obtain header item using index path
87 | let headerItem = self.dataSource.snapshot().sectionIdentifiers[indexPath.section]
88 |
89 | headerView.titleLabel.text = headerItem.title
90 | headerView.infoButtonDidTappedCallback = { [unowned self] in
91 |
92 | // Show an alert when user tap on infoButton
93 | let symbolCount = headerItem.symbols.count
94 | let alert = UIAlertController(title: "Info", message: "This section has \(symbolCount) symbols.", preferredStyle: .alert)
95 | let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
96 | alert.addAction(okAction)
97 |
98 | self.present(alert, animated: true)
99 | }
100 | }
101 |
102 | // MARK: Define supplementary view provider
103 | dataSource.supplementaryViewProvider = { [unowned self]
104 | (collectionView, elementKind, indexPath) -> UICollectionReusableView? in
105 |
106 | // Dequeue header view
107 | return self.collectionView.dequeueConfiguredReusableSupplementary(
108 | using: headerRegistration, for: indexPath)
109 | }
110 |
111 | // MARK: Setup snapshot
112 | var dataSourceSnapshot = NSDiffableDataSourceSnapshot()
113 |
114 | // Create collection view section based on number of HeaderItem in modelObjects
115 | dataSourceSnapshot.appendSections(modelObjects)
116 |
117 | // Loop through each header item to append symbols to their respective section
118 | for headerItem in modelObjects {
119 | dataSourceSnapshot.appendItems(headerItem.symbols, toSection: headerItem)
120 | }
121 |
122 | dataSource.apply(dataSourceSnapshot, animatingDifferences: false)
123 |
124 | }
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Expandable List/SingleSecExpandableListViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SingleSecExpandableListViewController.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 05/09/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class SingleSecExpandableListViewController: UIViewController {
11 |
12 | let modelObjects = [
13 |
14 | HeaderItem(title: "Communication", symbols: [
15 | SFSymbolItem(name: "mic"),
16 | SFSymbolItem(name: "mic.fill"),
17 | SFSymbolItem(name: "message"),
18 | SFSymbolItem(name: "message.fill"),
19 | ]),
20 |
21 | HeaderItem(title: "Weather", symbols: [
22 | SFSymbolItem(name: "sun.min"),
23 | SFSymbolItem(name: "sun.min.fill"),
24 | SFSymbolItem(name: "sunset"),
25 | SFSymbolItem(name: "sunset.fill"),
26 | ]),
27 |
28 | HeaderItem(title: "Objects & Tools", symbols: [
29 | SFSymbolItem(name: "pencil"),
30 | SFSymbolItem(name: "pencil.circle"),
31 | SFSymbolItem(name: "highlighter"),
32 | SFSymbolItem(name: "pencil.and.outline"),
33 | ]),
34 |
35 | ]
36 |
37 | var collectionView: UICollectionView!
38 | var dataSource: UICollectionViewDiffableDataSource!
39 |
40 | override func viewDidLoad() {
41 | super.viewDidLoad()
42 |
43 | self.title = "Single Section"
44 |
45 | // MARK: Create list layout
46 | let layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
47 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
48 |
49 | // MARK: Configure collection view
50 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
51 | view.addSubview(collectionView)
52 |
53 | // Make collection view take up the entire view
54 | collectionView.translatesAutoresizingMaskIntoConstraints = false
55 | NSLayoutConstraint.activate([
56 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
57 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
58 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
59 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
60 | ])
61 |
62 | // MARK: Cell registration
63 | let headerCellRegistration = UICollectionView.CellRegistration {
64 | (cell, indexPath, headerItem) in
65 |
66 | // Set headerItem's data to cell
67 | var content = cell.defaultContentConfiguration()
68 | content.text = headerItem.title
69 | cell.contentConfiguration = content
70 |
71 | // Add outline disclosure accessory
72 | // With this accessory, the header cell's children will expand / collapse when the header cell is tapped.
73 | let headerDisclosureOption = UICellAccessory.OutlineDisclosureOptions(style: .header)
74 | cell.accessories = [.outlineDisclosure(options:headerDisclosureOption)]
75 | }
76 |
77 | let symbolCellRegistration = UICollectionView.CellRegistration {
78 | (cell, indexPath, symbolItem) in
79 |
80 | // Set symbolItem's data to cell
81 | var content = cell.defaultContentConfiguration()
82 | content.image = symbolItem.image
83 | content.text = symbolItem.name
84 | cell.contentConfiguration = content
85 | }
86 |
87 | // MARK: Initialize data source
88 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
89 | (collectionView, indexPath, listItem) -> UICollectionViewCell? in
90 |
91 | switch listItem {
92 | case .header(let headerItem):
93 |
94 | // Dequeue header cell
95 | let cell = collectionView.dequeueConfiguredReusableCell(using: headerCellRegistration,
96 | for: indexPath,
97 | item: headerItem)
98 | return cell
99 |
100 | case .symbol(let symbolItem):
101 |
102 | // Dequeue symbol cell
103 | let cell = collectionView.dequeueConfiguredReusableCell(using: symbolCellRegistration,
104 | for: indexPath,
105 | item: symbolItem)
106 | return cell
107 | }
108 | }
109 |
110 | // MARK: Setup snapshots
111 | var dataSourceSnapshot = NSDiffableDataSourceSnapshot()
112 |
113 | // Create a section in the data source snapshot
114 | dataSourceSnapshot.appendSections([.main])
115 | dataSource.apply(dataSourceSnapshot)
116 |
117 | // Create a section snapshot for main section
118 | var sectionSnapshot = NSDiffableDataSourceSectionSnapshot()
119 |
120 | for headerItem in modelObjects {
121 |
122 | // Create a header ListItem & append as parent
123 | let headerListItem = ListItem.header(headerItem)
124 | sectionSnapshot.append([headerListItem])
125 |
126 | // Create an array of symbol ListItem & append as children of headerListItem
127 | let symbolListItemArray = headerItem.symbols.map { ListItem.symbol($0) }
128 | sectionSnapshot.append(symbolListItemArray, to: headerListItem)
129 |
130 | // Expand this section by default
131 | sectionSnapshot.expand([headerListItem])
132 | }
133 |
134 | // Apply section snapshot to main section
135 | dataSource.apply(sectionSnapshot, to: .main, animatingDifferences: false)
136 | }
137 | }
138 |
139 |
140 |
141 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Expandable List/MultiSecExpandableListViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MultiSecExpandableListViewController.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 04/09/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class MultiSecExpandableListViewController: UIViewController {
11 |
12 | let modelObjects = [
13 |
14 | HeaderItem(title: "Communication", symbols: [
15 | SFSymbolItem(name: "mic"),
16 | SFSymbolItem(name: "mic.fill"),
17 | SFSymbolItem(name: "message"),
18 | SFSymbolItem(name: "message.fill"),
19 | ]),
20 |
21 | HeaderItem(title: "Weather", symbols: [
22 | SFSymbolItem(name: "sun.min"),
23 | SFSymbolItem(name: "sun.min.fill"),
24 | SFSymbolItem(name: "sunset"),
25 | SFSymbolItem(name: "sunset.fill"),
26 | ]),
27 |
28 | HeaderItem(title: "Objects & Tools", symbols: [
29 | SFSymbolItem(name: "pencil"),
30 | SFSymbolItem(name: "pencil.circle"),
31 | SFSymbolItem(name: "highlighter"),
32 | SFSymbolItem(name: "pencil.and.outline"),
33 | ]),
34 |
35 | ]
36 |
37 | var collectionView: UICollectionView!
38 | var dataSource: UICollectionViewDiffableDataSource!
39 |
40 | override func viewDidLoad() {
41 | super.viewDidLoad()
42 |
43 | self.title = "Multi Section"
44 |
45 | // MARK: Create list layout
46 | var layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
47 | layoutConfig.headerMode = .firstItemInSection
48 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
49 |
50 | // MARK: Configure collection view
51 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
52 | view.addSubview(collectionView)
53 |
54 | // Make collection view take up the entire view
55 | collectionView.translatesAutoresizingMaskIntoConstraints = false
56 | NSLayoutConstraint.activate([
57 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
58 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
59 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
60 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
61 | ])
62 |
63 | // MARK: Cell registration
64 | let headerCellRegistration = UICollectionView.CellRegistration {
65 | (cell, indexPath, headerItem) in
66 |
67 | // Set headerItem's data to cell
68 | var content = cell.defaultContentConfiguration()
69 | content.text = headerItem.title
70 | cell.contentConfiguration = content
71 |
72 | // Add outline disclosure accessory
73 | // With this accessory, the header cell's children will expand / collapse when the header cell is tapped.
74 | let headerDisclosureOption = UICellAccessory.OutlineDisclosureOptions(style: .header)
75 | cell.accessories = [.outlineDisclosure(options:headerDisclosureOption)]
76 | }
77 |
78 | let symbolCellRegistration = UICollectionView.CellRegistration {
79 | (cell, indexPath, symbolItem) in
80 |
81 | // Set symbolItem's data to cell
82 | var content = cell.defaultContentConfiguration()
83 | content.image = symbolItem.image
84 | content.text = symbolItem.name
85 | cell.contentConfiguration = content
86 | }
87 |
88 | // MARK: Initialize data source
89 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
90 | (collectionView, indexPath, listItem) -> UICollectionViewCell? in
91 |
92 | switch listItem {
93 | case .header(let headerItem):
94 |
95 | // Dequeue header cell
96 | let cell = collectionView.dequeueConfiguredReusableCell(using: headerCellRegistration,
97 | for: indexPath,
98 | item: headerItem)
99 | return cell
100 |
101 | case .symbol(let symbolItem):
102 |
103 | // Dequeue symbol cell
104 | let cell = collectionView.dequeueConfiguredReusableCell(using: symbolCellRegistration,
105 | for: indexPath,
106 | item: symbolItem)
107 | return cell
108 | }
109 | }
110 |
111 | // MARK: Setup snapshots
112 | var dataSourceSnapshot = NSDiffableDataSourceSnapshot()
113 |
114 | // Create collection view section based on number of HeaderItem in modelObjects
115 | dataSourceSnapshot.appendSections(modelObjects)
116 | dataSource.apply(dataSourceSnapshot)
117 |
118 | // Loop through each header item so that we can create a section snapshot for each respective header item.
119 | for headerItem in modelObjects {
120 |
121 | // Create a section snapshot
122 | var sectionSnapshot = NSDiffableDataSourceSectionSnapshot()
123 |
124 | // Create a header ListItem & append as parent
125 | let headerListItem = ListItem.header(headerItem)
126 | sectionSnapshot.append([headerListItem])
127 |
128 | // Create an array of symbol ListItem & append as child of headerListItem
129 | let symbolListItemArray = headerItem.symbols.map { ListItem.symbol($0) }
130 | sectionSnapshot.append(symbolListItemArray, to: headerListItem)
131 |
132 | // Expand this section by default
133 | sectionSnapshot.expand([headerListItem])
134 |
135 | // Apply section snapshot to the respective collection view section
136 | dataSource.apply(sectionSnapshot, to: headerItem, animatingDifferences: false)
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Declarative Header & Footer/DeclarativeHeaderFooterViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DeclarativeHeaderFooterViewController.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 17/10/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class DeclarativeHeaderFooterViewController: UIViewController {
11 |
12 | let modelObjects = [
13 |
14 | HeaderItem(title: "Devices", symbols: [
15 | SFSymbolItem(name: "iphone.homebutton"),
16 | SFSymbolItem(name: "pc"),
17 | SFSymbolItem(name: "headphones"),
18 | ]),
19 |
20 | HeaderItem(title: "Weather", symbols: [
21 | SFSymbolItem(name: "sun.min"),
22 | SFSymbolItem(name: "sunset.fill"),
23 | ]),
24 |
25 | HeaderItem(title: "Nature", symbols: [
26 | SFSymbolItem(name: "drop.fill"),
27 | SFSymbolItem(name: "flame"),
28 | SFSymbolItem(name: "bolt.circle.fill"),
29 | SFSymbolItem(name: "tortoise.fill"),
30 | ]),
31 | ]
32 |
33 | var collectionView: UICollectionView!
34 | var dataSource: UICollectionViewDiffableDataSource!
35 |
36 | override func viewDidLoad() {
37 | super.viewDidLoad()
38 |
39 | self.title = "Declarative Header & Footer"
40 |
41 | // MARK: Create list layout
42 | var layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
43 | layoutConfig.headerMode = .supplementary
44 | layoutConfig.footerMode = .supplementary
45 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
46 |
47 | // MARK: Configure collection view
48 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
49 | view.addSubview(collectionView)
50 |
51 | // Make collection view take up the entire view
52 | collectionView.translatesAutoresizingMaskIntoConstraints = false
53 | NSLayoutConstraint.activate([
54 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
55 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
56 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
57 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
58 | ])
59 |
60 | // MARK: Cell registration
61 | let symbolCellRegistration = UICollectionView.CellRegistration {
62 | (cell, indexPath, symbolItem) in
63 |
64 | // Configure cell content
65 | var configuration = cell.defaultContentConfiguration()
66 | configuration.image = symbolItem.image
67 | configuration.text = symbolItem.name
68 | cell.contentConfiguration = configuration
69 | }
70 |
71 | // MARK: Initialize data source
72 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
73 | (collectionView, indexPath, symbolItem) -> UICollectionViewCell? in
74 |
75 | // Dequeue symbol cell
76 | let cell = collectionView.dequeueConfiguredReusableCell(using: symbolCellRegistration,
77 | for: indexPath,
78 | item: symbolItem)
79 | return cell
80 | }
81 |
82 | // MARK: Supplementary view registration
83 | let headerRegistration = UICollectionView.SupplementaryRegistration
84 | (elementKind: UICollectionView.elementKindSectionHeader) {
85 | [unowned self] (headerView, elementKind, indexPath) in
86 |
87 | // Obtain header item using index path
88 | let headerItem = self.dataSource.snapshot().sectionIdentifiers[indexPath.section]
89 |
90 | // Configure header view content based on headerItem
91 | var configuration = headerView.defaultContentConfiguration()
92 | configuration.text = headerItem.title
93 |
94 | // Customize header appearance to make it more eye-catching
95 | configuration.textProperties.font = .boldSystemFont(ofSize: 16)
96 | configuration.textProperties.color = .systemBlue
97 | configuration.directionalLayoutMargins = .init(top: 20.0, leading: 0.0, bottom: 10.0, trailing: 0.0)
98 |
99 | // Apply the configuration to header view
100 | headerView.contentConfiguration = configuration
101 | }
102 |
103 | let footerRegistration = UICollectionView.SupplementaryRegistration
104 | (elementKind: UICollectionView.elementKindSectionFooter) {
105 | [unowned self] (footerView, elementKind, indexPath) in
106 |
107 | let headerItem = self.dataSource.snapshot().sectionIdentifiers[indexPath.section]
108 | let symbolCount = headerItem.symbols.count
109 |
110 | // Configure footer view content
111 | var configuration = footerView.defaultContentConfiguration()
112 | configuration.text = "Symbol count: \(symbolCount)"
113 | footerView.contentConfiguration = configuration
114 | }
115 |
116 | // MARK: Define supplementary view provider
117 | dataSource.supplementaryViewProvider = { [unowned self]
118 | (collectionView, elementKind, indexPath) -> UICollectionReusableView? in
119 |
120 | if elementKind == UICollectionView.elementKindSectionHeader {
121 |
122 | // Dequeue header view
123 | return self.collectionView.dequeueConfiguredReusableSupplementary(
124 | using: headerRegistration, for: indexPath)
125 |
126 | } else {
127 |
128 | // Dequeue footer view
129 | return self.collectionView.dequeueConfiguredReusableSupplementary(
130 | using: footerRegistration, for: indexPath)
131 | }
132 | }
133 |
134 | // MARK: Setup snapshot
135 | var dataSourceSnapshot = NSDiffableDataSourceSnapshot()
136 |
137 | // Create collection view section based on number of HeaderItem in modelObjects
138 | dataSourceSnapshot.appendSections(modelObjects)
139 |
140 | // Loop through each header item to append symbols to their respective section
141 | for headerItem in modelObjects {
142 | dataSourceSnapshot.appendItems(headerItem.symbols, toSection: headerItem)
143 | }
144 |
145 | dataSource.apply(dataSourceSnapshot, animatingDifferences: false)
146 |
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Date Picker Replica/DatePickerReplicaViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DatePickerReplicaViewController.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 01/01/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | enum DatePickerItem: Hashable {
11 | case header(Date)
12 | case picker(Date, UIAction)
13 | }
14 |
15 | class DatePickerReplicaViewController: UIViewController {
16 |
17 | var collectionView: UICollectionView!
18 | var dataSource: UICollectionViewDiffableDataSource!
19 | var snapshot: NSDiffableDataSourceSnapshot!
20 |
21 | let dateFormatter = DateFormatter()
22 |
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 |
26 | self.title = "Date Picker Replica"
27 |
28 | dateFormatter.dateFormat = "EEEE, d MMM yyyy, hh:mm a"
29 |
30 | // Create list layout
31 | let layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
32 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
33 |
34 | // Create collection view with list layout
35 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
36 | view.addSubview(collectionView)
37 |
38 | // Make collection view take up the entire view
39 | collectionView.translatesAutoresizingMaskIntoConstraints = false
40 | NSLayoutConstraint.activate([
41 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
42 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
43 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
44 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
45 | ])
46 |
47 | let headerCellRegistration = UICollectionView.CellRegistration { [unowned self]
48 | (cell, indexPath, item) in
49 |
50 | // Extract date from DatePickerItem
51 | if case let DatePickerItem.header(date) = item {
52 |
53 | // Show date on cell
54 | var content = cell.defaultContentConfiguration()
55 | content.text = dateFormatter.string(from: date)
56 | cell.contentConfiguration = content
57 | }
58 |
59 | // Add outline disclosure accessory
60 | // With this accessory, the header cell's children will expand / collapse when the header cell is tapped.
61 | let headerDisclosureOption = UICellAccessory.OutlineDisclosureOptions(style: .header)
62 | cell.accessories = [.outlineDisclosure(options:headerDisclosureOption)]
63 | }
64 |
65 | let pickerCellRegistration = UICollectionView.CellRegistration { (cell, indexPath, item) in
66 |
67 | // Set `DatePickerItem` to date picker cell
68 | cell.item = item
69 | }
70 |
71 | // Define data source
72 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
73 | (collectionView, indexPath, datePickerItem) -> UICollectionViewCell? in
74 |
75 | switch datePickerItem {
76 | case .header(_):
77 |
78 | // Dequeue header cell
79 | let cell = collectionView.dequeueConfiguredReusableCell(using: headerCellRegistration,
80 | for: indexPath,
81 | item: datePickerItem)
82 | return cell
83 |
84 | case .picker(_, _):
85 |
86 | // Dequeue symbol cell
87 | let cell = collectionView.dequeueConfiguredReusableCell(using: pickerCellRegistration,
88 | for: indexPath,
89 | item: datePickerItem)
90 | return cell
91 | }
92 | }
93 |
94 | var dataSourceSnapshot = NSDiffableDataSourceSnapshot()
95 |
96 | // Define collection view section
97 | dataSourceSnapshot.appendSections([.main])
98 | dataSource.apply(dataSourceSnapshot)
99 |
100 | // Create a section snapshot
101 | var sectionSnapshot = NSDiffableDataSourceSectionSnapshot()
102 |
103 | // We will show current date in date picker & header cell
104 | let now = Date()
105 |
106 | // Define header and set it as parent
107 | let header = DatePickerItem.header(now)
108 | sectionSnapshot.append([header])
109 |
110 | // Define picker and set it as child of `header`
111 | let action = UIAction(handler: { [unowned self] (action) in
112 |
113 | // Make sure sender is a date picker
114 | guard let picker = action.sender as? UIDatePicker else {
115 | return
116 | }
117 |
118 | // Reload header cell with date picker's date
119 | reloadHeader(with: picker.date)
120 | })
121 | let picker = DatePickerItem.picker(now, action)
122 | sectionSnapshot.append([picker], to: header)
123 |
124 | // Expand this section by default
125 | sectionSnapshot.expand([header])
126 |
127 | // Apply section snapshot to main section
128 | dataSource.apply(sectionSnapshot, to: .main, animatingDifferences: false)
129 |
130 | }
131 |
132 | }
133 |
134 | private extension DatePickerReplicaViewController {
135 |
136 | private func reloadHeader(with date: Date) {
137 |
138 | let sectionSnapshot = dataSource.snapshot(for: .main)
139 |
140 | // Obtain reference to the header item and date picker item
141 | guard
142 | let oldHeaderItem = sectionSnapshot.rootItems.first,
143 | let datePickerItem = sectionSnapshot.snapshot(of: oldHeaderItem).items.first else {
144 | return
145 | }
146 |
147 | // Create new header item with new date
148 | let newHeaderItem = DatePickerItem.header(date)
149 |
150 | // Create a new copy of section snapshot for modification
151 | var newSectionSnapshot = sectionSnapshot
152 |
153 | // Replace the header item (by insert new item and then delete old item)
154 | newSectionSnapshot.insert([newHeaderItem], before: oldHeaderItem)
155 |
156 | // Important: Delete `oldHeaderItem` must come before append `datePickerItem` to avoid 'NSInternalInconsistencyException' with reason: 'Supplied identifiers are not unique.'
157 | newSectionSnapshot.delete([oldHeaderItem])
158 |
159 | // Reconstruct section snapshot by appending `datePickerItem` to `newHeaderItem`
160 | newSectionSnapshot.append([datePickerItem], to: newHeaderItem)
161 |
162 | // Expand the section
163 | newSectionSnapshot.expand([newHeaderItem])
164 |
165 | // Apply new section snapshot to `main` section
166 | dataSource.apply(newSectionSnapshot, to: .main, animatingDifferences: false)
167 |
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Basic List/BasicListViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // BasicListViewController.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 19/07/2020.
6 | //
7 |
8 | import UIKit
9 |
10 | class BasicListViewController: UIViewController {
11 |
12 | var collectionView: UICollectionView!
13 | var dataSource: UICollectionViewDiffableDataSource!
14 | var snapshot: NSDiffableDataSourceSnapshot!
15 |
16 | let dataItems = [
17 | SFSymbolItem(name: "mic"),
18 | SFSymbolItem(name: "mic.fill"),
19 | SFSymbolItem(name: "message"),
20 | SFSymbolItem(name: "message.fill"),
21 | SFSymbolItem(name: "sun.min"),
22 | SFSymbolItem(name: "sun.min.fill"),
23 | SFSymbolItem(name: "sunset"),
24 | SFSymbolItem(name: "sunset.fill"),
25 | SFSymbolItem(name: "pencil"),
26 | SFSymbolItem(name: "pencil.circle"),
27 | SFSymbolItem(name: "highlighter"),
28 | SFSymbolItem(name: "pencil.and.outline"),
29 | SFSymbolItem(name: "personalhotspot"),
30 | SFSymbolItem(name: "network"),
31 | SFSymbolItem(name: "icloud"),
32 | SFSymbolItem(name: "icloud.fill"),
33 | SFSymbolItem(name: "car"),
34 | SFSymbolItem(name: "car.fill"),
35 | SFSymbolItem(name: "bus"),
36 | SFSymbolItem(name: "bus.fill"),
37 | SFSymbolItem(name: "flame"),
38 | SFSymbolItem(name: "flame.fill"),
39 | SFSymbolItem(name: "bolt"),
40 | SFSymbolItem(name: "bolt.fill")
41 | ]
42 |
43 | override func viewDidLoad() {
44 | super.viewDidLoad()
45 |
46 | self.title = "Basic List"
47 |
48 | // Create list layout
49 | var layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
50 |
51 | // Define right-to-left swipe action
52 | layoutConfig.trailingSwipeActionsConfigurationProvider = { [unowned self] (indexPath) in
53 |
54 | guard let item = dataSource.itemIdentifier(for: indexPath) else {
55 | return nil
56 | }
57 |
58 | // Create action 1
59 | let action1 = UIContextualAction(style: .normal, title: "Action 1") { [unowned self] (action, view, completion) in
60 |
61 | // Handle swipe action by showing an alert message
62 | handleSwipe(for: action, item: item)
63 |
64 | // Trigger the action completion handler
65 | completion(true)
66 | }
67 | action1.backgroundColor = .systemGreen
68 |
69 | // Create action 2
70 | let action2 = UIContextualAction(style: .normal, title: "Action 2") { [unowned self] (action, view, completion) in
71 |
72 | // Handle swipe action by showing alert message
73 | handleSwipe(for: action, item: item)
74 |
75 | // Trigger the action completion handler
76 | completion(true)
77 | }
78 | action2.backgroundColor = .systemPink
79 |
80 | // Use all the actions to create a swipe action configuration
81 | // Return it to the swipe action configuration provider
82 | return UISwipeActionsConfiguration(actions: [action2, action1])
83 | }
84 |
85 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
86 |
87 | // Create collection view with list layout
88 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
89 | collectionView.delegate = self
90 | view.addSubview(collectionView)
91 |
92 | // Make collection view take up the entire view
93 | collectionView.translatesAutoresizingMaskIntoConstraints = false
94 | NSLayoutConstraint.activate([
95 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
96 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
97 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
98 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
99 | ])
100 |
101 | // Create cell registration that define how data should be shown in a cell
102 | // Contain what cell to use, data item type and cell registration handler
103 | let cellRegistration = UICollectionView.CellRegistration { (cell, indexPath, item) in
104 |
105 | // Define how data should be shown using content configuration
106 | var content = cell.defaultContentConfiguration()
107 | content.image = item.image
108 | content.text = item.name
109 |
110 | // Assign content configuration to cell
111 | cell.contentConfiguration = content
112 | }
113 |
114 | // Create a diffable data source by passing in a collection view and a cell provider closure.
115 | // This will connect the diffable data source with the view controller.
116 | // Item data type and identifier data type matched each other.
117 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
118 | (collectionView: UICollectionView, indexPath: IndexPath, identifier: SFSymbolItem) -> UICollectionViewCell? in
119 |
120 | // Dequeue reusable cell using cell registration (Reuse identifier no longer needed)
121 | let cell = collectionView.dequeueConfiguredReusableCell(using: cellRegistration,
122 | for: indexPath,
123 | item: identifier)
124 | // Configure cell appearance
125 | cell.accessories = [.disclosureIndicator()]
126 |
127 | return cell
128 | }
129 |
130 | // Create a snapshot that define the current state of data source's data
131 | snapshot = NSDiffableDataSourceSnapshot()
132 | snapshot.appendSections([.main])
133 | snapshot.appendItems(dataItems, toSection: .main)
134 |
135 | // Display data on the collection view by applying the snapshot to data source
136 | dataSource.apply(snapshot, animatingDifferences: false)
137 | }
138 | }
139 |
140 | private extension BasicListViewController {
141 |
142 | func handleSwipe(for action: UIContextualAction, item: SFSymbolItem) {
143 |
144 | let alert = UIAlertController(title: action.title,
145 | message: item.name,
146 | preferredStyle: .alert)
147 |
148 | let okAction = UIAlertAction(title:"OK", style: .default, handler: { (_) in })
149 | alert.addAction(okAction)
150 |
151 | present(alert, animated: true, completion:nil)
152 | }
153 | }
154 |
155 | extension BasicListViewController: UICollectionViewDelegate {
156 |
157 | func collectionView(_ collectionView: UICollectionView,
158 | didSelectItemAt indexPath: IndexPath) {
159 |
160 | // Retrieve the item identifier using index path.
161 | // The item identifier we get will be the selected data item
162 | // NOTE: Do not use dataItems[indexPath.item] (Apple recommends never using index paths as identifiers, as they’re not guaranteed to be stable as list items get inserted and removed.)
163 | guard let selectedItem = dataSource.itemIdentifier(for: indexPath) else {
164 | collectionView.deselectItem(at: indexPath, animated: true)
165 | return
166 | }
167 |
168 | // Show selected SFSymbol's name
169 | let alert = UIAlertController(title: selectedItem.name,
170 | message: "",
171 | preferredStyle: .alert)
172 |
173 | let okAction = UIAlertAction(title:"OK", style: .default, handler: { (_) in
174 | // Deselect the selected cell
175 | collectionView.deselectItem(at: indexPath, animated: true)
176 | })
177 | alert.addAction(okAction)
178 |
179 | present(alert, animated: true, completion:nil)
180 | }
181 | }
182 |
183 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/Reload Header/ReloadExpandableHeaderViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ReloadExpandableHeaderViewController.swift
3 | // Swift-Senpai-UICollectionView-List
4 | //
5 | // Created by Lee Kah Seng on 20/01/2021.
6 | //
7 |
8 | import UIKit
9 |
10 | class ReloadExpandableHeaderViewController: UIViewController {
11 |
12 | // MARK: Identifier Types
13 | struct Parent: Hashable {
14 | var title: String
15 | let children: [Child]
16 | }
17 |
18 | struct Child: Hashable {
19 | let title: String
20 | }
21 |
22 | enum DataItem: Hashable {
23 | case parent(Parent)
24 | case child(Child)
25 | }
26 |
27 | // MARK: Model objects
28 | let parents = [
29 |
30 | Parent(title: "Parent 1", children: [
31 | Child(title: "Child 1 - A"),
32 | Child(title: "Child 1 - B"),
33 | Child(title: "Child 1 - C"),
34 | ]),
35 |
36 | Parent(title: "Parent 2", children: [
37 | Child(title: "Child 2 - A"),
38 | Child(title: "Child 2 - B"),
39 | Child(title: "Child 2 - C"),
40 | Child(title: "Child 2 - D"),
41 | ]),
42 | ]
43 |
44 | var collectionView: UICollectionView!
45 | var dataSource: UICollectionViewDiffableDataSource!
46 |
47 | override func viewDidLoad() {
48 | super.viewDidLoad()
49 |
50 | self.title = "Reload Expandable Section Header"
51 |
52 | // MARK: Create list layout
53 | var layoutConfig = UICollectionLayoutListConfiguration(appearance: .insetGrouped)
54 | layoutConfig.headerMode = .firstItemInSection
55 | let listLayout = UICollectionViewCompositionalLayout.list(using: layoutConfig)
56 |
57 | // MARK: Configure collection view
58 | collectionView = UICollectionView(frame: view.bounds, collectionViewLayout: listLayout)
59 | collectionView.delegate = self
60 | view.addSubview(collectionView)
61 | collectionView.translatesAutoresizingMaskIntoConstraints = false
62 | NSLayoutConstraint.activate([
63 | collectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 0.0),
64 | collectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
65 | collectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: 0.0),
66 | collectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0),
67 | ])
68 |
69 | // MARK: Cell registration
70 | let headerCellRegistration = UICollectionView.CellRegistration {
71 | (cell, indexPath, parent) in
72 |
73 | // Set parent's title to header cell
74 | var content = cell.defaultContentConfiguration()
75 | content.text = parent.title
76 | cell.contentConfiguration = content
77 |
78 | let headerDisclosureOption = UICellAccessory.OutlineDisclosureOptions(style: .header)
79 | cell.accessories = [.outlineDisclosure(options:headerDisclosureOption)]
80 | }
81 |
82 | let childCellRegistration = UICollectionView.CellRegistration {
83 | (cell, indexPath, child) in
84 |
85 | // Set child title to cell
86 | var content = cell.defaultContentConfiguration()
87 | content.text = child.title
88 | cell.contentConfiguration = content
89 | }
90 |
91 | // MARK: Initialize data source
92 | dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) {
93 | (collectionView, indexPath, listItem) -> UICollectionViewCell? in
94 |
95 | switch listItem {
96 | case .parent(let parent):
97 |
98 | // Dequeue header cell
99 | let cell = collectionView.dequeueConfiguredReusableCell(using: headerCellRegistration,
100 | for: indexPath,
101 | item: parent)
102 | return cell
103 |
104 | case .child(let child):
105 |
106 | // Dequeue cell
107 | let cell = collectionView.dequeueConfiguredReusableCell(using: childCellRegistration,
108 | for: indexPath,
109 | item: child)
110 | return cell
111 | }
112 | }
113 |
114 | // MARK: Construct data source snapshot
115 | // Loop through each parent items to create a section snapshots.
116 | for parent in parents {
117 |
118 | // Create a section snapshot
119 | var sectionSnapshot = NSDiffableDataSourceSectionSnapshot()
120 |
121 | // Create a parent DataItem & append as parent
122 | let parentDataItem = DataItem.parent(parent)
123 | sectionSnapshot.append([parentDataItem])
124 |
125 | // Create an array of child items & append as children of parentDataItem
126 | let childDataItemArray = parent.children.map { DataItem.child($0) }
127 | sectionSnapshot.append(childDataItemArray, to: parentDataItem)
128 |
129 | // Expand this section by default
130 | sectionSnapshot.expand([parentDataItem])
131 |
132 | // Apply section snapshot to the respective collection view section
133 | dataSource.apply(sectionSnapshot, to: parent, animatingDifferences: false)
134 | }
135 |
136 | }
137 |
138 | }
139 |
140 | extension ReloadExpandableHeaderViewController: UICollectionViewDelegate {
141 |
142 | func collectionView(_ collectionView: UICollectionView,
143 | didSelectItemAt indexPath: IndexPath) {
144 |
145 | let selectedSection = dataSource.snapshot().sectionIdentifiers[indexPath.section]
146 | let selectedSectionSnapshot = dataSource.snapshot(for: selectedSection)
147 |
148 | // 1
149 | // Obtain a reference to the selected parent
150 | guard
151 | let selectedParentDataItem = selectedSectionSnapshot.rootItems.first,
152 | case let DataItem.parent(selectedParent) = selectedParentDataItem else {
153 | return
154 | }
155 |
156 | // 2
157 | // Obtain a reference to the selected child
158 | let selectedChildDataItem = selectedSectionSnapshot.items[indexPath.item]
159 | guard case let DataItem.child(selectedChild) = selectedChildDataItem else {
160 | return
161 | }
162 |
163 | // 3
164 | // Create a new parent with selectedChild's title
165 | let newParent = Parent(title: selectedChild.title, children: selectedParent.children)
166 | let newParentDataItem = DataItem.parent(newParent)
167 |
168 | // 4
169 | // Avoid crash when user tap on same cell twice (snapshot data must be unique)
170 | guard selectedParent != newParent else {
171 | return
172 | }
173 |
174 | // 5
175 | // Replace the parent in section snapshot (by insert new item and then delete old item)
176 | var newSectionSnapshot = selectedSectionSnapshot
177 | newSectionSnapshot.insert([newParentDataItem], before: selectedParentDataItem)
178 | newSectionSnapshot.delete([selectedParentDataItem])
179 |
180 | // 6
181 | // Reconstruct section snapshot by appending children to `newParentDataItem`
182 | let allChildDataItems = selectedParent.children.map { DataItem.child($0) }
183 | newSectionSnapshot.append(allChildDataItems, to: newParentDataItem)
184 |
185 | // 7
186 | // Expand the section
187 | newSectionSnapshot.expand([newParentDataItem])
188 |
189 | // 8
190 | // Apply new section snapshot to selected section
191 | dataSource.apply(newSectionSnapshot, to: selectedSection, animatingDifferences: true) {
192 | // The cell's select state will be gone after applying a new snapshot, thus manually reselecting the cell.
193 | collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .left)
194 | }
195 | }
196 |
197 | }
198 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 50;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | F92A29AD25B86AA100270D04 /* ReloadExpandableHeaderViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92A29AC25B86AA100270D04 /* ReloadExpandableHeaderViewController.swift */; };
11 | F92DF904254AFC5A001AEBA7 /* InteractiveHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = F92DF902254AFC5A001AEBA7 /* InteractiveHeader.swift */; };
12 | F93EA01024E941EE007206EB /* SFSymbolNameContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F93EA00F24E941EE007206EB /* SFSymbolNameContentView.swift */; };
13 | F93EA01224E941F5007206EB /* SFSymbolNameContentView.xib in Resources */ = {isa = PBXBuildFile; fileRef = F93EA01124E941F5007206EB /* SFSymbolNameContentView.xib */; };
14 | F940B00F24C3E259000E6D17 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F940B00E24C3E259000E6D17 /* AppDelegate.swift */; };
15 | F940B01124C3E259000E6D17 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = F940B01024C3E259000E6D17 /* SceneDelegate.swift */; };
16 | F940B01324C3E259000E6D17 /* BasicListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F940B01224C3E259000E6D17 /* BasicListViewController.swift */; };
17 | F940B01624C3E259000E6D17 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F940B01424C3E259000E6D17 /* Main.storyboard */; };
18 | F940B01824C3E25B000E6D17 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F940B01724C3E25B000E6D17 /* Assets.xcassets */; };
19 | F940B01B24C3E25B000E6D17 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F940B01924C3E25B000E6D17 /* LaunchScreen.storyboard */; };
20 | F9660949251A460D00C2DB98 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9660948251A460D00C2DB98 /* Model.swift */; };
21 | F96AFF452524CC6D00F24248 /* ReloadReferenceTypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96AFF442524CC6D00F24248 /* ReloadReferenceTypeViewController.swift */; };
22 | F96AFF482524CE3D00F24248 /* ReloadValueTypeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F96AFF472524CE3D00F24248 /* ReloadValueTypeViewController.swift */; };
23 | F97AD794250393C1006B9692 /* SingleSecExpandableListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F97AD793250393C1006B9692 /* SingleSecExpandableListViewController.swift */; };
24 | F986E53C24D5AC8F007431C2 /* CustomCellListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F986E53B24D5AC8F007431C2 /* CustomCellListViewController.swift */; };
25 | F986E53E24D5AE3B007431C2 /* SFSymbolContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F986E53D24D5AE3B007431C2 /* SFSymbolContentConfiguration.swift */; };
26 | F986E54024D5AFEE007431C2 /* SFSymbolVerticalContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F986E53F24D5AFEE007431C2 /* SFSymbolVerticalContentView.swift */; };
27 | F99F952124D67484004C4002 /* SFSymbolVerticalListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F99F952024D67484004C4002 /* SFSymbolVerticalListCell.swift */; };
28 | F9A404342502535600582192 /* MultiSecExpandableListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9A404332502535600582192 /* MultiSecExpandableListViewController.swift */; };
29 | F9D8B7EE253A8912009944FE /* DeclarativeHeaderFooterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9D8B7ED253A8912009944FE /* DeclarativeHeaderFooterViewController.swift */; };
30 | F9E5E99A24EAC0C900D59C15 /* SFSymbolNameListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E5E99924EAC0C900D59C15 /* SFSymbolNameListCell.swift */; };
31 | F9E5E99C24EAC18700D59C15 /* SFSymbolNameContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E5E99B24EAC18700D59C15 /* SFSymbolNameContentConfiguration.swift */; };
32 | F9E5E99E24EAC39000D59C15 /* NameListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9E5E99D24EAC39000D59C15 /* NameListViewController.swift */; };
33 | F9F4F5AE259F503E00FB236B /* DatePickerReplicaViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9F4F5AD259F503E00FB236B /* DatePickerReplicaViewController.swift */; };
34 | F9F4F5B2259F515000FB236B /* DatePickerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9F4F5B1259F515000FB236B /* DatePickerCell.swift */; };
35 | F9F4F5B5259F515D00FB236B /* DatePickerContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9F4F5B4259F515D00FB236B /* DatePickerContentView.swift */; };
36 | F9F4F5B8259F517C00FB236B /* DatePickerContentConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9F4F5B7259F517C00FB236B /* DatePickerContentConfiguration.swift */; };
37 | F9FA37BC25486DB900421758 /* CustomHeaderFooterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F9FA37BB25486DB900421758 /* CustomHeaderFooterViewController.swift */; };
38 | /* End PBXBuildFile section */
39 |
40 | /* Begin PBXFileReference section */
41 | F92A29AC25B86AA100270D04 /* ReloadExpandableHeaderViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadExpandableHeaderViewController.swift; sourceTree = ""; };
42 | F92DF902254AFC5A001AEBA7 /* InteractiveHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveHeader.swift; sourceTree = ""; };
43 | F93EA00F24E941EE007206EB /* SFSymbolNameContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFSymbolNameContentView.swift; sourceTree = ""; };
44 | F93EA01124E941F5007206EB /* SFSymbolNameContentView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SFSymbolNameContentView.xib; sourceTree = ""; };
45 | F940B00B24C3E259000E6D17 /* Swift-Senpai-UICollectionView-List.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Swift-Senpai-UICollectionView-List.app"; sourceTree = BUILT_PRODUCTS_DIR; };
46 | F940B00E24C3E259000E6D17 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
47 | F940B01024C3E259000E6D17 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
48 | F940B01224C3E259000E6D17 /* BasicListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicListViewController.swift; sourceTree = ""; };
49 | F940B01524C3E259000E6D17 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
50 | F940B01724C3E25B000E6D17 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
51 | F940B01A24C3E25B000E6D17 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
52 | F940B01C24C3E25B000E6D17 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
53 | F9660948251A460D00C2DB98 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; };
54 | F96AFF442524CC6D00F24248 /* ReloadReferenceTypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadReferenceTypeViewController.swift; sourceTree = ""; };
55 | F96AFF472524CE3D00F24248 /* ReloadValueTypeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReloadValueTypeViewController.swift; sourceTree = ""; };
56 | F97AD793250393C1006B9692 /* SingleSecExpandableListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleSecExpandableListViewController.swift; sourceTree = ""; };
57 | F986E53B24D5AC8F007431C2 /* CustomCellListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomCellListViewController.swift; sourceTree = ""; };
58 | F986E53D24D5AE3B007431C2 /* SFSymbolContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFSymbolContentConfiguration.swift; sourceTree = ""; };
59 | F986E53F24D5AFEE007431C2 /* SFSymbolVerticalContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFSymbolVerticalContentView.swift; sourceTree = ""; };
60 | F99F952024D67484004C4002 /* SFSymbolVerticalListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFSymbolVerticalListCell.swift; sourceTree = ""; };
61 | F9A404332502535600582192 /* MultiSecExpandableListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSecExpandableListViewController.swift; sourceTree = ""; };
62 | F9D8B7ED253A8912009944FE /* DeclarativeHeaderFooterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeclarativeHeaderFooterViewController.swift; sourceTree = ""; };
63 | F9E5E99924EAC0C900D59C15 /* SFSymbolNameListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFSymbolNameListCell.swift; sourceTree = ""; };
64 | F9E5E99B24EAC18700D59C15 /* SFSymbolNameContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SFSymbolNameContentConfiguration.swift; sourceTree = ""; };
65 | F9E5E99D24EAC39000D59C15 /* NameListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NameListViewController.swift; sourceTree = ""; };
66 | F9F4F5AD259F503E00FB236B /* DatePickerReplicaViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerReplicaViewController.swift; sourceTree = ""; };
67 | F9F4F5B1259F515000FB236B /* DatePickerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerCell.swift; sourceTree = ""; };
68 | F9F4F5B4259F515D00FB236B /* DatePickerContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerContentView.swift; sourceTree = ""; };
69 | F9F4F5B7259F517C00FB236B /* DatePickerContentConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerContentConfiguration.swift; sourceTree = ""; };
70 | F9FA37BB25486DB900421758 /* CustomHeaderFooterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomHeaderFooterViewController.swift; sourceTree = ""; };
71 | /* End PBXFileReference section */
72 |
73 | /* Begin PBXFrameworksBuildPhase section */
74 | F940B00824C3E259000E6D17 /* Frameworks */ = {
75 | isa = PBXFrameworksBuildPhase;
76 | buildActionMask = 2147483647;
77 | files = (
78 | );
79 | runOnlyForDeploymentPostprocessing = 0;
80 | };
81 | /* End PBXFrameworksBuildPhase section */
82 |
83 | /* Begin PBXGroup section */
84 | F940B00224C3E259000E6D17 = {
85 | isa = PBXGroup;
86 | children = (
87 | F940B00D24C3E259000E6D17 /* Swift-Senpai-UICollectionView-List */,
88 | F940B00C24C3E259000E6D17 /* Products */,
89 | );
90 | sourceTree = "";
91 | };
92 | F940B00C24C3E259000E6D17 /* Products */ = {
93 | isa = PBXGroup;
94 | children = (
95 | F940B00B24C3E259000E6D17 /* Swift-Senpai-UICollectionView-List.app */,
96 | );
97 | name = Products;
98 | sourceTree = "";
99 | };
100 | F940B00D24C3E259000E6D17 /* Swift-Senpai-UICollectionView-List */ = {
101 | isa = PBXGroup;
102 | children = (
103 | F946A1FE25B425DA0053E194 /* Reload Header */,
104 | F9F4F5AC259F4FE100FB236B /* Date Picker Replica */,
105 | F9FA37BA25486D6900421758 /* Custom Header & Footer */,
106 | F9D8B7EA253A7103009944FE /* Declarative Header & Footer */,
107 | F96AFF422524C98800F24248 /* Reload Cell */,
108 | F94C4FCE24F69E6500BF6544 /* Expandable List */,
109 | F9E5E99824EAC05600D59C15 /* Basic List */,
110 | F986E53A24D5AC6A007431C2 /* Custom Cell */,
111 | F984D41B24E8C7CB0029679F /* Custom Cell - nib */,
112 | F9660948251A460D00C2DB98 /* Model.swift */,
113 | F940B00E24C3E259000E6D17 /* AppDelegate.swift */,
114 | F940B01024C3E259000E6D17 /* SceneDelegate.swift */,
115 | F940B01424C3E259000E6D17 /* Main.storyboard */,
116 | F940B01724C3E25B000E6D17 /* Assets.xcassets */,
117 | F940B01924C3E25B000E6D17 /* LaunchScreen.storyboard */,
118 | F940B01C24C3E25B000E6D17 /* Info.plist */,
119 | );
120 | path = "Swift-Senpai-UICollectionView-List";
121 | sourceTree = "";
122 | };
123 | F946A1FE25B425DA0053E194 /* Reload Header */ = {
124 | isa = PBXGroup;
125 | children = (
126 | F92A29AC25B86AA100270D04 /* ReloadExpandableHeaderViewController.swift */,
127 | );
128 | path = "Reload Header";
129 | sourceTree = "";
130 | };
131 | F94C4FCE24F69E6500BF6544 /* Expandable List */ = {
132 | isa = PBXGroup;
133 | children = (
134 | F97AD793250393C1006B9692 /* SingleSecExpandableListViewController.swift */,
135 | F9A404332502535600582192 /* MultiSecExpandableListViewController.swift */,
136 | );
137 | path = "Expandable List";
138 | sourceTree = "";
139 | };
140 | F96AFF422524C98800F24248 /* Reload Cell */ = {
141 | isa = PBXGroup;
142 | children = (
143 | F96AFF442524CC6D00F24248 /* ReloadReferenceTypeViewController.swift */,
144 | F96AFF472524CE3D00F24248 /* ReloadValueTypeViewController.swift */,
145 | );
146 | path = "Reload Cell";
147 | sourceTree = "";
148 | };
149 | F984D41B24E8C7CB0029679F /* Custom Cell - nib */ = {
150 | isa = PBXGroup;
151 | children = (
152 | F9E5E99D24EAC39000D59C15 /* NameListViewController.swift */,
153 | F9E5E99B24EAC18700D59C15 /* SFSymbolNameContentConfiguration.swift */,
154 | F9E5E99924EAC0C900D59C15 /* SFSymbolNameListCell.swift */,
155 | F93EA00F24E941EE007206EB /* SFSymbolNameContentView.swift */,
156 | F93EA01124E941F5007206EB /* SFSymbolNameContentView.xib */,
157 | );
158 | path = "Custom Cell - nib";
159 | sourceTree = "";
160 | };
161 | F986E53A24D5AC6A007431C2 /* Custom Cell */ = {
162 | isa = PBXGroup;
163 | children = (
164 | F986E53B24D5AC8F007431C2 /* CustomCellListViewController.swift */,
165 | F986E53D24D5AE3B007431C2 /* SFSymbolContentConfiguration.swift */,
166 | F99F952024D67484004C4002 /* SFSymbolVerticalListCell.swift */,
167 | F986E53F24D5AFEE007431C2 /* SFSymbolVerticalContentView.swift */,
168 | );
169 | path = "Custom Cell";
170 | sourceTree = "";
171 | };
172 | F9D8B7EA253A7103009944FE /* Declarative Header & Footer */ = {
173 | isa = PBXGroup;
174 | children = (
175 | F9D8B7ED253A8912009944FE /* DeclarativeHeaderFooterViewController.swift */,
176 | );
177 | path = "Declarative Header & Footer";
178 | sourceTree = "";
179 | };
180 | F9E5E99824EAC05600D59C15 /* Basic List */ = {
181 | isa = PBXGroup;
182 | children = (
183 | F940B01224C3E259000E6D17 /* BasicListViewController.swift */,
184 | );
185 | path = "Basic List";
186 | sourceTree = "";
187 | };
188 | F9F4F5AC259F4FE100FB236B /* Date Picker Replica */ = {
189 | isa = PBXGroup;
190 | children = (
191 | F9F4F5AD259F503E00FB236B /* DatePickerReplicaViewController.swift */,
192 | F9F4F5B7259F517C00FB236B /* DatePickerContentConfiguration.swift */,
193 | F9F4F5B1259F515000FB236B /* DatePickerCell.swift */,
194 | F9F4F5B4259F515D00FB236B /* DatePickerContentView.swift */,
195 | );
196 | path = "Date Picker Replica";
197 | sourceTree = "";
198 | };
199 | F9FA37BA25486D6900421758 /* Custom Header & Footer */ = {
200 | isa = PBXGroup;
201 | children = (
202 | F9FA37BB25486DB900421758 /* CustomHeaderFooterViewController.swift */,
203 | F92DF902254AFC5A001AEBA7 /* InteractiveHeader.swift */,
204 | );
205 | path = "Custom Header & Footer";
206 | sourceTree = "";
207 | };
208 | /* End PBXGroup section */
209 |
210 | /* Begin PBXNativeTarget section */
211 | F940B00A24C3E259000E6D17 /* Swift-Senpai-UICollectionView-List */ = {
212 | isa = PBXNativeTarget;
213 | buildConfigurationList = F940B01F24C3E25B000E6D17 /* Build configuration list for PBXNativeTarget "Swift-Senpai-UICollectionView-List" */;
214 | buildPhases = (
215 | F940B00724C3E259000E6D17 /* Sources */,
216 | F940B00824C3E259000E6D17 /* Frameworks */,
217 | F940B00924C3E259000E6D17 /* Resources */,
218 | );
219 | buildRules = (
220 | );
221 | dependencies = (
222 | );
223 | name = "Swift-Senpai-UICollectionView-List";
224 | productName = "Swift-Senpai-UICollectionView-List";
225 | productReference = F940B00B24C3E259000E6D17 /* Swift-Senpai-UICollectionView-List.app */;
226 | productType = "com.apple.product-type.application";
227 | };
228 | /* End PBXNativeTarget section */
229 |
230 | /* Begin PBXProject section */
231 | F940B00324C3E259000E6D17 /* Project object */ = {
232 | isa = PBXProject;
233 | attributes = {
234 | LastSwiftUpdateCheck = 1200;
235 | LastUpgradeCheck = 1200;
236 | TargetAttributes = {
237 | F940B00A24C3E259000E6D17 = {
238 | CreatedOnToolsVersion = 12.0;
239 | };
240 | };
241 | };
242 | buildConfigurationList = F940B00624C3E259000E6D17 /* Build configuration list for PBXProject "Swift-Senpai-UICollectionView-List" */;
243 | compatibilityVersion = "Xcode 9.3";
244 | developmentRegion = en;
245 | hasScannedForEncodings = 0;
246 | knownRegions = (
247 | en,
248 | Base,
249 | );
250 | mainGroup = F940B00224C3E259000E6D17;
251 | productRefGroup = F940B00C24C3E259000E6D17 /* Products */;
252 | projectDirPath = "";
253 | projectRoot = "";
254 | targets = (
255 | F940B00A24C3E259000E6D17 /* Swift-Senpai-UICollectionView-List */,
256 | );
257 | };
258 | /* End PBXProject section */
259 |
260 | /* Begin PBXResourcesBuildPhase section */
261 | F940B00924C3E259000E6D17 /* Resources */ = {
262 | isa = PBXResourcesBuildPhase;
263 | buildActionMask = 2147483647;
264 | files = (
265 | F93EA01224E941F5007206EB /* SFSymbolNameContentView.xib in Resources */,
266 | F940B01B24C3E25B000E6D17 /* LaunchScreen.storyboard in Resources */,
267 | F940B01824C3E25B000E6D17 /* Assets.xcassets in Resources */,
268 | F940B01624C3E259000E6D17 /* Main.storyboard in Resources */,
269 | );
270 | runOnlyForDeploymentPostprocessing = 0;
271 | };
272 | /* End PBXResourcesBuildPhase section */
273 |
274 | /* Begin PBXSourcesBuildPhase section */
275 | F940B00724C3E259000E6D17 /* Sources */ = {
276 | isa = PBXSourcesBuildPhase;
277 | buildActionMask = 2147483647;
278 | files = (
279 | F986E53C24D5AC8F007431C2 /* CustomCellListViewController.swift in Sources */,
280 | F92A29AD25B86AA100270D04 /* ReloadExpandableHeaderViewController.swift in Sources */,
281 | F9E5E99A24EAC0C900D59C15 /* SFSymbolNameListCell.swift in Sources */,
282 | F9F4F5B5259F515D00FB236B /* DatePickerContentView.swift in Sources */,
283 | F97AD794250393C1006B9692 /* SingleSecExpandableListViewController.swift in Sources */,
284 | F92DF904254AFC5A001AEBA7 /* InteractiveHeader.swift in Sources */,
285 | F9E5E99E24EAC39000D59C15 /* NameListViewController.swift in Sources */,
286 | F9D8B7EE253A8912009944FE /* DeclarativeHeaderFooterViewController.swift in Sources */,
287 | F9FA37BC25486DB900421758 /* CustomHeaderFooterViewController.swift in Sources */,
288 | F9F4F5AE259F503E00FB236B /* DatePickerReplicaViewController.swift in Sources */,
289 | F99F952124D67484004C4002 /* SFSymbolVerticalListCell.swift in Sources */,
290 | F940B01324C3E259000E6D17 /* BasicListViewController.swift in Sources */,
291 | F93EA01024E941EE007206EB /* SFSymbolNameContentView.swift in Sources */,
292 | F9660949251A460D00C2DB98 /* Model.swift in Sources */,
293 | F9F4F5B8259F517C00FB236B /* DatePickerContentConfiguration.swift in Sources */,
294 | F940B00F24C3E259000E6D17 /* AppDelegate.swift in Sources */,
295 | F9E5E99C24EAC18700D59C15 /* SFSymbolNameContentConfiguration.swift in Sources */,
296 | F940B01124C3E259000E6D17 /* SceneDelegate.swift in Sources */,
297 | F96AFF482524CE3D00F24248 /* ReloadValueTypeViewController.swift in Sources */,
298 | F986E54024D5AFEE007431C2 /* SFSymbolVerticalContentView.swift in Sources */,
299 | F986E53E24D5AE3B007431C2 /* SFSymbolContentConfiguration.swift in Sources */,
300 | F9A404342502535600582192 /* MultiSecExpandableListViewController.swift in Sources */,
301 | F96AFF452524CC6D00F24248 /* ReloadReferenceTypeViewController.swift in Sources */,
302 | F9F4F5B2259F515000FB236B /* DatePickerCell.swift in Sources */,
303 | );
304 | runOnlyForDeploymentPostprocessing = 0;
305 | };
306 | /* End PBXSourcesBuildPhase section */
307 |
308 | /* Begin PBXVariantGroup section */
309 | F940B01424C3E259000E6D17 /* Main.storyboard */ = {
310 | isa = PBXVariantGroup;
311 | children = (
312 | F940B01524C3E259000E6D17 /* Base */,
313 | );
314 | name = Main.storyboard;
315 | sourceTree = "";
316 | };
317 | F940B01924C3E25B000E6D17 /* LaunchScreen.storyboard */ = {
318 | isa = PBXVariantGroup;
319 | children = (
320 | F940B01A24C3E25B000E6D17 /* Base */,
321 | );
322 | name = LaunchScreen.storyboard;
323 | sourceTree = "";
324 | };
325 | /* End PBXVariantGroup section */
326 |
327 | /* Begin XCBuildConfiguration section */
328 | F940B01D24C3E25B000E6D17 /* Debug */ = {
329 | isa = XCBuildConfiguration;
330 | buildSettings = {
331 | ALWAYS_SEARCH_USER_PATHS = NO;
332 | CLANG_ANALYZER_NONNULL = YES;
333 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
334 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
335 | CLANG_CXX_LIBRARY = "libc++";
336 | CLANG_ENABLE_MODULES = YES;
337 | CLANG_ENABLE_OBJC_ARC = YES;
338 | CLANG_ENABLE_OBJC_WEAK = YES;
339 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
340 | CLANG_WARN_BOOL_CONVERSION = YES;
341 | CLANG_WARN_COMMA = YES;
342 | CLANG_WARN_CONSTANT_CONVERSION = YES;
343 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
344 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
345 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
346 | CLANG_WARN_EMPTY_BODY = YES;
347 | CLANG_WARN_ENUM_CONVERSION = YES;
348 | CLANG_WARN_INFINITE_RECURSION = YES;
349 | CLANG_WARN_INT_CONVERSION = YES;
350 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
351 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
352 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
353 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
354 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
355 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
356 | CLANG_WARN_STRICT_PROTOTYPES = YES;
357 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
358 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
359 | CLANG_WARN_UNREACHABLE_CODE = YES;
360 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
361 | COPY_PHASE_STRIP = NO;
362 | DEBUG_INFORMATION_FORMAT = dwarf;
363 | ENABLE_STRICT_OBJC_MSGSEND = YES;
364 | ENABLE_TESTABILITY = YES;
365 | GCC_C_LANGUAGE_STANDARD = gnu11;
366 | GCC_DYNAMIC_NO_PIC = NO;
367 | GCC_NO_COMMON_BLOCKS = YES;
368 | GCC_OPTIMIZATION_LEVEL = 0;
369 | GCC_PREPROCESSOR_DEFINITIONS = (
370 | "DEBUG=1",
371 | "$(inherited)",
372 | );
373 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
374 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
375 | GCC_WARN_UNDECLARED_SELECTOR = YES;
376 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
377 | GCC_WARN_UNUSED_FUNCTION = YES;
378 | GCC_WARN_UNUSED_VARIABLE = YES;
379 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
380 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
381 | MTL_FAST_MATH = YES;
382 | ONLY_ACTIVE_ARCH = YES;
383 | SDKROOT = iphoneos;
384 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
385 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
386 | };
387 | name = Debug;
388 | };
389 | F940B01E24C3E25B000E6D17 /* Release */ = {
390 | isa = XCBuildConfiguration;
391 | buildSettings = {
392 | ALWAYS_SEARCH_USER_PATHS = NO;
393 | CLANG_ANALYZER_NONNULL = YES;
394 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
395 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
396 | CLANG_CXX_LIBRARY = "libc++";
397 | CLANG_ENABLE_MODULES = YES;
398 | CLANG_ENABLE_OBJC_ARC = YES;
399 | CLANG_ENABLE_OBJC_WEAK = YES;
400 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
401 | CLANG_WARN_BOOL_CONVERSION = YES;
402 | CLANG_WARN_COMMA = YES;
403 | CLANG_WARN_CONSTANT_CONVERSION = YES;
404 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
405 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
406 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
407 | CLANG_WARN_EMPTY_BODY = YES;
408 | CLANG_WARN_ENUM_CONVERSION = YES;
409 | CLANG_WARN_INFINITE_RECURSION = YES;
410 | CLANG_WARN_INT_CONVERSION = YES;
411 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
412 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
413 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
414 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
415 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
416 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
417 | CLANG_WARN_STRICT_PROTOTYPES = YES;
418 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
419 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
420 | CLANG_WARN_UNREACHABLE_CODE = YES;
421 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
422 | COPY_PHASE_STRIP = NO;
423 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
424 | ENABLE_NS_ASSERTIONS = NO;
425 | ENABLE_STRICT_OBJC_MSGSEND = YES;
426 | GCC_C_LANGUAGE_STANDARD = gnu11;
427 | GCC_NO_COMMON_BLOCKS = YES;
428 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
429 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
430 | GCC_WARN_UNDECLARED_SELECTOR = YES;
431 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
432 | GCC_WARN_UNUSED_FUNCTION = YES;
433 | GCC_WARN_UNUSED_VARIABLE = YES;
434 | IPHONEOS_DEPLOYMENT_TARGET = 14.0;
435 | MTL_ENABLE_DEBUG_INFO = NO;
436 | MTL_FAST_MATH = YES;
437 | SDKROOT = iphoneos;
438 | SWIFT_COMPILATION_MODE = wholemodule;
439 | SWIFT_OPTIMIZATION_LEVEL = "-O";
440 | VALIDATE_PRODUCT = YES;
441 | };
442 | name = Release;
443 | };
444 | F940B02024C3E25B000E6D17 /* Debug */ = {
445 | isa = XCBuildConfiguration;
446 | buildSettings = {
447 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
448 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
449 | CODE_SIGN_STYLE = Automatic;
450 | DEVELOPMENT_TEAM = QVEMCYHXHL;
451 | INFOPLIST_FILE = "Swift-Senpai-UICollectionView-List/Info.plist";
452 | LD_RUNPATH_SEARCH_PATHS = (
453 | "$(inherited)",
454 | "@executable_path/Frameworks",
455 | );
456 | PRODUCT_BUNDLE_IDENTIFIER = "com.leekahseng.Swift-Senpai-UICollectionView-List";
457 | PRODUCT_NAME = "$(TARGET_NAME)";
458 | SWIFT_VERSION = 5.0;
459 | TARGETED_DEVICE_FAMILY = 1;
460 | };
461 | name = Debug;
462 | };
463 | F940B02124C3E25B000E6D17 /* Release */ = {
464 | isa = XCBuildConfiguration;
465 | buildSettings = {
466 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
467 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
468 | CODE_SIGN_STYLE = Automatic;
469 | DEVELOPMENT_TEAM = QVEMCYHXHL;
470 | INFOPLIST_FILE = "Swift-Senpai-UICollectionView-List/Info.plist";
471 | LD_RUNPATH_SEARCH_PATHS = (
472 | "$(inherited)",
473 | "@executable_path/Frameworks",
474 | );
475 | PRODUCT_BUNDLE_IDENTIFIER = "com.leekahseng.Swift-Senpai-UICollectionView-List";
476 | PRODUCT_NAME = "$(TARGET_NAME)";
477 | SWIFT_VERSION = 5.0;
478 | TARGETED_DEVICE_FAMILY = 1;
479 | };
480 | name = Release;
481 | };
482 | /* End XCBuildConfiguration section */
483 |
484 | /* Begin XCConfigurationList section */
485 | F940B00624C3E259000E6D17 /* Build configuration list for PBXProject "Swift-Senpai-UICollectionView-List" */ = {
486 | isa = XCConfigurationList;
487 | buildConfigurations = (
488 | F940B01D24C3E25B000E6D17 /* Debug */,
489 | F940B01E24C3E25B000E6D17 /* Release */,
490 | );
491 | defaultConfigurationIsVisible = 0;
492 | defaultConfigurationName = Release;
493 | };
494 | F940B01F24C3E25B000E6D17 /* Build configuration list for PBXNativeTarget "Swift-Senpai-UICollectionView-List" */ = {
495 | isa = XCConfigurationList;
496 | buildConfigurations = (
497 | F940B02024C3E25B000E6D17 /* Debug */,
498 | F940B02124C3E25B000E6D17 /* Release */,
499 | );
500 | defaultConfigurationIsVisible = 0;
501 | defaultConfigurationName = Release;
502 | };
503 | /* End XCConfigurationList section */
504 | };
505 | rootObject = F940B00324C3E259000E6D17 /* Project object */;
506 | }
507 |
--------------------------------------------------------------------------------
/Swift-Senpai-UICollectionView-List/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 |
74 |
75 |
76 |
77 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
424 |
425 |
426 |
427 |
428 |
429 |
430 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
442 |
443 |
444 |
445 |
446 |
447 |
448 |
449 |
450 |
451 |
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 |
460 |
461 |
462 |
463 |
464 |
465 |
466 |
467 |
468 |
469 |
470 |
471 |
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
480 |
481 |
482 |
483 |
484 |
490 |
491 |
492 |
493 |
494 |
495 |
496 |
497 |
498 |
499 |
500 |
501 |
502 |
503 |
504 |
505 |
506 |
507 |
508 |
509 |
515 |
516 |
517 |
518 |
519 |
520 |
521 |
522 |
523 |
524 |
525 |
526 |
527 |
528 |
529 |
530 |
531 |
532 |
533 |
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
545 |
546 |
547 |
548 |
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 |
557 |
558 |
559 |
560 |
561 |
562 |
563 |
564 |
565 |
566 |
567 |
568 |
569 |
570 |
571 |
572 |
573 |
574 |
575 |
576 |
577 |
578 |
579 |
580 |
581 |
582 |
583 |
584 |
585 |
586 |
587 |
588 |
589 |
590 |
591 |
592 |
593 |
594 |
595 |
596 |
597 |
598 |
599 |
600 |
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
--------------------------------------------------------------------------------