Skip to content

Commit 4af4c46

Browse files
authored
Merge pull request #2 from jeantimex/added-collapsible-table-view-controller
Added the collapsible table view controller.
2 parents ca3d97a + 0c5516c commit 4af4c46

5 files changed

Lines changed: 359 additions & 0 deletions

File tree

CollapsibleTableSectionViewController.xcodeproj/project.pbxproj

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
0AC8864C1F2184FD007E4E2F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 0AC8864A1F2184FD007E4E2F /* LaunchScreen.storyboard */; };
1515
0AC886571F2184FD007E4E2F /* CollapsibleTableSectionViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC886561F2184FD007E4E2F /* CollapsibleTableSectionViewControllerTests.swift */; };
1616
0AC886621F2184FD007E4E2F /* CollapsibleTableSectionViewControllerUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC886611F2184FD007E4E2F /* CollapsibleTableSectionViewControllerUITests.swift */; };
17+
0AC886701F218932007E4E2F /* CollapsibleTableSectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC8866F1F218932007E4E2F /* CollapsibleTableSectionViewController.swift */; };
18+
0AC886721F218CA8007E4E2F /* CollapsibleTableViewHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC886711F218CA8007E4E2F /* CollapsibleTableViewHeader.swift */; };
19+
0AC886741F218CE9007E4E2F /* CollapsibleTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC886731F218CE9007E4E2F /* CollapsibleTableViewCell.swift */; };
20+
0AC886761F218D0F007E4E2F /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AC886751F218D0F007E4E2F /* Extensions.swift */; };
1721
/* End PBXBuildFile section */
1822

1923
/* Begin PBXContainerItemProxy section */
@@ -47,6 +51,10 @@
4751
0AC8865D1F2184FD007E4E2F /* CollapsibleTableSectionViewControllerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CollapsibleTableSectionViewControllerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
4852
0AC886611F2184FD007E4E2F /* CollapsibleTableSectionViewControllerUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollapsibleTableSectionViewControllerUITests.swift; sourceTree = "<group>"; };
4953
0AC886631F2184FD007E4E2F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
54+
0AC8866F1F218932007E4E2F /* CollapsibleTableSectionViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollapsibleTableSectionViewController.swift; sourceTree = "<group>"; };
55+
0AC886711F218CA8007E4E2F /* CollapsibleTableViewHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollapsibleTableViewHeader.swift; sourceTree = "<group>"; };
56+
0AC886731F218CE9007E4E2F /* CollapsibleTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollapsibleTableViewCell.swift; sourceTree = "<group>"; };
57+
0AC886751F218D0F007E4E2F /* Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = "<group>"; };
5058
/* End PBXFileReference section */
5159

