Adding comments to the Table components implementation.

This commit is contained in:
Sumanth Nadigadda 2024-05-14 19:09:03 +05:30
parent 0d421190a2
commit d4957e5aec
4 changed files with 93 additions and 9 deletions

View File

@ -9,13 +9,15 @@ import Foundation
import UIKit import UIKit
import VDSTokens import VDSTokens
///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) @objc(VDSTable)
open class Table: View { open class Table: View {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Private Properties // MARK: - Private Properties
//-------------------------------------------------- //--------------------------------------------------
/// CollectionView to show the rows and columns
private lazy var matrixView = SelfSizingCollectionView(frame: .zero, collectionViewLayout: flowLayout).with { private lazy var matrixView = SelfSizingCollectionView(frame: .zero, collectionViewLayout: flowLayout).with {
$0.register(TableCellItem.self, forCellWithReuseIdentifier: TableCellItem.Identifier) $0.register(TableCellItem.self, forCellWithReuseIdentifier: TableCellItem.Identifier)
$0.dataSource = self $0.dataSource = self
@ -29,10 +31,12 @@ open class Table: View {
$0.backgroundColor = .clear $0.backgroundColor = .clear
} }
/// Custom flow layout to manage the height of the cells
private lazy var flowLayout = MatrixFlowLayout().with { private lazy var flowLayout = MatrixFlowLayout().with {
$0.delegate = self $0.delegate = self
} }
/// Array of ``TableItemModel`` by combining Header & Row items
private var tableData: [[TableItemModel]] { private var tableData: [[TableItemModel]] {
return tableHeader + tableRows return tableHeader + tableRows
} }
@ -41,6 +45,7 @@ open class Table: View {
// MARK: - Enums // MARK: - Enums
//-------------------------------------------------- //--------------------------------------------------
/// Enums used to define the padding for the cell edge spacing.
public enum Padding: String, CaseIterable { public enum Padding: String, CaseIterable {
case standard, compact case standard, compact
@ -67,34 +72,55 @@ open class Table: View {
// MARK: - Public Properties // MARK: - Public Properties
//-------------------------------------------------- //--------------------------------------------------
/// Parameter to set striped status for the table
open var striped: Bool = false { didSet { setNeedsUpdate() } } open var striped: Bool = false { didSet { setNeedsUpdate() } }
/// Parameter to set the padding for the cell
open var padding: Padding = .standard { didSet { setNeedsUpdate() } } open var padding: Padding = .standard { didSet { setNeedsUpdate() } }
/// Parameter to show the table header row
open var tableHeader: [[TableItemModel]] = [] { didSet { setNeedsUpdate() } } open var tableHeader: [[TableItemModel]] = [] { didSet { setNeedsUpdate() } }
/// Parameter to show the all table rows
open var tableRows: [[TableItemModel]] = [] { didSet { setNeedsUpdate() } } open var tableRows: [[TableItemModel]] = [] { didSet { setNeedsUpdate() } }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Overrides // MARK: - Overrides
//-------------------------------------------------- //--------------------------------------------------
///Called upon initializing the table view
open override func initialSetup() { open override func initialSetup() {
super.initialSetup() super.initialSetup()
addSubview(matrixView) addSubview(matrixView)
matrixView.pinToSuperView() matrixView.pinToSuperView()
} }
/// Will update the table view, when called becasue of any changes in component parameters
open override func updateView() { open override func updateView() {
super.updateView() super.updateView()
flowLayout.layoutPadding = padding flowLayout.layoutPadding = padding
matrixView.reloadData() matrixView.reloadData()
matrixView.collectionViewLayout.invalidateLayout() matrixView.collectionViewLayout.invalidateLayout()
} }
/// Resets to default settings.
open override func reset() {
super.reset()
striped = false
padding = .standard
tableHeader = []
tableRows = []
setNeedsUpdate()
}
} }
extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableCollectionViewLayoutDataDelegate { extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableCollectionViewLayoutDataDelegate {
//--------------------------------------------------
// MARK: - UICollectionViewDelegate & UICollectionViewDataSource
//--------------------------------------------------
public func numberOfSections(in collectionView: UICollectionView) -> Int { public func numberOfSections(in collectionView: UICollectionView) -> Int {
return tableData.count return tableData.count
} }
@ -110,6 +136,10 @@ extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableColl
return cell return cell
} }
//--------------------------------------------------
// MARK: - TableCollectionViewLayoutDataDelegate
//--------------------------------------------------
func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableItemModel { func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableItemModel {
return tableData[indexPath.section][indexPath.row] return tableData[indexPath.section][indexPath.row]
} }

View File

@ -11,17 +11,31 @@ import VDSTokens
final class TableCellItem: UICollectionViewCell { final class TableCellItem: UICollectionViewCell {
/// Identifier for TableCellItem
static let Identifier: String = String(describing: TableCellItem.self) static let Identifier: String = String(describing: TableCellItem.self)
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
/// Main view which holds the content of the cell
private let containerView = View() private let containerView = View()
/// Line seperator for cell
private let separator: Line = Line() private let separator: Line = Line()
/// Color configuration for default background color
private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark)
/// Color configuration for striped background color
private let stripedColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark) private let stripedColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark)
/// Padding parameter to maintain the edge spacing of the containerView
private var padding: Table.Padding = .standard private var padding: Table.Padding = .standard
//--------------------------------------------------
// MARK: - Initializers
//--------------------------------------------------
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
setupCell() setupCell()
@ -39,7 +53,12 @@ final class TableCellItem: UICollectionViewCell {
containerView.pinToSuperView() containerView.pinToSuperView()
} }
func updateCell(content: TableItemModel, surface: Surface, striped: Bool = false, padding: Table.Padding = .standard) { //--------------------------------------------------
// MARK: - Public Methods
//--------------------------------------------------
/// Updates the cell content with ``TableItemModel`` and styling/padding attributes from other parameters
public func updateCell(content: TableItemModel, surface: Surface, striped: Bool = false, padding: Table.Padding = .standard) {
containerView.subviews.forEach({ $0.removeFromSuperview() }) containerView.subviews.forEach({ $0.removeFromSuperview() })
self.padding = padding self.padding = padding

View File

@ -9,16 +9,20 @@ import Foundation
import UIKit import UIKit
import VDSTokens import VDSTokens
/// Fetches the estimated height of component for said width
public protocol ComponentHeight { public protocol ComponentHeight {
func estimatedHeight(for width: CGFloat) -> CGFloat? func estimatedHeight(for width: CGFloat) -> CGFloat?
} }
/// Model that represent the content of each cell of Table component
public struct TableItemModel { public struct TableItemModel {
/// Default cell height
public var defaultHeight: CGFloat { return 50.0 } public var defaultHeight: CGFloat { return 50.0 }
public var bottomLine: Line.Style? public var bottomLine: Line.Style?
/// Component to be show in the Table cell
public var component: UIView & ComponentHeight public var component: UIView & ComponentHeight
public init(bottomLine: Line.Style? = nil, component: UIView & ComponentHeight) { public init(bottomLine: Line.Style? = nil, component: UIView & ComponentHeight) {
@ -27,6 +31,7 @@ public struct TableItemModel {
} }
} }
/// Confirming protocol ``ComponentHeight`` to determine the height, based on the width
extension Label: ComponentHeight { extension Label: ComponentHeight {
public func estimatedHeight(for width: CGFloat) -> CGFloat? { public func estimatedHeight(for width: CGFloat) -> CGFloat? {

View File

@ -14,14 +14,33 @@ protocol TableCollectionViewLayoutDataDelegate: AnyObject {
class MatrixFlowLayout : UICollectionViewLayout { class MatrixFlowLayout : UICollectionViewLayout {
//--------------------------------------------------
// MARK: - Private Properties
//--------------------------------------------------
///Spacing between the pagination cells
private let defaultLeadingPadding: CGFloat = VDSLayout.space1X
/// Parameter to store the layout attributes of cell, while calculate the size & position of the cell
private var itemCache: [UICollectionViewLayoutAttributes] = []
/// Parameter to store the total height of the collectionView
private var layoutHeight: CGFloat = 0.0
//--------------------------------------------------
// MARK: - Internal Properties
//--------------------------------------------------
weak var delegate: TableCollectionViewLayoutDataDelegate? weak var delegate: TableCollectionViewLayoutDataDelegate?
///padding type to be set from Table component, which is used to calculate the size & position of the cell.
var layoutPadding: Table.Padding = .standard var layoutPadding: Table.Padding = .standard
var itemCache: [UICollectionViewLayoutAttributes] = [] //--------------------------------------------------
// MARK: - Overrides
var layoutHeight: CGFloat = 0.0 //--------------------------------------------------
/// Calculates the layout attribute properties & total height of the collectionView
override func prepare() { override func prepare() {
super.prepare() super.prepare()
@ -35,18 +54,22 @@ class MatrixFlowLayout : UICollectionViewLayout {
var yPos: CGFloat = 0.0 var yPos: CGFloat = 0.0
///Looping through all the sections of the collectionView, visually these are rows
for currentSection in 0..<sections { for currentSection in 0..<sections {
let items = collectionView.numberOfItems(inSection: currentSection) let items = collectionView.numberOfItems(inSection: currentSection)
///Looping through all the items in section, visually these are each column in the row
for currentItem in 0..<items { for currentItem in 0..<items {
let indexPath = IndexPath(row: currentItem, section: currentSection) let indexPath = IndexPath(row: currentItem, section: currentSection)
/// Dividing the colletionView width by number of items(columns) in the row to determine width of each cell
let itemWidth = floor(collectionView.safeAreaLayoutGuide.layoutFrame.width / CGFloat(items)) let itemWidth = floor(collectionView.safeAreaLayoutGuide.layoutFrame.width / CGFloat(items))
let selectedItem = delegate.collectionView(collectionView, dataForItemAt: indexPath) let selectedItem = delegate.collectionView(collectionView, dataForItemAt: indexPath)
///Calculate the estimated height of the cell
let itemHeight = estimateHeightFor(item: selectedItem, with: itemWidth) let itemHeight = estimateHeightFor(item: selectedItem, with: itemWidth)
let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath) let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath)
@ -60,26 +83,30 @@ class MatrixFlowLayout : UICollectionViewLayout {
itemCache.append(attribute) itemCache.append(attribute)
} }
///Determines the highest height from all the cells(columns) in the row
let highestHeightForSection = itemCache.sorted(by: {$0.frame.size.height > $1.frame.size.height }).first?.frame.size.height ?? 0.0 let highestHeightForSection = itemCache.sorted(by: {$0.frame.size.height > $1.frame.size.height }).first?.frame.size.height ?? 0.0
///Set the highest height as height to all the cells in the row to make the row in uniform height.
itemCache.filter({$0.indexPath.section == currentSection}).forEach { attributes in itemCache.filter({$0.indexPath.section == currentSection}).forEach { attributes in
attributes.frame.size.height = highestHeightForSection attributes.frame.size.height = highestHeightForSection
} }
///Adds the height to y position for the next section
yPos += highestHeightForSection yPos += highestHeightForSection
} }
layoutHeight = yPos layoutHeight = yPos
} }
/// Fetches estimated height by calling the cell's component estimated height and adding padding
private func estimateHeightFor(item: TableItemModel, with width: CGFloat) -> CGFloat { private func estimateHeightFor(item: TableItemModel, with width: CGFloat) -> CGFloat {
let itemWidth = width - layoutPadding.horizontalValue() - VDSLayout.space1X let itemWidth = width - layoutPadding.horizontalValue() - defaultLeadingPadding
let height = item.component.estimatedHeight(for: itemWidth) ?? 0 let height = item.component.estimatedHeight(for: itemWidth) ?? item.defaultHeight
return height + (2 * layoutPadding.verticalValue()) return height + (2 * layoutPadding.verticalValue())
} }
///This will return the layout attributes for the elements in the defined rect
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = [] var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = []
for attributes in itemCache { for attributes in itemCache {
@ -89,15 +116,18 @@ class MatrixFlowLayout : UICollectionViewLayout {
} }
return visibleLayoutAttributes return visibleLayoutAttributes
} }
///This will return the layout attributes at particular indexPath
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
return itemCache.filter({ $0.indexPath == indexPath}).first return itemCache.filter({ $0.indexPath == indexPath}).first
} }
///Returns the collectionview content size
override var collectionViewContentSize: CGSize { override var collectionViewContentSize: CGSize {
return CGSize(width: contentWidth, height: layoutHeight) return CGSize(width: contentWidth, height: layoutHeight)
} }
/// Returns the width collectionView width
private var contentWidth: CGFloat { private var contentWidth: CGFloat {
guard let collectionView = collectionView else { return 0 } guard let collectionView = collectionView else { return 0 }
return collectionView.bounds.width return collectionView.bounds.width