Skip to content

Instantly share code, notes, and snippets.

@Eridana
Last active November 8, 2018 13:55
Show Gist options
  • Select an option

  • Save Eridana/5fe4511c5d1c258f2af63fe8b9b13341 to your computer and use it in GitHub Desktop.

Select an option

Save Eridana/5fe4511c5d1c258f2af63fe8b9b13341 to your computer and use it in GitHub Desktop.

Revisions

  1. Eridana revised this gist Nov 8, 2018. 1 changed file with 8 additions and 7 deletions.
    15 changes: 8 additions & 7 deletions 1) ExpandableTableView.swift
    Original file line number Diff line number Diff line change
    @@ -30,13 +30,14 @@ protocol ExpandableCell {
    }

    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 expand(_ tableView: ExpandableTableView, at indexPath: IndexPath, expand: Bool)
    func isCellExpanded(_ tableView: ExpandableTableView, at indexPath: IndexPath) -> Bool
    func expandableTableView(_ tableView: ExpandableTableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
    func expandableTableView(_ tableView: ExpandableTableView, heightForRowAt indexPath: IndexPath) -> CGFloat
    func expandableTableView(_ tableView: ExpandableTableView, didSelectRowAt indexPath: IndexPath)
    }

    protocol ExpandableTableViewDelegate {
    func scrollViewDidScroll(_ scrollView: UIScrollView)
    }

  2. Eridana revised this gist Nov 8, 2018. 2 changed files with 86 additions and 57 deletions.
    49 changes: 42 additions & 7 deletions 1) ExpandableTableView.swift
    Original file line number Diff line number Diff line change
    @@ -43,8 +43,30 @@ protocol ExpandableTableViewDataSource {
    class ExpandableTableView: UITableView, UITableViewDataSource, UITableViewDelegate {

    public var customDataSource: ExpandableTableViewDataSource?
    public var customDelegate: ExpandableTableViewDelegate?

    public var expandableData = Dictionary<Int, [String?]>()

    internal var cellsData = [ExpandableObject]()

    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
    }
    }

    override init(frame: CGRect, style: UITableViewStyle) {
    super.init(frame: frame, style: style)
    self.setup()
    @@ -62,11 +84,24 @@ class ExpandableTableView: UITableView, UITableViewDataSource, UITableViewDelega
    self.backgroundColor = .clear
    }

    public func setCellsData(_ data: [ExpandableObject]) {
    self.cellsData = data
    }

    public func registerCell(with name: String) {
    self.register(UINib(nibName: String(describing: name), bundle: nil), forCellReuseIdentifier: String(describing: name))
    }

    public func object(at indexPath: IndexPath) -> ExpandableObject {
    return self.mappedCellsData[indexPath.row]
    }

    public func isLast(_ indexPath: IndexPath) -> Bool {
    return indexPath.row == self.mappedCellsData.count - 1
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    guard let ds = self.customDataSource else {
    return 0
    }
    return ds.currentNumberOfRowsInTable()
    return self.mappedCellsData.count
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    @@ -82,7 +117,7 @@ class ExpandableTableView: UITableView, UITableViewDataSource, UITableViewDelega
    return
    }
    if cell.canExpandCell() {
    ds.expand(at: indexPath, expand: !cell.isCellExpanded())
    ds.expand(self, at: indexPath, expand: !cell.isCellExpanded())
    self.reloadData()
    } else {
    ds.expandableTableView(self, didSelectRowAt: indexPath)
    @@ -96,8 +131,8 @@ class ExpandableTableView: UITableView, UITableViewDataSource, UITableViewDelega
    }
    return ds.expandableTableView(self, heightForRowAt:indexPath)
    }

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
    self.customDataSource?.scrollViewDidScroll(scrollView)
    self.customDelegate?.scrollViewDidScroll(scrollView)
    }
    }
    94 changes: 44 additions & 50 deletions 4) Initialisation in View Contorller
    Original file line number Diff line number Diff line change
    @@ -1,103 +1,97 @@
    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
    self.tableView.customDelegate = self
    self.tableView.estimatedRowHeight = 0
    self.tableView.registerCell(with: String(describing: ExpandableTVC.self)) // base cell
    }

    func setupCellsData() {
    var cellsData = [ExpandableObject]()

    // 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)
    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)
    cellsData.append(obj2)

    // obj1 is expandable cell with 2 childs
    let obj3 = ExpandableObject(cellIdentifier: String(describing: ExpandableTVC.self))
    obj3.title = "title3"
    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)
    cellsData.append(obj3)

    self.tableView.reloadData()
    self.tableView.setCellsData(cellsData)
    }

    // MARK: - Expandable Table

    // for cell which are not expandable
    func expandableTableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    func expandableTableView(_ tableView: ExpandableTableView, didSelectRowAt indexPath: IndexPath) {

    }

    // for cell which are expandable
    func expand(at indexPath: IndexPath, expand: Bool) {
    let obj = self.mappedCellsData[indexPath.row]
    func expand(_ tableView: ExpandableTableView, at indexPath: IndexPath, expand: Bool) {
    let obj = tableView.object(at: indexPath)
    obj.isExpanded = expand
    self.tableView.reloadData()
    }

    func isCellExpanded(at indexPath: IndexPath) -> Bool {
    let obj = self.mappedCellsData[indexPath.row]
    return obj.isExpanded
    }
    let newCellIndexPath = IndexPath(row: indexPath.row + 1, section: 0)

    func numberOfExpandableRows(at indexPath: IndexPath) -> Int {
    if let cell = self.tableView.cellForRow(at: indexPath) as? ExpandableCell {
    return cell.numberOfExpandableElements()
    if expand {
    self.tableView.insertRows(at: [newCellIndexPath], with: .none)
    } else {
    self.tableView.deleteRows(at: [newCellIndexPath], with: .none)
    }
    return 0

    self.tableView.beginUpdates()
    self.tableView.endUpdates()

    // or use this line instead of lines above
    //self.tableView.reloadData()
    self.tableView.scrollToRow(at: indexPath, at: .top, animated: false)
    }

    func currentNumberOfRowsInTable() -> Int {
    return self.mappedCellsData.count
    func isCellExpanded(_ tableView: ExpandableTableView, at indexPath: IndexPath) -> Bool {
    let obj = tableView.object(at: indexPath)
    return obj.isExpanded
    }

    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) {

    func expandableTableView(_ tableView: ExpandableTableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let obj = tableView.object(at: indexPath)
    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 {
    if cellId == String(describing: SubDetailsTableCell.self) {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as? SubDetailsTableCell else {
    return UITableViewCell()
    }
    cell.setup()
    cell.setup(...)
    return cell
    }
    return UITableViewCell()
    }

    func expandableTableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    // if you need data from object: let obj = tableView.object(at: indexPath)
    return 50.0
    }


    // from delegate

    func scrollViewDidScroll(_ scrollView: UIScrollView) {
    // ...
    }
  3. Eridana revised this gist Oct 10, 2018. 4 changed files with 0 additions and 0 deletions.
    File renamed without changes.
    File renamed without changes.
    File renamed without changes.
  4. Eridana revised this gist Oct 10, 2018. 1 changed file with 48 additions and 48 deletions.
    96 changes: 48 additions & 48 deletions Initialisation in View Contorller
    Original file line number Diff line number Diff line change
    @@ -37,7 +37,7 @@ func setupCellsData() {

    // obj1 is expandable cell with 2 childs
    let obj3 = ExpandableObject(cellIdentifier: String(describing: ExpandableTVC.self))
    obj3.title = "title1"
    obj3.title = "title3"
    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)
    @@ -46,58 +46,58 @@ func setupCellsData() {
    }

    // 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()

    // 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()
    }

    func isCellExpanded(at indexPath: IndexPath) -> Bool {
    let obj = self.mappedCellsData[indexPath.row]
    return obj.isExpanded
    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()
    }

    func numberOfExpandableRows(at indexPath: IndexPath) -> Int {
    if let cell = self.tableView.cellForRow(at: indexPath) as? ExpandableCell {
    return cell.numberOfExpandableElements()
    if cellId == String(describing: ExpandableTVC.self) {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as? ExpandableTVC else {
    return UITableViewCell()
    }
    return 0
    }

    func currentNumberOfRowsInTable() -> Int {
    return self.mappedCellsData.count
    cell.setup(for: obj)
    return cell
    }

    func expandableTableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let obj = self.mappedCellsData[indexPath.row]
    guard let cellId = obj.cellIdentifier else {
    if cellId == String(describing: SubIngredientsTableCell.self) {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as? AnyTableCell 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
    cell.setup()
    return cell
    }
    return UITableViewCell()
    }

    func expandableTableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    return 50.0
    }

  5. Eridana created this gist Oct 10, 2018.
    30 changes: 30 additions & 0 deletions AnyTableCell.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,30 @@
    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
    }
    }
    41 changes: 41 additions & 0 deletions ExpandableTVC.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    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
    }
    }
    103 changes: 103 additions & 0 deletions ExpandableTableView.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,103 @@
    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)
    }
    }
    103 changes: 103 additions & 0 deletions Initialisation in View Contorller
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,103 @@
    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
    }