Last active
November 8, 2018 13:55
-
-
Save Eridana/5fe4511c5d1c258f2af63fe8b9b13341 to your computer and use it in GitHub Desktop.
Expandable UITableView
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class AnyTableCell: UITableViewCell, ExpandableCell { | |
| override func awakeFromNib() { | |
| super.awakeFromNib() | |
| } | |
| override func setSelected(_ selected: Bool, animated: Bool) { | |
| super.setSelected(selected, animated: animated) | |
| } | |
| func setup() { | |
| } | |
| func canExpandCell() -> Bool { | |
| return false | |
| } | |
| func isCellExpanded() -> Bool { | |
| return false | |
| } | |
| func setCellIsExpanded(expanded: Bool) { | |
| } | |
| func numberOfExpandableElements() -> Int { | |
| return 0 // or if you setup cell with object: return self.cellObject?.subItems.count ?? 0 | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import UIKit | |
| class ExpandableObject { | |
| public var subItems = [ExpandableObject]() | |
| public var title: String? | |
| public var isExpanded: Bool = false | |
| public var isChild: Bool = false | |
| public var cellIdentifier: String? | |
| public var canExpand: Bool { | |
| return self.subItems.count > 0 | |
| } | |
| init(_ title: String?, isChild: Bool = false) { | |
| self.title = title | |
| self.isChild = isChild | |
| } | |
| init(cellIdentifier: String?, isChild: Bool = false) { | |
| self.cellIdentifier = cellIdentifier | |
| self.isChild = isChild | |
| } | |
| } | |
| protocol ExpandableCell { | |
| func canExpandCell() -> Bool | |
| func isCellExpanded() -> Bool | |
| func setCellIsExpanded(expanded: Bool) | |
| func numberOfExpandableElements() -> Int | |
| } | |
| protocol ExpandableTableViewDataSource { | |
| func expand(at indexPath: IndexPath, expand: Bool) | |
| func isCellExpanded(at indexPath: IndexPath) -> Bool | |
| func numberOfExpandableRows(at indexPath: IndexPath) -> Int | |
| func currentNumberOfRowsInTable() -> Int | |
| func expandableTableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell | |
| func expandableTableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat | |
| func expandableTableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) | |
| func scrollViewDidScroll(_ scrollView: UIScrollView) | |
| } | |
| class ExpandableTableView: UITableView, UITableViewDataSource, UITableViewDelegate { | |
| public var customDataSource: ExpandableTableViewDataSource? | |
| public var expandableData = Dictionary<Int, [String?]>() | |
| override init(frame: CGRect, style: UITableViewStyle) { | |
| super.init(frame: frame, style: style) | |
| self.setup() | |
| } | |
| required init?(coder aDecoder: NSCoder) { | |
| super.init(coder: aDecoder) | |
| self.setup() | |
| } | |
| private func setup() { | |
| self.dataSource = self | |
| self.delegate = self | |
| self.estimatedRowHeight = 44.0 | |
| self.backgroundColor = .clear | |
| } | |
| func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { | |
| guard let ds = self.customDataSource else { | |
| return 0 | |
| } | |
| return ds.currentNumberOfRowsInTable() | |
| } | |
| func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | |
| guard let ds = self.customDataSource else { | |
| return UITableViewCell() | |
| } | |
| return ds.expandableTableView(self, cellForRowAt:indexPath) | |
| } | |
| func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { | |
| if let cell = tableView.cellForRow(at: indexPath) as? ExpandableCell { | |
| guard let ds = self.customDataSource else { | |
| return | |
| } | |
| if cell.canExpandCell() { | |
| ds.expand(at: indexPath, expand: !cell.isCellExpanded()) | |
| self.reloadData() | |
| } else { | |
| ds.expandableTableView(self, didSelectRowAt: indexPath) | |
| } | |
| } | |
| } | |
| func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { | |
| guard let ds = self.customDataSource else { | |
| return self.estimatedRowHeight | |
| } | |
| return ds.expandableTableView(self, heightForRowAt:indexPath) | |
| } | |
| func scrollViewDidScroll(_ scrollView: UIScrollView) { | |
| self.customDataSource?.scrollViewDidScroll(scrollView) | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import UIKit | |
| class ExpandableTVC: UITableViewCell, ExpandableCell { | |
| @IBOutlet weak var cellTitleLabel: UILabel! | |
| @IBOutlet weak var expandableImage: UIImageView! | |
| private var cellObject: ExpandableObject? | |
| override func awakeFromNib() { | |
| super.awakeFromNib() | |
| } | |
| override func setSelected(_ selected: Bool, animated: Bool) { | |
| super.setSelected(selected, animated: animated) | |
| } | |
| func setup(for object: ExpandableObject) { | |
| self.cellObject = object | |
| self.cellTitleLabel.text = self.cellObject?.title | |
| UIView.animate(withDuration: 0.15) { | |
| self.expandableImage.isHighlighted = (self.cellObject?.isExpanded ?? false) | |
| } | |
| } | |
| func canExpandCell() -> Bool { | |
| return self.cellObject?.canExpand ?? false | |
| } | |
| func isCellExpanded() -> Bool { | |
| return self.cellObject?.isExpanded ?? false | |
| } | |
| func setCellIsExpanded(expanded: Bool) { | |
| } | |
| func numberOfExpandableElements() -> Int { | |
| return self.cellObject?.subItems.count ?? 0 | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| internal var cellsData = [ExpandableObject]() | |
| // cellsData mapped to one dimension array | |
| internal var mappedCellsData: [ExpandableObject] { | |
| get { | |
| var newArray = [ExpandableObject]() | |
| for obj in self.cellsData { | |
| newArray.append(obj) | |
| if obj.isExpanded { | |
| for subItem in obj.subItems { | |
| newArray.append(subItem) | |
| if subItem.isExpanded { | |
| newArray.append(contentsOf: subItem.subItems) | |
| } | |
| } | |
| } | |
| } | |
| return newArray | |
| } | |
| } | |
| func setupTableView() { | |
| self.tableView.customDataSource = self | |
| } | |
| func setupCellsData() { | |
| // obj1 is expandable cell with 1 not expandable child cell | |
| let obj1 = ExpandableObject(cellIdentifier: String(describing: ExpandableTVC.self)) | |
| obj1.title = "title1" | |
| obj1.subItems.append(ExpandableObject(cellIdentifier: String(describing: AnyTVC.self), isChild: true)) | |
| self.cellsData.append(obj1) | |
| // obj1 is not expandable cell (no childs) | |
| let obj2 = ExpandableObject(cellIdentifier: String(describing: ExpandableTVC.self)) | |
| obj2.title = "title2" | |
| self.cellsData.append(obj2) | |
| // obj1 is expandable cell with 2 childs | |
| let obj3 = ExpandableObject(cellIdentifier: String(describing: ExpandableTVC.self)) | |
| obj3.title = "title1" | |
| obj3.subItems.append(ExpandableObject(cellIdentifier: String(describing: AnyTVC.self), isChild: true)) | |
| obj3.subItems.append(ExpandableObject(cellIdentifier: String(describing: AnyTVC.self), isChild: true)) | |
| self.cellsData.append(obj3) | |
| self.tableView.reloadData() | |
| } | |
| // MARK: - Expandable Table | |
| // for cell which are not expandable | |
| func expandableTableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { | |
| } | |
| // for cell which are expandable | |
| func expand(at indexPath: IndexPath, expand: Bool) { | |
| let obj = self.mappedCellsData[indexPath.row] | |
| obj.isExpanded = expand | |
| self.tableView.reloadData() | |
| } | |
| func isCellExpanded(at indexPath: IndexPath) -> Bool { | |
| let obj = self.mappedCellsData[indexPath.row] | |
| return obj.isExpanded | |
| } | |
| func numberOfExpandableRows(at indexPath: IndexPath) -> Int { | |
| if let cell = self.tableView.cellForRow(at: indexPath) as? ExpandableCell { | |
| return cell.numberOfExpandableElements() | |
| } | |
| return 0 | |
| } | |
| func currentNumberOfRowsInTable() -> Int { | |
| return self.mappedCellsData.count | |
| } | |
| func expandableTableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { | |
| let obj = self.mappedCellsData[indexPath.row] | |
| guard let cellId = obj.cellIdentifier else { | |
| return UITableViewCell() | |
| } | |
| if cellId == String(describing: ExpandableTVC.self) { | |
| guard let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as? ExpandableTVC else { | |
| return UITableViewCell() | |
| } | |
| cell.setup(for: obj) | |
| return cell | |
| } | |
| if cellId == String(describing: SubIngredientsTableCell.self) { | |
| guard let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as? AnyTableCell else { | |
| return UITableViewCell() | |
| } | |
| cell.setup() | |
| return cell | |
| } | |
| return UITableViewCell() | |
| } | |
| func expandableTableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { | |
| return 50.0 | |
| } | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment