/CollapsibleTable

Collapsable table view sections with custom section header views.

Primary LanguageSwiftMIT LicenseMIT

CollapsibleTable

Carthage compatible Cocoapods compatible Platform Language Build Status

The collapsing mechanism can be installed in just a few minutes. But the creative design and styling of the UI, is at the mercy of the implementing developer.

demo

Take a look at the demo App by running the XCode scheme 'CollapsibleTableDemo'

Installation

CocoaPods:

Add the line pod "CollapsibleTable" to your Podfile

Carthage:

Add the line github "rob-nash/CollapsibleTable" to your Cartfile

Usage

First subclass UITableViewHeaderFooterView

import UIKit

class ArrowSectionHeaderView: UITableViewHeaderFooterView
{
    @IBOutlet fileprivate weak var mainTitleLabel: UILabel!
    
    @IBOutlet fileprivate weak var arrowImageView: UIImageView?
    
    fileprivate var isRotating = false
}

Then conform to the HeaderFooterViewCollapsible protocol

import CollapsibleTable

extension ArrowSectionHeaderView: HeaderFooterViewCollapsible
{
    func updateTitle(with value: String) {
        mainTitleLabel.text = value
    }
    
    func open(animated: Bool) {
        if animated == true && isRotating == false {
            isRotating = true
            UIView.animate(withDuration: 0.2, delay: 0, options: [.curveLinear,.allowUserInteraction], animations: {
                self.arrowImageView?.transform = .identity
            }, completion: { _ in
                self.isRotating = false
            })
        } else {
            layer.removeAllAnimations()
            arrowImageView?.transform = .identity
            isRotating = false
        }
    }
    
    func close(animated: Bool) {
        let angle = radians(degrees: 90)
        let transform = CGAffineTransform(rotationAngle: angle)
        if animated == true && isRotating == false {
            isRotating = true
            UIView.animate(withDuration: 0.2, delay: 0, options: [.curveLinear,.allowUserInteraction], animations: {
                self.arrowImageView?.transform = transform
            }, completion: { _ in
                self.isRotating = false
            })
        } else {
            layer.removeAllAnimations()
            arrowImageView?.transform = transform
            isRotating = false
        }
    }

    private func radians(degrees: CGFloat) -> CGFloat {
        return .pi * degrees / 180
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        guard let t = touches.first else { return }
        let point = t.location(in: self)
        respondToTouchAtPoint(point)
    }
}

Create a collection of instances that represent each section of your table view.

import Foundation

class Food
{
    /// The section title
    fileprivate let title: String
    
    /// The rows
    fileprivate var items: [Item]
    
    /// Are rows hidden
    fileprivate var isHidden = false
    
    init(title: String, items: [Item], isHidden: Bool) {
        self.title = title
        self.items = items
        self.isHidden = isHidden
    }
}

class Item
{
    let title: String
    var isSelected: Bool
    
    init(title: String, isSelected: Bool = false) {
        self.title = title
        self.isSelected = isSelected
    }
}

Each model instance representing your table view sections, must conform to CollapsibleTableSectionDatasource.

extension Food: CollapsibleTableSectionDatasource
{
    typealias TableRow = Item
    
    var rows: [Item] {
        get {
            return items
        }
        set(newValue) {
            items = newValue
        }
    }
    
    var state: RowVisibility {
        get {
            return isHidden ? .collapsed : .expanded
        }
        set(newValue) {
            switch newValue {
            case .collapsed:
                isHidden = true
            case .expanded:
                isHidden = false
            }
        }
    }
    
    var sectionTitle: String {
        return title
    }
    
    var sectionHeaderNibName: String {
        return "ArrowSectionHeaderView"
    }
    
    var sectionHeaderViewIdentifier: String {
        return "ArrowSectionHeaderViewID"
    }
    
    var sectionHeaderNibBundle: Bundle {
        return .main
    }
}

Create a UITableView subclass that conforms to TableCollapsible.

import UIKit
import CollapsibleTable

class FoodShoppingTableView: UITableView, TableCollapsible
{
    typealias TableSection = Food
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        separatorStyle = .none
        let nib = UINib(nibName: "ArrowSectionHeaderView", bundle: nil)
        register(nib, forHeaderFooterViewReuseIdentifier: "ArrowSectionHeaderViewID")
        observeSectionHeaders()
    }
    
    deinit {
        stopObservingSectionHeaders()
    }
}

Create a datasource by subclassing CollapsibleTableDatasource

import UIKit
import CollapsibleTable

class FoodShoppingTableViewDatasource: CollapsibleTableDatasource<Food>
{
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: CustomCell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
        let section: Food = sections[indexPath.section]
        let item: Item = section.rows[indexPath.row]
        cell.mainTitleLabel?.text = item.title
        return cell
    }
}

Then connect it all up

import UIKit
import CollapsibleTable

class ViewController: UIViewController
{
    @IBOutlet private weak var tableView: FoodShoppingTableView! {
        didSet {
            let appDelegate = UIApplication.shared.delegate as? AppDelegate
            tableView.dataSource = appDelegate?.foodShoppingTableViewDatasource
        }
    }
}

Donations.

If you like this and you want to buy me a drink, use bitcoin.

Bitcoin Image

Bitcoin Address: 15Gj4DBTzSujnJrfRZ6ivrR9kDnWXNPvNQ