5260
/* Begin PBXFrameworksBuildPhase section */
@@ -103,6 +111,10 @@
103111
0AC886481F2184FD007E4E2F /* Assets.xcassets */,
104112
0AC8864A1F2184FD007E4E2F /* LaunchScreen.storyboard */,
105113
0AC8864D1F2184FD007E4E2F /* Info.plist */,
114+
0AC8866F1F218932007E4E2F /* CollapsibleTableSectionViewController.swift */,
115+
0AC886711F218CA8007E4E2F /* CollapsibleTableViewHeader.swift */,
116+
0AC886731F218CE9007E4E2F /* CollapsibleTableViewCell.swift */,
117+
0AC886751F218D0F007E4E2F /* Extensions.swift */,
106118
);
107119
path = CollapsibleTableSectionViewController;
108120
sourceTree = "<group>";
@@ -260,7 +272,11 @@
260272
isa = PBXSourcesBuildPhase;
261273
buildActionMask = 2147483647;
262274
files = (
275+
0AC886701F218932007E4E2F /* CollapsibleTableSectionViewController.swift in Sources */,
276+
0AC886761F218D0F007E4E2F /* Extensions.swift in Sources */,
263277
0AC886441F2184FD007E4E2F /* ViewController.swift in Sources */,
278+
0AC886741F218CE9007E4E2F /* CollapsibleTableViewCell.swift in Sources */,
279+
0AC886721F218CA8007E4E2F /* CollapsibleTableViewHeader.swift in Sources */,
264280
0AC886421F2184FD007E4E2F /* AppDelegate.swift in Sources */,
265281
);
266282
runOnlyForDeploymentPostprocessing = 0;
@@ -510,6 +526,7 @@
510526
0AC886681F2184FD007E4E2F /* Release */,
511527
);
512528
defaultConfigurationIsVisible = 0;
529+
defaultConfigurationName = Release;
513530
};
514531
0AC886691F2184FD007E4E2F /* Build configuration list for PBXNativeTarget "CollapsibleTableSectionViewControllerTests" */ = {
515532
isa = XCConfigurationList;
@@ -518,6 +535,7 @@
518535
0AC8866B1F2184FD007E4E2F /* Release */,
519536
);
520537
defaultConfigurationIsVisible = 0;
538+
defaultConfigurationName = Release;
521539
};
522540
0AC8866C1F2184FD007E4E2F /* Build configuration list for PBXNativeTarget "CollapsibleTableSectionViewControllerUITests" */ = {
523541
isa = XCConfigurationList;
@@ -526,6 +544,7 @@
526544
0AC8866E1F2184FD007E4E2F /* Release */,
527545
);
528546
defaultConfigurationIsVisible = 0;
547+
defaultConfigurationName = Release;
529548
};
530549
/* End XCConfigurationList section */
531550
};
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
//
2+
// CollapsibleTableSectionViewController.swift
3+
// CollapsibleTableSectionViewController
4+
//
5+
// Created by Yong Su on 7/20/17.
6+
// Copyright © 2017 jeantimex. All rights reserved.
7+
//
8+
9+
import UIKit
10+
11+
//
12+
// MARK: - Section Data Structure
13+
//
14+
struct Item {
15+
var name: String
16+
var detail: String
17+
18+
init(name: String, detail: String) {
19+
self.name = name
20+
self.detail = detail
21+
}
22+
}
23+
24+
struct Section {
25+
var name: String
26+
var items: [Item]
27+
var collapsed: Bool
28+
29+
init(name: String, items: [Item], collapsed: Bool = false) {
30+
self.name = name
31+
self.items = items
32+
self.collapsed = collapsed
33+
}
34+
}
35+
36+
//
37+
// MARK: - View Controller
38+
//
39+
class CollapsibleTableSectionViewController: UITableViewController {
40+
41+
var sections = [Section]()
42+
43+
override func viewDidLoad() {
44+
super.viewDidLoad()
45+
46+
// Auto resizing the height of the cell
47+
tableView.estimatedRowHeight = 44.0
48+
tableView.rowHeight = UITableViewAutomaticDimension
49+
50+
self.title = "Apple Products"
51+
52+
// Initialize the sections array
53+
// Here we have three sections: Mac, iPad, iPhone
54+
sections = [
55+
Section(name: "Mac", items: [
56+
Item(name: "MacBook", detail: "Apple's ultraportable laptop, trading portability for speed and connectivity."),
57+
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."),
58+
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."),
59+
Item(name: "iMac", detail: "iMac combines enhanced performance with our best ever Retina display for the ultimate desktop experience in two sizes."),
60+
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."),
61+
Item(name: "Mac mini", detail: "Mac mini is an affordable powerhouse that packs the entire Mac experience into a 7.7-inch-square frame."),
62+
Item(name: "OS X El Capitan", detail: "The twelfth major release of OS X (now named macOS)."),
63+
Item(name: "Accessories", detail: "")
64+
]),
65+
Section(name: "iPad", items: [
66+
Item(name: "iPad Pro", detail: "iPad Pro delivers epic power, in 12.9-inch and a new 10.5-inch size."),
67+
Item(name: "iPad Air 2", detail: "The second-generation iPad Air tablet computer designed, developed, and marketed by Apple Inc."),
68+
Item(name: "iPad mini 4", detail: "iPad mini 4 puts uncompromising performance and potential in your hand."),
69+
Item(name: "Accessories", detail: "")
70+
]),
71+
Section(name: "iPhone", items: [
72+
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."),
73+
Item(name: "iPhone 6", detail: "The iPhone 6 and iPhone 6 Plus are smartphones designed and marketed by Apple Inc."),
74+
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."),
75+
Item(name: "Accessories", detail: "")
76+
])
77+
]
78+
}
79+
80+
}
81+
82+
//
83+
// MARK: - View Controller DataSource and Delegate
84+
//
85+
extension CollapsibleTableSectionViewController {
86+
87+
override func numberOfSections(in tableView: UITableView) -> Int {
88+
return sections.count
89+
}
90+
91+
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
92+
return sections[section].collapsed ? 0 : sections[section].items.count
93+
}
94+
95+
// Cell
96+
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
97+
let cell: CollapsibleTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell") as? CollapsibleTableViewCell ??
98+
CollapsibleTableViewCell(style: .default, reuseIdentifier: "cell")
99+
100+
let item: Item = sections[(indexPath as NSIndexPath).section].items[(indexPath as NSIndexPath).row]
101+
102+
cell.nameLabel.text = item.name
103+
cell.detailLabel.text = item.detail
104+
105+
return cell
106+
}
107+
108+
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
109+
return sections[(indexPath as NSIndexPath).section].collapsed ? 0 : UITableViewAutomaticDimension
110+
}
111+
112+
// Header
113+
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
114+
let header = tableView.dequeueReusableHeaderFooterView(withIdentifier: "header") as? CollapsibleTableViewHeader ?? CollapsibleTableViewHeader(reuseIdentifier: "header")
115+
116+
header.titleLabel.text = sections[section].name
117+
header.arrowLabel.text = ">"
118+
header.setCollapsed(sections[section].collapsed)
119+
120+
header.section = section
121+
header.delegate = self
122+
123+
return header
124+
}
125+
126+
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
127+
return 44.0
128+
}
129+
130+
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
131+
return 1.0
132+
}
133+
134+
}
135+
136+
//
137+
// MARK: - Section Header Delegate
138+
//
139+
extension CollapsibleTableSectionViewController: CollapsibleTableViewHeaderDelegate {
140+
141+
func toggleSection(_ header: CollapsibleTableViewHeader, section: Int) {
142+
let collapsed = !sections[section].collapsed
143+
144+
// Toggle collapse
145+
sections[section].collapsed = collapsed
146+
header.setCollapsed(collapsed)
147+
148+
tableView.reloadSections(NSIndexSet(index: section) as IndexSet, with: .automatic)
149+
}
150+
151+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
//
2+
// CollapsibleTableViewCell.swift
3+
// CollapsibleTableSectionViewController
4+
//
5+
// Created by Yong Su on 7/20/17.
6+
// Copyright © 2017 jeantimex. 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: UITableViewCellStyle, 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+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
//
2+
// CollapsibleTableViewHeader.swift
3+
// CollapsibleTableSectionViewController
4+
//
5+
// Created by Yong Su on 7/20/17.
6+
// Copyright © 2017 jeantimex. 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+
//
27+
// Constraint the size of arrow label for auto layout
28+
//
29+
arrowLabel.widthAnchor.constraint(equalToConstant: 12).isActive = true
30+
31+
titleLabel.translatesAutoresizingMaskIntoConstraints = false
32+
arrowLabel.translatesAutoresizingMaskIntoConstraints = false
33+
34+
contentView.addSubview(titleLabel)
35+
contentView.addSubview(arrowLabel)
36+
37+
//
38+
// Call tapHeader when tapping on this header
39+
//
40+
addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(CollapsibleTableViewHeader.tapHeader(_:))))
41+
}
42+
43+
required init?(coder aDecoder: NSCoder) {
44+
fatalError("init(coder:) has not been implemented")
45+
}
46+
47+
override func layoutSubviews() {
48+
super.layoutSubviews()
49+
50+
contentView.backgroundColor = UIColor(hex: 0x2E3944)
51+
52+
titleLabel.textColor = UIColor.white
53+
arrowLabel.textColor = UIColor.white
54+
55+
//
56+
// Autolayout the lables
57+
//
58+
let views = [
59+
"titleLabel" : titleLabel,
60+
"arrowLabel" : arrowLabel,
61+
]
62+
63+
contentView.addConstraints(NSLayoutConstraint.constraints(
64+
withVisualFormat: "H:|-20-[titleLabel]-[arrowLabel]-20-|",
65+
options: [],
66+
metrics: nil,
67+
views: views
68+
))
69+
70+
contentView.addConstraints(NSLayoutConstraint.constraints(
71+
withVisualFormat: "V:|-[titleLabel]-|",
72+
options: [],
73+
metrics: nil,
74+
views: views
75+
))
76+
77+
contentView.addConstraints(NSLayoutConstraint.constraints(
78+
withVisualFormat: "V:|-[arrowLabel]-|",
79+
options: [],
80+
metrics: nil,
81+
views: views
82+
))
83+
}
84+
85+
//
86+
// Trigger toggle section when tapping on the header
87+
//
88+
func tapHeader(_ gestureRecognizer: UITapGestureRecognizer) {
89+
guard let cell = gestureRecognizer.view as? CollapsibleTableViewHeader else {
90+
return
91+
}
92+
93+
delegate?.toggleSection(self, section: cell.section)
94+
}
95+
96+
func setCollapsed(_ collapsed: Bool) {
97+
//
98+
// Animate the arrow rotation (see Extensions.swf)
99+
//
100+
arrowLabel.rotate(collapsed ? 0.0 : .pi / 2)
101+
}
102+
103+
}
104+

0 commit comments

Comments
 (0)