├── .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 | [![Language](https://img.shields.io/badge/swift-5-brightgreen.svg?style=flat)]() 4 | [![Backers on Open Collective](https://opencollective.com/ios-collapsible-table-section/backers/badge.svg)](#backers) 5 | [![Sponsors on Open Collective](https://opencollective.com/ios-collapsible-table-section/sponsors/badge.svg)](#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 | ![cover](https://user-images.githubusercontent.com/565300/33296371-1c17e332-d390-11e7-910b-947ed42fcbb3.gif) 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 | But Me a Coffee 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 | --------------------------------------------------------------------------------