├── .github
└── ISSUE_TEMPLATE.md
├── .gitignore
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── docs
└── images
│ └── cover.gif
├── ios-swift-collapsible-table-section.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ └── contents.xcworkspacedata
└── ios-swift-collapsible-table-section
├── AppDelegate.swift
├── Assets.xcassets
└── AppIcon.appiconset
│ └── Contents.json
├── Base.lproj
└── LaunchScreen.storyboard
├── CollapsibleTableViewCell.swift
├── CollapsibleTableViewController.swift
├── CollapsibleTableViewHeader.swift
├── ExampleData.swift
├── Extensions.swift
└── Info.plist
/.github/ISSUE_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata
19 |
20 | ## Other
21 | *.xccheckout
22 | *.moved-aside
23 | *.xcuserstate
24 | *.xcscmblueprint
25 |
26 | ### Xcode Patch ###
27 | *.xcodeproj/*
28 | !*.xcodeproj/project.pbxproj
29 | !*.xcodeproj/xcshareddata/
30 | !*.xcworkspace/contents.xcworkspacedata
31 | /*.gcno
32 |
33 | ## Obj-C/Swift specific
34 | *.hmap
35 | *.ipa
36 |
37 | ## Playgrounds
38 | timeline.xctimeline
39 | playground.xcworkspace
40 |
41 | # Swift Package Manager
42 | #
43 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
44 | # Packages/
45 | .build/
46 |
47 | # CocoaPods
48 | #
49 | # We recommend against adding the Pods directory to your .gitignore. However
50 | # you should judge for yourself, the pros and cons are mentioned at:
51 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
52 | #
53 | # Pods/
54 |
55 | # Carthage
56 | #
57 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
58 | # Carthage/Checkouts
59 |
60 | Carthage/Build
61 |
62 | # fastlane
63 | #
64 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
65 | # screenshots whenever they are needed.
66 | # For more information about the recommended setup visit:
67 | # https://github.com/fastlane/fastlane/blob/master/docs/Gitignore.md
68 |
69 | fastlane/report.xml
70 | fastlane/screenshots
71 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contribute
2 |
3 | ## Introduction
4 |
5 | First, thank you for considering contributing to ios-swift-collapsible-table-section! It's people like you that make the open source community such a great community! 😊
6 |
7 | We welcome any type of contribution, not only code. You can help with
8 | - **QA**: file bug reports, the more details you can give the better (e.g. screenshots with the console open)
9 | - **Marketing**: writing blog posts, howto's, printing stickers, ...
10 | - **Community**: presenting the project at meetups, organizing a dedicated meetup for the local community, ...
11 | - **Code**: take a look at the [open issues](issues). Even if you can't write code, commenting on them, showing that you care about a given issue matters. It helps us triage them.
12 | - **Money**: we welcome financial contributions in full transparency on our [open collective](https://opencollective.com/ios-swift-collapsible-table-section).
13 |
14 | ## Your First Contribution
15 |
16 | Working on your first Pull Request? You can learn how from this *free* series, [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
17 |
18 | ## Submitting code
19 |
20 | Any code change should be submitted as a pull request. The description should explain what the code does and give steps to execute it. The pull request should also contain tests.
21 |
22 | ## Code review process
23 |
24 | The bigger the pull request, the longer it will take to review and merge. Try to break down large pull requests in smaller chunks that are easier to review and merge.
25 | It is also always helpful to have some context for your pull request. What was the purpose? Why does it matter to you?
26 |
27 | ## Financial contributions
28 |
29 | We also welcome financial contributions in full transparency on our [open collective](https://opencollective.com/ios-swift-collapsible-table-section).
30 | Anyone can file an expense. If the expense makes sense for the development of the community, it will be "merged" in the ledger of our open collective by the core contributors and the person who filed the expense will be reimbursed.
31 |
32 | ## Questions
33 |
34 | If you have any questions, create an [issue](issue) (protip: do a quick search first to see if someone else didn't ask the same question before!).
35 | You can also reach us at hello@ios-swift-collapsible-table-section.opencollective.com.
36 |
37 | ## Credits
38 |
39 | ### Contributors
40 |
41 | Thank you to all the people who have already contributed to ios-swift-collapsible-table-section!
42 |
43 |
44 |
45 | ### Backers
46 |
47 | Thank you to all our backers! [[Become a backer](https://opencollective.com/ios-swift-collapsible-table-section#backer)]
48 |
49 |
50 |
51 |
52 | ### Sponsors
53 |
54 | Thank you to all our sponsors! (please ask your company to also support this open source project by [becoming a sponsor](https://opencollective.com/ios-swift-collapsible-table-section#sponsor))
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Yong Su
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # How to Implement Collapsible Table Section in iOS
2 |
3 | []()
4 | [](#backers)
5 | [](#sponsors)
6 |
7 | A simple iOS swift project demonstrates how to implement collapsible table section programmatically, that is no main storyboard, no XIB, no need to register nib, just pure Swift!
8 |
9 | In this project, the table view automatically resizes the height of the rows to fit the content in each cell, and the custom cell is also implemented programmatically.
10 |
11 | 
12 |
13 | ## How to implement collapsible table sections? ##
14 |
15 | #### Step 1. Prepare the Data ####
16 |
17 | Let's say we have the following data that is grouped into different sections, each section is represented by a `Section` object:
18 |
19 | ```swift
20 | struct Section {
21 | var name: String
22 | var items: [String]
23 | var collapsed: Bool
24 |
25 | init(name: String, items: [Item], collapsed: Bool = false) {
26 | self.name = name
27 | self.items = items
28 | self.collapsed = collapsed
29 | }
30 | }
31 |
32 | var sections = [Section]()
33 |
34 | sections = [
35 | Section(name: "Mac", items: ["MacBook", "MacBook Air"]),
36 | Section(name: "iPad", items: ["iPad Pro", "iPad Air 2"]),
37 | Section(name: "iPhone", items: ["iPhone 7", "iPhone 6"])
38 | ]
39 | ```
40 | `collapsed` indicates whether the current section is collapsed or not, by default is `false`.
41 |
42 | #### Step 2. Setup TableView to Support Autosizing ####
43 |
44 | ```swift
45 | override func viewDidLoad() {
46 | super.viewDidLoad()
47 |
48 | // Auto resizing the height of the cell
49 | tableView.estimatedRowHeight = 44.0
50 | tableView.rowHeight = UITableViewAutomaticDimension
51 |
52 | ...
53 | }
54 |
55 | override func tableView(_ tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
56 | return UITableViewAutomaticDimension
57 | }
58 | ```
59 |
60 | #### Step 3. The Section Header ####
61 |
62 | According to [Apple API reference](https://developer.apple.com/reference/uikit/uitableviewheaderfooterview), we should use `UITableViewHeaderFooterView`. Let's subclass it and implement the section header `CollapsibleTableViewHeader`:
63 |
64 | ```swift
65 | class CollapsibleTableViewHeader: UITableViewHeaderFooterView {
66 | let titleLabel = UILabel()
67 | let arrowLabel = UILabel()
68 |
69 | override init(reuseIdentifier: String?) {
70 | super.init(reuseIdentifier: reuseIdentifier)
71 |
72 | contentView.addSubview(titleLabel)
73 | contentView.addSubview(arrowLabel)
74 | }
75 |
76 | required init?(coder aDecoder: NSCoder) {
77 | fatalError("init(coder:) has not been implemented")
78 | }
79 | }
80 | ```
81 |
82 | We need to collapse or expand the section when user taps on the header, to achieve this, let's borrow `UITapGestureRecognizer`. Also we need to delegate this event to the table view to update the `collapsed` property.
83 |
84 | ```swift
85 | protocol CollapsibleTableViewHeaderDelegate {
86 | func toggleSection(_ header: CollapsibleTableViewHeader, section: Int)
87 | }
88 |
89 | class CollapsibleTableViewHeader: UITableViewHeaderFooterView {
90 | var delegate: CollapsibleTableViewHeaderDelegate?
91 | var section: Int = 0
92 | ...
93 | override init(reuseIdentifier: String?) {
94 | super.init(reuseIdentifier: reuseIdentifier)
95 | ...
96 | addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(CollapsibleTableViewHeader.tapHeader(_:))))
97 | }
98 | ...
99 | func tapHeader(_ gestureRecognizer: UITapGestureRecognizer) {
100 | guard let cell = gestureRecognizer.view as? CollapsibleTableViewHeader else {
101 | return
102 | }
103 | delegate?.toggleSection(self, section: cell.section)
104 | }
105 |
106 | func setCollapsed(_ collapsed: Bool) {
107 | // Animate the arrow rotation (see Extensions.swf)
108 | arrowLabel.rotate(collapsed ? 0.0 : .pi / 2)
109 | }
110 | }
111 | ```
112 |
113 | Since we are not using any storyboard or XIB, how to do auto layout programmatically? The answer is `constraint anchors`.
114 |
115 | ```swift
116 | override init(reuseIdentifier: String?) {
117 | ...
118 | // Content View
119 | contentView.backgroundColor = UIColor(hex: 0x2E3944)
120 |
121 | let marginGuide = contentView.layoutMarginsGuide
122 |
123 | // Arrow label
124 | contentView.addSubview(arrowLabel)
125 | arrowLabel.textColor = UIColor.white
126 | arrowLabel.translatesAutoresizingMaskIntoConstraints = false
127 | arrowLabel.widthAnchor.constraint(equalToConstant: 12).isActive = true
128 | arrowLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor).isActive = true
129 | arrowLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true
130 | arrowLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
131 |
132 | // Title label
133 | contentView.addSubview(titleLabel)
134 | titleLabel.textColor = UIColor.white
135 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
136 | titleLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor).isActive = true
137 | titleLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true
138 | titleLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
139 | titleLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor).isActive = true
140 | }
141 | ```
142 |
143 | #### Step 4. The UITableView DataSource and Delegate ####
144 |
145 | Now we implemented the header view, let's get back to the table view controller.
146 |
147 | The number of sections is `sections.count`:
148 |
149 | ```swift
150 | override func numberOfSectionsInTableView(in tableView: UITableView) -> Int {
151 | return sections.count
152 | }
153 | ```
154 |
155 | Here is the key ingredient of implementing the collapsible table section, if the section is collapsed, then that section should not have any row:
156 |
157 | ```swift
158 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
159 | return sections[section].collapsed ? 0 : sections[section].items.count
160 | }
161 | ```
162 |
163 | Noticed that we don't need to render any cell for the collapsed section, this can improve the performance a lot if there are tons of cells in that section.
164 |
165 | Next, we use tableView's viewForHeaderInSection function to hook up our custom header:
166 |
167 | ```swift
168 | override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
169 | let header = tableView.dequeueReusableHeaderFooterViewWithIdentifier("header") as? CollapsibleTableViewHeader ?? CollapsibleTableViewHeader(reuseIdentifier: "header")
170 |
171 | header.titleLabel.text = sections[section].name
172 | header.arrowLabel.text = ">"
173 | header.setCollapsed(sections[section].collapsed)
174 |
175 | header.section = section
176 | header.delegate = self
177 |
178 | return header
179 | }
180 | ```
181 |
182 | Setup the normal row cell is pretty straightforward:
183 |
184 | ```swift
185 | override func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
186 | let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as UITableViewCell? ?? UITableViewCell(style: .default, reuseIdentifier: "Cell")
187 | cell.textLabel?.text = sections[indexPath.section].items[indexPath.row]
188 | return cell
189 | }
190 | ```
191 |
192 | In the above code, we use the plain `UITableViewCell`, if you would like to see how to make a autosizing cell, please take a look at our `CollapsibleTableViewCell` in the source code. The `CollapsibleTableViewCell` is a subclass of `UITableViewCell` that adds the name and detail labels, and the most important thing is that it supports autosizing feature, the key is to setup the autolayout constrains properly, make sure the subviews are proplery stretched to the top and bottom in the `contentView`.
193 |
194 | #### Step 5. How to Toggle Collapse and Expand ####
195 |
196 | The idea is pretty starightforward, reverse the `collapsed` flag for the section and tell the tableView to reload that section:
197 |
198 | ```swift
199 | extension CollapsibleTableViewController: CollapsibleTableViewHeaderDelegate {
200 | func toggleSection(_ header: CollapsibleTableViewHeader, section: Int) {
201 | let collapsed = !sections[section].collapsed
202 |
203 | // Toggle collapse
204 | sections[section].collapsed = collapsed
205 | header.setCollapsed(collapsed)
206 |
207 | // Reload the whole section
208 | tableView.reloadSections(NSIndexSet(index: section) as IndexSet, with: .automatic)
209 | }
210 | }
211 | ```
212 |
213 | After the sections get reloaded, the number of cells in that section will be recalculated and redrawn.
214 |
215 | That's it, we have implemented the collapsible table section! Please refer to the source code and see the detailed implementation.
216 |
217 | ## More Collapsible Demo ##
218 |
219 | Sometimes you might want to implement the collapsible cells in a grouped-style table, I have a separate demo at [https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section](https://github.com/jeantimex/ios-swift-collapsible-table-section-in-grouped-section). The implementation is pretty much the same but slightly different.
220 |
221 | ## Can I use it as a pod? ##
222 |
223 | :tada: Yes! The CocoaPod is finally released, see [CollapsibleTableSectionViewController](https://github.com/jeantimex/CollapsibleTableSectionViewController).
224 |
225 | ## Support ##
226 |
227 |
228 |
229 |
230 | ## Contributors
231 |
232 | This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
233 |
236 |
237 | ## Backers
238 |
239 | Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com/ios-collapsible-table-section#backer)]
240 |
241 |
242 |
243 |
244 | ## Sponsors
245 |
246 | Support this project by becoming a sponsor. Your logo will show up here with a link to your website. [[Become a sponsor](https://opencollective.com/ios-collapsible-table-section#sponsor)]
247 |
250 |
251 | ## License ##
252 |
253 | MIT License
254 |
255 | Copyright (c) 2019 Yong Su @jeantimex
256 |
257 | Permission is hereby granted, free of charge, to any person obtaining a copy
258 | of this software and associated documentation files (the "Software"), to deal
259 | in the Software without restriction, including without limitation the rights
260 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
261 | copies of the Software, and to permit persons to whom the Software is
262 | furnished to do so, subject to the following conditions:
263 |
264 | The above copyright notice and this permission notice shall be included in all
265 | copies or substantial portions of the Software.
266 |
267 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
268 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
269 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
270 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
271 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
272 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
273 | SOFTWARE.
274 |
275 |
--------------------------------------------------------------------------------
/docs/images/cover.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeantimex/ios-swift-collapsible-table-section/ed1677a1f5450f336a69bcc74fad255df3d5f5b0/docs/images/cover.gif
--------------------------------------------------------------------------------
/ios-swift-collapsible-table-section.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 0A12C8C21D9C3AAF00D0BEE3 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12C8C11D9C3AAF00D0BEE3 /* Extensions.swift */; };
11 | 0A6AFF931F31311800FA070E /* ExampleData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6AFF921F31311800FA070E /* ExampleData.swift */; };
12 | 0A8465851F1DBC6E002CD874 /* CollapsibleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A8465841F1DBC6E002CD874 /* CollapsibleTableViewCell.swift */; };
13 | 0A908DEF1CFCAA9200470F33 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A908DEE1CFCAA9200470F33 /* AppDelegate.swift */; };
14 | 0A908DF61CFCAA9200470F33 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 0A908DF51CFCAA9200470F33 /* Assets.xcassets */; };
15 | 0A908DF91CFCAA9200470F33 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0A908DF71CFCAA9200470F33 /* LaunchScreen.storyboard */; };
16 | 0A908E011CFCAC7500470F33 /* CollapsibleTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A908E001CFCAC7500470F33 /* CollapsibleTableViewController.swift */; };
17 | 0A908E031CFCB8D600470F33 /* CollapsibleTableViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A908E021CFCB8D600470F33 /* CollapsibleTableViewHeader.swift */; };
18 | /* End PBXBuildFile section */
19 |
20 | /* Begin PBXFileReference section */
21 | 0A12C8C11D9C3AAF00D0BEE3 /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = ""; };
22 | 0A6AFF921F31311800FA070E /* ExampleData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExampleData.swift; sourceTree = ""; };
23 | 0A8465841F1DBC6E002CD874 /* CollapsibleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollapsibleTableViewCell.swift; sourceTree = ""; };
24 | 0A908DEB1CFCAA9200470F33 /* ios-swift-collapsible-table-section.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ios-swift-collapsible-table-section.app"; sourceTree = BUILT_PRODUCTS_DIR; };
25 | 0A908DEE1CFCAA9200470F33 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
26 | 0A908DF51CFCAA9200470F33 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
27 | 0A908DF81CFCAA9200470F33 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
28 | 0A908DFA1CFCAA9200470F33 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | 0A908E001CFCAC7500470F33 /* CollapsibleTableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollapsibleTableViewController.swift; sourceTree = ""; };
30 | 0A908E021CFCB8D600470F33 /* CollapsibleTableViewHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollapsibleTableViewHeader.swift; sourceTree = ""; };
31 | /* End PBXFileReference section */
32 |
33 | /* Begin PBXFrameworksBuildPhase section */
34 | 0A908DE81CFCAA9200470F33 /* Frameworks */ = {
35 | isa = PBXFrameworksBuildPhase;
36 | buildActionMask = 2147483647;
37 | files = (
38 | );
39 | runOnlyForDeploymentPostprocessing = 0;
40 | };
41 | /* End PBXFrameworksBuildPhase section */
42 |
43 | /* Begin PBXGroup section */
44 | 0A908DE21CFCAA9200470F33 = {
45 | isa = PBXGroup;
46 | children = (
47 | 0A908DED1CFCAA9200470F33 /* ios-swift-collapsible-table-section */,
48 | 0A908DEC1CFCAA9200470F33 /* Products */,
49 | );
50 | sourceTree = "";
51 | };
52 | 0A908DEC1CFCAA9200470F33 /* Products */ = {
53 | isa = PBXGroup;
54 | children = (
55 | 0A908DEB1CFCAA9200470F33 /* ios-swift-collapsible-table-section.app */,
56 | );
57 | name = Products;
58 | sourceTree = "";
59 | };
60 | 0A908DED1CFCAA9200470F33 /* ios-swift-collapsible-table-section */ = {
61 | isa = PBXGroup;
62 | children = (
63 | 0A908DEE1CFCAA9200470F33 /* AppDelegate.swift */,
64 | 0A908DF51CFCAA9200470F33 /* Assets.xcassets */,
65 | 0A908DF71CFCAA9200470F33 /* LaunchScreen.storyboard */,
66 | 0A908DFA1CFCAA9200470F33 /* Info.plist */,
67 | 0A908E001CFCAC7500470F33 /* CollapsibleTableViewController.swift */,
68 | 0A908E021CFCB8D600470F33 /* CollapsibleTableViewHeader.swift */,
69 | 0A12C8C11D9C3AAF00D0BEE3 /* Extensions.swift */,
70 | 0A8465841F1DBC6E002CD874 /* CollapsibleTableViewCell.swift */,
71 | 0A6AFF921F31311800FA070E /* ExampleData.swift */,
72 | );
73 | path = "ios-swift-collapsible-table-section";
74 | sourceTree = "";
75 | };
76 | /* End PBXGroup section */
77 |
78 | /* Begin PBXNativeTarget section */
79 | 0A908DEA1CFCAA9200470F33 /* ios-swift-collapsible-table-section */ = {
80 | isa = PBXNativeTarget;
81 | buildConfigurationList = 0A908DFD1CFCAA9200470F33 /* Build configuration list for PBXNativeTarget "ios-swift-collapsible-table-section" */;
82 | buildPhases = (
83 | 0A908DE71CFCAA9200470F33 /* Sources */,
84 | 0A908DE81CFCAA9200470F33 /* Frameworks */,
85 | 0A908DE91CFCAA9200470F33 /* Resources */,
86 | );
87 | buildRules = (
88 | );
89 | dependencies = (
90 | );
91 | name = "ios-swift-collapsible-table-section";
92 | productName = "ios-swift-collapsible-table-section";
93 | productReference = 0A908DEB1CFCAA9200470F33 /* ios-swift-collapsible-table-section.app */;
94 | productType = "com.apple.product-type.application";
95 | };
96 | /* End PBXNativeTarget section */
97 |
98 | /* Begin PBXProject section */
99 | 0A908DE31CFCAA9200470F33 /* Project object */ = {
100 | isa = PBXProject;
101 | attributes = {
102 | LastSwiftUpdateCheck = 0730;
103 | LastUpgradeCheck = 1010;
104 | ORGANIZATIONNAME = "Yong Su";
105 | TargetAttributes = {
106 | 0A908DEA1CFCAA9200470F33 = {
107 | CreatedOnToolsVersion = 7.3.1;
108 | LastSwiftMigration = 1030;
109 | };
110 | };
111 | };
112 | buildConfigurationList = 0A908DE61CFCAA9200470F33 /* Build configuration list for PBXProject "ios-swift-collapsible-table-section" */;
113 | compatibilityVersion = "Xcode 3.2";
114 | developmentRegion = en;
115 | hasScannedForEncodings = 0;
116 | knownRegions = (
117 | en,
118 | Base,
119 | );
120 | mainGroup = 0A908DE21CFCAA9200470F33;
121 | productRefGroup = 0A908DEC1CFCAA9200470F33 /* Products */;
122 | projectDirPath = "";
123 | projectRoot = "";
124 | targets = (
125 | 0A908DEA1CFCAA9200470F33 /* ios-swift-collapsible-table-section */,
126 | );
127 | };
128 | /* End PBXProject section */
129 |
130 | /* Begin PBXResourcesBuildPhase section */
131 | 0A908DE91CFCAA9200470F33 /* Resources */ = {
132 | isa = PBXResourcesBuildPhase;
133 | buildActionMask = 2147483647;
134 | files = (
135 | 0A908DF91CFCAA9200470F33 /* LaunchScreen.storyboard in Resources */,
136 | 0A908DF61CFCAA9200470F33 /* Assets.xcassets in Resources */,
137 | );
138 | runOnlyForDeploymentPostprocessing = 0;
139 | };
140 | /* End PBXResourcesBuildPhase section */
141 |
142 | /* Begin PBXSourcesBuildPhase section */
143 | 0A908DE71CFCAA9200470F33 /* Sources */ = {
144 | isa = PBXSourcesBuildPhase;
145 | buildActionMask = 2147483647;
146 | files = (
147 | 0A908E011CFCAC7500470F33 /* CollapsibleTableViewController.swift in Sources */,
148 | 0A908E031CFCB8D600470F33 /* CollapsibleTableViewHeader.swift in Sources */,
149 | 0A6AFF931F31311800FA070E /* ExampleData.swift in Sources */,
150 | 0A8465851F1DBC6E002CD874 /* CollapsibleTableViewCell.swift in Sources */,
151 | 0A908DEF1CFCAA9200470F33 /* AppDelegate.swift in Sources */,
152 | 0A12C8C21D9C3AAF00D0BEE3 /* Extensions.swift in Sources */,
153 | );
154 | runOnlyForDeploymentPostprocessing = 0;
155 | };
156 | /* End PBXSourcesBuildPhase section */
157 |
158 | /* Begin PBXVariantGroup section */
159 | 0A908DF71CFCAA9200470F33 /* LaunchScreen.storyboard */ = {
160 | isa = PBXVariantGroup;
161 | children = (
162 | 0A908DF81CFCAA9200470F33 /* Base */,
163 | );
164 | name = LaunchScreen.storyboard;
165 | sourceTree = "";
166 | };
167 | /* End PBXVariantGroup section */
168 |
169 | /* Begin XCBuildConfiguration section */
170 | 0A908DFB1CFCAA9200470F33 /* Debug */ = {
171 | isa = XCBuildConfiguration;
172 | buildSettings = {
173 | ALWAYS_SEARCH_USER_PATHS = NO;
174 | CLANG_ANALYZER_NONNULL = YES;
175 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
176 | CLANG_CXX_LIBRARY = "libc++";
177 | CLANG_ENABLE_MODULES = YES;
178 | CLANG_ENABLE_OBJC_ARC = YES;
179 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
180 | CLANG_WARN_BOOL_CONVERSION = YES;
181 | CLANG_WARN_COMMA = YES;
182 | CLANG_WARN_CONSTANT_CONVERSION = YES;
183 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
184 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
185 | CLANG_WARN_EMPTY_BODY = YES;
186 | CLANG_WARN_ENUM_CONVERSION = YES;
187 | CLANG_WARN_INFINITE_RECURSION = YES;
188 | CLANG_WARN_INT_CONVERSION = YES;
189 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
190 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
191 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
192 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
193 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
194 | CLANG_WARN_STRICT_PROTOTYPES = YES;
195 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
196 | CLANG_WARN_UNREACHABLE_CODE = YES;
197 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
198 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
199 | COPY_PHASE_STRIP = NO;
200 | DEBUG_INFORMATION_FORMAT = dwarf;
201 | ENABLE_STRICT_OBJC_MSGSEND = YES;
202 | ENABLE_TESTABILITY = YES;
203 | GCC_C_LANGUAGE_STANDARD = gnu99;
204 | GCC_DYNAMIC_NO_PIC = NO;
205 | GCC_NO_COMMON_BLOCKS = YES;
206 | GCC_OPTIMIZATION_LEVEL = 0;
207 | GCC_PREPROCESSOR_DEFINITIONS = (
208 | "DEBUG=1",
209 | "$(inherited)",
210 | );
211 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
212 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
213 | GCC_WARN_UNDECLARED_SELECTOR = YES;
214 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
215 | GCC_WARN_UNUSED_FUNCTION = YES;
216 | GCC_WARN_UNUSED_VARIABLE = YES;
217 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
218 | MTL_ENABLE_DEBUG_INFO = YES;
219 | ONLY_ACTIVE_ARCH = YES;
220 | SDKROOT = iphoneos;
221 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
222 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
223 | SWIFT_VERSION = 4.2;
224 | TARGETED_DEVICE_FAMILY = "1,2";
225 | };
226 | name = Debug;
227 | };
228 | 0A908DFC1CFCAA9200470F33 /* Release */ = {
229 | isa = XCBuildConfiguration;
230 | buildSettings = {
231 | ALWAYS_SEARCH_USER_PATHS = NO;
232 | CLANG_ANALYZER_NONNULL = YES;
233 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
234 | CLANG_CXX_LIBRARY = "libc++";
235 | CLANG_ENABLE_MODULES = YES;
236 | CLANG_ENABLE_OBJC_ARC = YES;
237 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
238 | CLANG_WARN_BOOL_CONVERSION = YES;
239 | CLANG_WARN_COMMA = YES;
240 | CLANG_WARN_CONSTANT_CONVERSION = YES;
241 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
242 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
243 | CLANG_WARN_EMPTY_BODY = YES;
244 | CLANG_WARN_ENUM_CONVERSION = YES;
245 | CLANG_WARN_INFINITE_RECURSION = YES;
246 | CLANG_WARN_INT_CONVERSION = YES;
247 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
248 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
249 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
250 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
251 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
252 | CLANG_WARN_STRICT_PROTOTYPES = YES;
253 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
254 | CLANG_WARN_UNREACHABLE_CODE = YES;
255 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
256 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
257 | COPY_PHASE_STRIP = NO;
258 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
259 | ENABLE_NS_ASSERTIONS = NO;
260 | ENABLE_STRICT_OBJC_MSGSEND = YES;
261 | GCC_C_LANGUAGE_STANDARD = gnu99;
262 | GCC_NO_COMMON_BLOCKS = YES;
263 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
264 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
265 | GCC_WARN_UNDECLARED_SELECTOR = YES;
266 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
267 | GCC_WARN_UNUSED_FUNCTION = YES;
268 | GCC_WARN_UNUSED_VARIABLE = YES;
269 | IPHONEOS_DEPLOYMENT_TARGET = 9.3;
270 | MTL_ENABLE_DEBUG_INFO = NO;
271 | SDKROOT = iphoneos;
272 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
273 | SWIFT_SWIFT3_OBJC_INFERENCE = Default;
274 | SWIFT_VERSION = 4.2;
275 | TARGETED_DEVICE_FAMILY = "1,2";
276 | VALIDATE_PRODUCT = YES;
277 | };
278 | name = Release;
279 | };
280 | 0A908DFE1CFCAA9200470F33 /* Debug */ = {
281 | isa = XCBuildConfiguration;
282 | buildSettings = {
283 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
284 | INFOPLIST_FILE = "ios-swift-collapsible-table-section/Info.plist";
285 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
286 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
287 | PRODUCT_BUNDLE_IDENTIFIER = "su.ios-swift-collapsible-table-section";
288 | PRODUCT_NAME = "$(TARGET_NAME)";
289 | SWIFT_VERSION = 5.0;
290 | };
291 | name = Debug;
292 | };
293 | 0A908DFF1CFCAA9200470F33 /* Release */ = {
294 | isa = XCBuildConfiguration;
295 | buildSettings = {
296 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
297 | INFOPLIST_FILE = "ios-swift-collapsible-table-section/Info.plist";
298 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
299 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
300 | PRODUCT_BUNDLE_IDENTIFIER = "su.ios-swift-collapsible-table-section";
301 | PRODUCT_NAME = "$(TARGET_NAME)";
302 | SWIFT_VERSION = 5.0;
303 | };
304 | name = Release;
305 | };
306 | /* End XCBuildConfiguration section */
307 |
308 | /* Begin XCConfigurationList section */
309 | 0A908DE61CFCAA9200470F33 /* Build configuration list for PBXProject "ios-swift-collapsible-table-section" */ = {
310 | isa = XCConfigurationList;
311 | buildConfigurations = (
312 | 0A908DFB1CFCAA9200470F33 /* Debug */,
313 | 0A908DFC1CFCAA9200470F33 /* Release */,
314 | );
315 | defaultConfigurationIsVisible = 0;
316 | defaultConfigurationName = Release;
317 | };
318 | 0A908DFD1CFCAA9200470F33 /* Build configuration list for PBXNativeTarget "ios-swift-collapsible-table-section" */ = {
319 | isa = XCConfigurationList;
320 | buildConfigurations = (
321 | 0A908DFE1CFCAA9200470F33 /* Debug */,
322 | 0A908DFF1CFCAA9200470F33 /* Release */,
323 | );
324 | defaultConfigurationIsVisible = 0;
325 | defaultConfigurationName = Release;
326 | };
327 | /* End XCConfigurationList section */
328 | };
329 | rootObject = 0A908DE31CFCAA9200470F33 /* Project object */;
330 | }
331 |
--------------------------------------------------------------------------------
/ios-swift-collapsible-table-section.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/ios-swift-collapsible-table-section/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ios-swift-collapsible-table-section
4 | //
5 | // Created by Yong Su on 5/30/16.
6 | // Copyright © 2016 Yong Su. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | self.window = UIWindow(frame:UIScreen.main.bounds)
20 | self.window?.makeKeyAndVisible()
21 | self.window?.rootViewController = UINavigationController(rootViewController: CollapsibleTableViewController())
22 | return true
23 | }
24 |
25 | func applicationWillResignActive(_ application: UIApplication) {
26 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
27 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
28 | }
29 |
30 | func applicationDidEnterBackground(_ application: UIApplication) {
31 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
32 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
33 | }
34 |
35 | func applicationWillEnterForeground(_ application: UIApplication) {
36 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
37 | }
38 |
39 | func applicationDidBecomeActive(_ application: UIApplication) {
40 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
41 | }
42 |
43 | func applicationWillTerminate(_ application: UIApplication) {
44 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
45 | }
46 |
47 |
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/ios-swift-collapsible-table-section/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "29x29",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "29x29",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "40x40",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "40x40",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "60x60",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "60x60",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "ipad",
35 | "size" : "29x29",
36 | "scale" : "1x"
37 | },
38 | {
39 | "idiom" : "ipad",
40 | "size" : "29x29",
41 | "scale" : "2x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "40x40",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "40x40",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "76x76",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "76x76",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/ios-swift-collapsible-table-section/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 |
27 |
28 |
--------------------------------------------------------------------------------
/ios-swift-collapsible-table-section/CollapsibleTableViewCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollapsibleTableViewCell.swift
3 | // ios-swift-collapsible-table-section
4 | //
5 | // Created by Yong Su on 7/17/17.
6 | // Copyright © 2017 Yong Su. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class CollapsibleTableViewCell: UITableViewCell {
12 |
13 | let nameLabel = UILabel()
14 | let detailLabel = UILabel()
15 |
16 | // MARK: Initalizers
17 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
18 | super.init(style: style, reuseIdentifier: reuseIdentifier)
19 |
20 | let marginGuide = contentView.layoutMarginsGuide
21 |
22 | // configure nameLabel
23 | contentView.addSubview(nameLabel)
24 | nameLabel.translatesAutoresizingMaskIntoConstraints = false
25 | nameLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor).isActive = true
26 | nameLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor).isActive = true
27 | nameLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true
28 | nameLabel.numberOfLines = 0
29 | nameLabel.font = UIFont.systemFont(ofSize: 16)
30 |
31 | // configure detailLabel
32 | contentView.addSubview(detailLabel)
33 | detailLabel.lineBreakMode = .byWordWrapping
34 | detailLabel.translatesAutoresizingMaskIntoConstraints = false
35 | detailLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor).isActive = true
36 | detailLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
37 | detailLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true
38 | detailLabel.topAnchor.constraint(equalTo: nameLabel.bottomAnchor, constant: 5).isActive = true
39 | detailLabel.numberOfLines = 0
40 | detailLabel.font = UIFont.systemFont(ofSize: 12)
41 | detailLabel.textColor = UIColor.lightGray
42 | }
43 |
44 | required init?(coder aDecoder: NSCoder) {
45 | fatalError("init(coder:) has not been implemented")
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/ios-swift-collapsible-table-section/CollapsibleTableViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollapsibleTableViewController.swift
3 | // ios-swift-collapsible-table-section
4 | //
5 | // Created by Yong Su on 5/30/16.
6 | // Copyright © 2016 Yong Su. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | //
12 | // MARK: - View Controller
13 | //
14 | class CollapsibleTableViewController: UITableViewController {
15 |
16 | var sections = sectionsData
17 |
18 | override func viewDidLoad() {
19 | super.viewDidLoad()
20 |
21 | // Auto resizing the height of the cell
22 | tableView.estimatedRowHeight = 44.0
23 | tableView.rowHeight = UITableView.automaticDimension
24 |
25 | self.title = "Apple Products"
26 | }
27 |
28 | }
29 |
30 | //
31 | // MARK: - View Controller DataSource and Delegate
32 | //
33 | extension CollapsibleTableViewController {
34 |
35 | override func numberOfSections(in tableView: UITableView) -> Int {
36 | return sections.count
37 | }
38 |
39 | override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
40 | return sections[section].collapsed ? 0 : sections[section].items.count
41 | }
42 |
43 | // Cell
44 | override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
45 | let cell: CollapsibleTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell") as? CollapsibleTableViewCell ??
46 | CollapsibleTableViewCell(style: .default, reuseIdentifier: "cell")
47 |
48 | let item: Item = sections[indexPath.section].items[indexPath.row]
49 |
50 | cell.nameLabel.text = item.name
51 | cell.detailLabel.text = item.detail
52 |
53 | return cell
54 | }
55 |
56 | override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
57 | return UITableView.automaticDimension
58 | }
59 |
60 | // Header
61 | override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
62 | let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as? CollapsibleTableViewHeader ?? CollapsibleTableViewHeader(reuseIdentifier: "header")
63 |
64 | header.titleLabel.text = sections[section].name
65 | header.arrowLabel.text = ">"
66 | header.setCollapsed(sections[section].collapsed)
67 |
68 | header.section = section
69 | header.delegate = self
70 |
71 | return header
72 | }
73 |
74 | override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
75 | return 44.0
76 | }
77 |
78 | override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
79 | return 1.0
80 | }
81 |
82 | }
83 |
84 | //
85 | // MARK: - Section Header Delegate
86 | //
87 | extension CollapsibleTableViewController: CollapsibleTableViewHeaderDelegate {
88 |
89 | func toggleSection(_ header: CollapsibleTableViewHeader, section: Int) {
90 | let collapsed = !sections[section].collapsed
91 |
92 | // Toggle collapse
93 | sections[section].collapsed = collapsed
94 | header.setCollapsed(collapsed)
95 |
96 | tableView.reloadSections(NSIndexSet(index: section) as IndexSet, with: .automatic)
97 | }
98 |
99 | }
100 |
--------------------------------------------------------------------------------
/ios-swift-collapsible-table-section/CollapsibleTableViewHeader.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CollapsibleTableViewHeader.swift
3 | // ios-swift-collapsible-table-section
4 | //
5 | // Created by Yong Su on 5/30/16.
6 | // Copyright © 2016 Yong Su. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | protocol CollapsibleTableViewHeaderDelegate {
12 | func toggleSection(_ header: CollapsibleTableViewHeader, section: Int)
13 | }
14 |
15 | class CollapsibleTableViewHeader: UITableViewHeaderFooterView {
16 |
17 | var delegate: CollapsibleTableViewHeaderDelegate?
18 | var section: Int = 0
19 |
20 | let titleLabel = UILabel()
21 | let arrowLabel = UILabel()
22 |
23 | override init(reuseIdentifier: String?) {
24 | super.init(reuseIdentifier: reuseIdentifier)
25 |
26 | // Content View
27 | contentView.backgroundColor = UIColor(hex: 0x2E3944)
28 |
29 | let marginGuide = contentView.layoutMarginsGuide
30 |
31 | // Arrow label
32 | contentView.addSubview(arrowLabel)
33 | arrowLabel.textColor = UIColor.white
34 | arrowLabel.translatesAutoresizingMaskIntoConstraints = false
35 | arrowLabel.widthAnchor.constraint(equalToConstant: 12).isActive = true
36 | arrowLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor).isActive = true
37 | arrowLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true
38 | arrowLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
39 |
40 | // Title label
41 | contentView.addSubview(titleLabel)
42 | titleLabel.textColor = UIColor.white
43 | titleLabel.translatesAutoresizingMaskIntoConstraints = false
44 | titleLabel.topAnchor.constraint(equalTo: marginGuide.topAnchor).isActive = true
45 | titleLabel.trailingAnchor.constraint(equalTo: marginGuide.trailingAnchor).isActive = true
46 | titleLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor).isActive = true
47 | titleLabel.leadingAnchor.constraint(equalTo: marginGuide.leadingAnchor).isActive = true
48 |
49 | //
50 | // Call tapHeader when tapping on this header
51 | //
52 | addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(CollapsibleTableViewHeader.tapHeader(_:))))
53 | }
54 |
55 | required init?(coder aDecoder: NSCoder) {
56 | fatalError("init(coder:) has not been implemented")
57 | }
58 |
59 | //
60 | // Trigger toggle section when tapping on the header
61 | //
62 | @objc func tapHeader(_ gestureRecognizer: UITapGestureRecognizer) {
63 | guard let cell = gestureRecognizer.view as? CollapsibleTableViewHeader else {
64 | return
65 | }
66 |
67 | delegate?.toggleSection(self, section: cell.section)
68 | }
69 |
70 | func setCollapsed(_ collapsed: Bool) {
71 | //
72 | // Animate the arrow rotation (see Extensions.swf)
73 | //
74 | arrowLabel.rotate(collapsed ? 0.0 : .pi / 2)
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/ios-swift-collapsible-table-section/ExampleData.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExampleData.swift
3 | // ios-swift-collapsible-table-section
4 | //
5 | // Created by Yong Su on 8/1/17.
6 | // Copyright © 2017 Yong Su. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | //
12 | // MARK: - Section Data Structure
13 | //
14 | public struct Item {
15 | var name: String
16 | var detail: String
17 |
18 | public init(name: String, detail: String) {
19 | self.name = name
20 | self.detail = detail
21 | }
22 | }
23 |
24 | public struct Section {
25 | var name: String
26 | var items: [Item]
27 | var collapsed: Bool
28 |
29 | public init(name: String, items: [Item], collapsed: Bool = false) {
30 | self.name = name
31 | self.items = items
32 | self.collapsed = collapsed
33 | }
34 | }
35 |
36 | public var sectionsData: [Section] = [
37 | Section(name: "Mac", items: [
38 | Item(name: "MacBook", detail: "Apple's ultraportable laptop, trading portability for speed and connectivity."),
39 | Item(name: "MacBook Air", detail: "While the screen could be sharper, the updated 11-inch MacBook Air is a very light ultraportable that offers great performance and battery life for the price."),
40 | Item(name: "MacBook Pro", detail: "Retina Display The brightest, most colorful Mac notebook display ever. The display in the MacBook Pro is the best ever in a Mac notebook."),
41 | Item(name: "iMac", detail: "iMac combines enhanced performance with our best ever Retina display for the ultimate desktop experience in two sizes."),
42 | Item(name: "Mac Pro", detail: "Mac Pro is equipped with pro-level graphics, storage, expansion, processing power, and memory. It's built for creativity on an epic scale."),
43 | Item(name: "Mac mini", detail: "Mac mini is an affordable powerhouse that packs the entire Mac experience into a 7.7-inch-square frame."),
44 | Item(name: "OS X El Capitan", detail: "The twelfth major release of OS X (now named macOS)."),
45 | Item(name: "Accessories", detail: "")
46 | ]),
47 | Section(name: "iPad", items: [
48 | Item(name: "iPad Pro", detail: "iPad Pro delivers epic power, in 12.9-inch and a new 10.5-inch size."),
49 | Item(name: "iPad Air 2", detail: "The second-generation iPad Air tablet computer designed, developed, and marketed by Apple Inc."),
50 | Item(name: "iPad mini 4", detail: "iPad mini 4 puts uncompromising performance and potential in your hand."),
51 | Item(name: "Accessories", detail: "")
52 | ]),
53 | Section(name: "iPhone", items: [
54 | Item(name: "iPhone 6s", detail: "The iPhone 6S has a similar design to the 6 but updated hardware, including a strengthened chassis and upgraded system-on-chip, a 12-megapixel camera, improved fingerprint recognition sensor, and LTE Advanced support."),
55 | Item(name: "iPhone 6", detail: "The iPhone 6 and iPhone 6 Plus are smartphones designed and marketed by Apple Inc."),
56 | Item(name: "iPhone SE", detail: "The iPhone SE was received positively by critics, who noted its familiar form factor and design, improved hardware over previous 4-inch iPhone models, as well as its overall performance and battery life."),
57 | Item(name: "Accessories", detail: "")
58 | ])
59 | ]
60 |
--------------------------------------------------------------------------------
/ios-swift-collapsible-table-section/Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Extensions.swift
3 | // ios-swift-collapsible-table-section
4 | //
5 | // Created by Yong Su on 9/28/16.
6 | // Copyright © 2016 Yong Su. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | extension UIColor {
12 |
13 | convenience init(hex:Int, alpha:CGFloat = 1.0) {
14 | self.init(
15 | red: CGFloat((hex & 0xFF0000) >> 16) / 255.0,
16 | green: CGFloat((hex & 0x00FF00) >> 8) / 255.0,
17 | blue: CGFloat((hex & 0x0000FF) >> 0) / 255.0,
18 | alpha: alpha
19 | )
20 | }
21 |
22 | }
23 |
24 | extension UIView {
25 |
26 | func rotate(_ toValue: CGFloat, duration: CFTimeInterval = 0.2) {
27 | let animation = CABasicAnimation(keyPath: "transform.rotation")
28 |
29 | animation.toValue = toValue
30 | animation.duration = duration
31 | animation.isRemovedOnCompletion = false
32 | animation.fillMode = CAMediaTimingFillMode.forwards
33 |
34 | self.layer.add(animation, forKey: nil)
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/ios-swift-collapsible-table-section/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 | LSRequiresIPhoneOS
24 |
25 | UILaunchStoryboardName
26 | LaunchScreen
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------