Skip to content

Latest commit

 

History

History
128 lines (91 loc) · 4.1 KB

README.md

File metadata and controls

128 lines (91 loc) · 4.1 KB

AccordionTable

A wrapper for a diffable data source which facilitates collapsible table view sections. You may want to disable floating headers (see demo app).

Implementation

The following steps are for a typical table view implementation.

Prepare a model

Example
```swift
struct Food: Hashable {
    let id: UUID
    let title: String
    let items: [Item] // rows
}

struct Item: Hashable {
    let id: UUID
    let title: String
}
```

Prepare an instance of AccordionTable.

Show me
```swift
let tableDataSource = UITableViewDiffableDataSource<Food, Item>(
    tableView: tableView,
    cellProvider: cellProvider
)

let diffableTableManager = AccordionTable<Food, Item>(
    dataSource: tableDataSource,
    headerProvider: headerProvider
)
```

The AccordionTable type also has an enabledFeatures parameter.

Prepare a table view delegate.

Show me
```swift
class TypicalTableViewDelegate: NSObject, UITableViewDelegate {

    let tableManager: AccordionTable<Food, Item>
    
    init(_ tableManager: AccordionTable<Food, Item>) {
        self.tableManager = tableManager
    }
    
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        tableManager.viewForHeader(in: tableView, at: section)
    }
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        tableManager.selectRowIfNeeded(in: tableView, at: indexPath)
    }
    
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        guard let isSelected = tableManager.toggleSelectedStateForRow(at: indexPath) else {
            return
        }
        if !isSelected {
            tableView.deselectRow(at: indexPath, animated: true)
        }
    }
    
    func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
        tableManager.saveDeselectedStateForRow(at: indexPath)
    }
    
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 0 // your header height
    }
}
```

Map your data

let data = [Food: [Item]]()

Usage

To reload the data, call the following on your instance of AccordionTable.

func update(with data: [Food, [Item]], animated: true)

On initial load, pass animated: false.

To programmatically select a row.

diffableTableManager.saveSelectedStateForRow(at: indexPath)
tableView.selectRow(at: indexPath, animated: animated, scrollPosition: scrollPosition)

To programmatically deselect a row.

diffableTableManager.saveDeselectedStateForRow(at: indexPath)
tableView.deselectRow(at: indexPath, animated: animated)

Side Note

Consider avoiding any user interface state management in your model structures (and hashable implementation), such as isHighlighted, as this will be destroyed per snapshot (use a backing store instead, such as a Set or key/value collection - see demo app).

If your user interface is reloading more rows/sections than it should, it is likely the hashable implementation to blame. It seems each row must be unique within the entire dataset (not just within the section it belongs). Use GUID's on your dataset to achieve this. Alternatively, at the row level, make a reference to the section in which that row belongs (be careful of retain cycle when using classes) and use that in the hashable implementation of the row.

If you need to use reloadItems(_ identifiers: [ItemIdentifierType]) then you will need to use class's for your model.