Expandable is very useful in an iOS application and To create it we follow the MVVM structure. First, create three classes ExpandableModal, ExpandableViewModal, ExpandableController .ExpandableController is of ViewController’s subclass, ExpandableModal, and ExpandableViewModal.
To start creating first you know is your table view’s states is collapsible or not. and there you know the current state of your section i.e. isCollapsed or not.
After that, we need to create the custom header view that will contain title and image(will indicate us that the view state).Create a Xib of subclass UITableViewHeaderFooterView
Then create ExpandableModal that will contain the header data and subheader data (Your child nodes)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
import SwiftyJSON class ExpandableModal { var categories = [Category]() init?(data: JSON) { if let categories = data.array { self.categories = categories.map { Category(json: $0 ) } } } } class Category { var name: String? var CategoryId: String? var isCollapsed = true var SubcategoryArray = [SubCategoryData]() init(json: JSON) { self.name = json["name"].stringValue self.CategoryId = json["category_id"].stringValue if let categories = json["children"].array { self.SubcategoryArray = categories.map { SubCategoryData(json: $0 ) } } } } class SubCategoryData { var name: String? var CategoryId: String? init(json: JSON) { self.name = json["name"].stringValue self.CategoryId = json["category_id"].stringValue } } |
After that set your all data in your header
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
protocol HeaderViewDelegate: class { func toggleSection(header: HeaderViewCell, section: Int) } class HeaderViewCell: UITableViewHeaderFooterView { var section: Int = 0 weak var delegate: HeaderViewDelegate? @IBOutlet weak var titleLabel: UILabel? @IBOutlet weak var arrowLabel: UIImageView! @IBOutlet weak var lineView: UIView! static var nib:UINib { return UINib(nibName: identifier, bundle: nil) } static var identifier: String { return String(describing: self) } var item: Category? { didSet { guard let item = item else { return } titleLabel?.text = item.name if item.SubcategoryArray.count > 0 { arrowLabel.image = #imageLiteral(resourceName: "icon-drop-down") setCollapsed(collapsed: item.isCollapsed) } else{ arrowLabel.image = #imageLiteral(resourceName: "icon-angle-right") setCollapsed(collapsed: true) } } } override func awakeFromNib() { super.awakeFromNib() addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(didTapHeader))) lineView.backgroundColor = GlobalData.Credentials.defaultTextColor } @objc private func didTapHeader() { delegate?.toggleSection(header: self, section: section) } func setCollapsed(collapsed: Bool) { arrowLabel?.rotate(collapsed ? 0.0 : .pi) } } // For animate your section extension UIView { func rotate(_ toValue: CGFloat, duration: CFTimeInterval = 0.2) { let animation = CABasicAnimation(keyPath: "transform.rotation") animation.toValue = toValue animation.duration = duration animation.isRemovedOnCompletion = false animation.fillMode = kCAFillModeForwards self.layer.add(animation, forKey: nil) } } |
Create a reload function in your view controller which is directly bound with your view modal
1 2 3 4 5 6 7 8 9 10 11 |
fileprivate let viewModel = ExpandableViewModal() @IBOutlet weak var categoryTableView: UITableView! override func viewDidLoad() { super.viewDidLoad() fileprivate let viewModel = CategoryMenuViewModel() override func viewDidLoad() { super.viewDidLoad() } categoryTableView?.dataSource = viewModel categoryTableView?.delegate = viewModel } |
Create your ExpandableViewModal and present your all data:-
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
import SwiftyJSON class ExpandableViewModal: NSObject { var reloadSections: ((_ section: Int) -> Void)? var items = [ExpandableViewModalItem]() func getValue(categoryData : JSON) { items.removeAll() guard let profile = CategoryMenuModel(data : categoryData) else { return } if !profile.categories.isEmpty { let categoriesItem = ProfileViewModecategoriesItem(categories: profile.categories) items.append(categoriesItem) } } } extension ExpandableViewModal: UITableViewDataSource { func numberOfSections(in tableView: UITableView) -> Int { if items.count > 0 , let item = items[0] as? ProfileViewModecategoriesItem { return item.rowCount } return 0 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if items.count > 0 , let item = items[0] as? ProfileViewModecategoriesItem { if item.categories[section].isCollapsed { return 0 } else { return item.categories[section].SubcategoryArray.count } } else { return 0 } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell(style: .default, reuseIdentifier: "cell") if items.count > 0 , let item = items[0] as? ProfileViewModecategoriesItem { cell.textLabel?.text = item.categories[indexPath.section].SubcategoryArray[indexPath.row].name cell.textLabel?.textColor = GlobalData.Credentials.defaultTextColor cell.accessoryType = .disclosureIndicator } return cell } } class ProfileViewModecategoriesItem: ExpandableViewModalItem { var rowCount: Int { return categories.count } var categories: [Category] init(categories: [Category]) { self.categories = categories } } protocol ExpandableViewModalItem { var rowCount: Int { get } } // For reload your section extension ExpandableViewModal: HeaderViewDelegate { func toggleSection(header: CategoryHeadeViewCell, section: Int) { if let item = items[0] as? ProfileViewModecategoriesItem { if item.categories[section].SubcategoryArray.count > 0 { let collapsed = !item.categories[section].isCollapsed item.categories[section].isCollapsed = collapsed header.setCollapsed(collapsed: collapsed) reloadSections?(section) } } } } |
So when you get all data just reload your table view categoryTableView.reloadData()
So you have successfully added an Expandable TableView in your app.
Thanks for go through this blog. Stay cool and stay updated.