vds_ios/VDS/Components/Table/Table.swift
Matt Bruce 78dd2d5df8 removed obj-c members
Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
2025-01-23 13:24:48 -06:00

199 lines
7.8 KiB
Swift

//
// Table.swift
// VDS
//
// Created by Nadigadda, Sumanth on 24/04/24.
//
import Foundation
import UIKit
import VDSCoreTokens
///Table is view composed of rows and columns, which takes any view into each cell and resizes based on the highest cell height.
@objc(VDSTable)
open class Table: View {
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
/// CollectionView to show the rows and columns
private lazy var matrixView = SelfSizingCollectionView(frame: .zero, collectionViewLayout: flowLayout).with {
$0.register(TableCellItem.self, forCellWithReuseIdentifier: TableCellItem.Identifier)
$0.dataSource = self
$0.delegate = self
$0.translatesAutoresizingMaskIntoConstraints = false
$0.allowsSelection = false
$0.showsVerticalScrollIndicator = false
$0.showsHorizontalScrollIndicator = false
$0.backgroundColor = .clear
}
/// Custom flow layout to manage the height of the cells
private lazy var flowLayout = MatrixFlowLayout().with {
$0.delegate = self
$0.scrollDirection = .horizontal
}
/// Array of ``TableItemModel`` by combining Header & Row items
private var tableData: [TableRowModel] {
return tableHeader + tableRows
}
//--------------------------------------------------
// MARK: - Enums
//--------------------------------------------------
/// Enums used to define the padding for the cell edge spacing.
public enum Padding: String, CaseIterable {
case standard, compact
func horizontalValue() -> CGFloat {
switch self {
case .standard, .compact:
return UIDevice.isIPad ? VDSLayout.space4X : VDSLayout.space3X
}
}
func verticalValue() -> CGFloat {
switch self {
case .standard:
return UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X
case .compact:
return UIDevice.isIPad ? VDSLayout.space4X : VDSLayout.space3X
}
}
}
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
/// Parameter to set striped status for the table
open var striped: Bool = false { didSet { setNeedsUpdate() } }
/// Parameter to set the padding for the cell
open var padding: Padding = .standard { didSet { setNeedsUpdate() } }
/// Parameter to show the table header row
open var tableHeader: [TableRowModel] = [] { didSet { setNeedsUpdate() } }
/// Parameter to show the all table rows
open var tableRows: [TableRowModel] = [] { didSet { setNeedsUpdate() } }
open var fillContainer: Bool = true { didSet { setNeedsUpdate() } }
open var columnWidths: [CGFloat]? { didSet { setNeedsUpdate() } }
//--------------------------------------------------
// MARK: - Overrides
//--------------------------------------------------
///Called upon initializing the table view
open override func setup() {
super.setup()
addSubview(matrixView)
matrixView.pinToSuperView()
}
/// Will update the table view, when called becasue of any changes in component parameters
open override func updateView() {
super.updateView()
if fillContainer == true || (fillContainer == false && columnWidths == nil) {
columnWidths = calculateColumnWidths()
}
flowLayout.layoutPadding = padding
matrixView.reloadData()
matrixView.collectionViewLayout.invalidateLayout()
}
open override func setDefaults() {
super.setDefaults()
striped = false
padding = .standard
tableHeader = []
tableRows = []
fillContainer = true
columnWidths = nil
}
func calculateColumnWidths() -> [CGFloat] {
guard let noOfColumns = tableData.first?.columnsCount else { return [] }
let itemWidth = floor(matrixView.safeAreaLayoutGuide.layoutFrame.width / CGFloat(noOfColumns))
return Array(repeating: itemWidth, count: noOfColumns)
}
}
extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableCollectionViewLayoutDataDelegate {
//--------------------------------------------------
// MARK: - UICollectionViewDelegate & UICollectionViewDataSource
//--------------------------------------------------
public func numberOfSections(in collectionView: UICollectionView) -> Int {
return tableData.count
}
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return tableData[section].columnsCount
}
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TableCellItem.Identifier, for: indexPath) as? TableCellItem else { return UICollectionViewCell() }
let currentItem = tableData[indexPath.section].columns[indexPath.row]
let shouldStrip = striped ? (indexPath.section % 2 != 0) : false
let isHeader = tableData[indexPath.section].isHeader
var edgePadding = UIEdgeInsets(top: padding.verticalValue(), left: 0, bottom: padding.verticalValue(), right: padding.horizontalValue())
edgePadding.left = (indexPath.row == 0 && !striped) ? VDSLayout.space1X : padding.horizontalValue()
cell.updateCell(content: currentItem, surface: surface, striped: shouldStrip, padding: edgePadding, isHeader: isHeader)
setAccessibilityForCell(cell: cell, content: currentItem, path: indexPath)
return cell
}
//--------------------------------------------------
// MARK: - TableCollectionViewLayoutDataDelegate
//--------------------------------------------------
func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableItemModel {
return tableData[indexPath.section].columns[indexPath.row]
}
func collectionView(_ collectionView: UICollectionView, widthForItemAt indexPath: IndexPath) -> CGFloat {
return columnWidths?[indexPath.row] ?? 0.0
}
//--------------------------------------------------
// MARK: - Accessibility
//--------------------------------------------------
/// To set the accessibility label for the each cell based on the criteria. Table name along with total no of column & row information should be passed in the first cell's accessibility label.
private func setAccessibilityForCell(cell: TableCellItem, content: TableItemModel, path: IndexPath) {
var accLabel = content.component?.accessibilityLabel ?? "Empty"
///Set the type of header label
if path.section == 0 {
accLabel.append(", Column Header")
} else if path.row == 0 {
///As per design team, inspite of column 0 may not look like a header, it should be read as header.
accLabel.append(", Row Header")
}
///Set the Row/Column number for each cell
if path.row == 0 {
accLabel.append(", Row \(path.section + 1), Column \(path.row + 1)")
} else {
accLabel.append(", Column \(path.row + 1)")
}
///Set the Row header accessibilityLabel at the end of the non-header cells accessibilityLabel
if path.section != 0,
path.row != 0,
let columnHeaderAccLabel = tableHeader.first?.columns[path.row].component?.accessibilityLabel {
accLabel.append(", \(columnHeaderAccLabel)")
}
cell.accessibilityLabel = accLabel
}
}