From 17c5ab948e59cb9594ca9661790cceed9e26cf9e Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Tue, 30 Apr 2024 16:49:45 +0530 Subject: [PATCH] Initial commit for Table component --- VDS.xcodeproj/project.pbxproj | 16 ++ .../xcshareddata/xcschemes/VDS.xcscheme | 2 +- VDS/Components/Table/Table.swift | 157 ++++++++++++++++++ VDS/Components/Table/TableCellItem.swift | 80 +++++++++ 4 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 VDS/Components/Table/Table.swift create mode 100644 VDS/Components/Table/TableCellItem.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index f805c10f..1d495038 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -20,6 +20,8 @@ 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A032B96F050006602CC /* BreadcrumbItem.swift */; }; 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; }; 18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */; }; + 440B84CA2BD8E0E9004A732A /* Table.swift in Sources */ = {isa = PBXBuildFile; fileRef = 440B84C92BD8E0E9004A732A /* Table.swift */; }; + 443DBAFA2BDA303F0021497E /* TableCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 443DBAF92BDA303F0021497E /* TableCellItem.swift */; }; 445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BA07729C07B3D0036A7C5 /* Notification.swift */; }; 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */; }; 44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; }; @@ -209,6 +211,8 @@ 18A65A032B96F050006602CC /* BreadcrumbItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItem.swift; sourceTree = ""; }; 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = ""; }; 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = ""; }; + 440B84C92BD8E0E9004A732A /* Table.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Table.swift; sourceTree = ""; }; + 443DBAF92BDA303F0021497E /* TableCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellItem.swift; sourceTree = ""; }; 445BA07729C07B3D0036A7C5 /* Notification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notification.swift; sourceTree = ""; }; 44604AD329CE186A00E62B51 /* NotificationButtonModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationButtonModel.swift; sourceTree = ""; }; 44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; @@ -427,6 +431,15 @@ path = Breadcrumbs; sourceTree = ""; }; + 440B84C82BD8E0CE004A732A /* Table */ = { + isa = PBXGroup; + children = ( + 440B84C92BD8E0E9004A732A /* Table.swift */, + 443DBAF92BDA303F0021497E /* TableCellItem.swift */, + ); + path = Table; + sourceTree = ""; + }; 445BA07629C07ABA0036A7C5 /* Notification */ = { isa = PBXGroup; children = ( @@ -584,6 +597,7 @@ 71B23C2B2B91FA510027F7D9 /* Pagination */, EA89200B28B530F0006B9984 /* RadioBox */, EAF7F11428A1470D00B287F5 /* RadioButton */, + 440B84C82BD8E0CE004A732A /* Table */, EA596ABB2A16B4D500300C4B /* Tabs */, EAC925852911C9DE00091998 /* TextFields */, EA5E304A294CBDBB0082B959 /* TileContainer */, @@ -1156,6 +1170,7 @@ EAC9258F2911C9DE00091998 /* EntryFieldBase.swift in Sources */, EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */, EAD068922A560B65002E3A2D /* LoaderViewController.swift in Sources */, + 443DBAFA2BDA303F0021497E /* TableCellItem.swift in Sources */, 71FC86DA2B96F44C00700965 /* PaginationButton.swift in Sources */, EABFEB642A26473700C4C106 /* NSAttributedString.swift in Sources */, EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */, @@ -1174,6 +1189,7 @@ EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */, EA0D1C3D2A6AD57600E5C127 /* Typography+Enums.swift in Sources */, EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */, + 440B84CA2BD8E0E9004A732A /* Table.swift in Sources */, EAF7F0A2289AFB3900B287F5 /* Errorable.swift in Sources */, EA8E40912A7D3F6300934ED3 /* UIView+Accessibility.swift in Sources */, EA6F330E2B911E9000BACAB9 /* TextView.swift in Sources */, diff --git a/VDS.xcodeproj/xcshareddata/xcschemes/VDS.xcscheme b/VDS.xcodeproj/xcshareddata/xcschemes/VDS.xcscheme index 470df395..d523a42c 100644 --- a/VDS.xcodeproj/xcshareddata/xcschemes/VDS.xcscheme +++ b/VDS.xcodeproj/xcshareddata/xcschemes/VDS.xcscheme @@ -1,7 +1,7 @@ + version = "1.8"> CGFloat { + switch self { + case .standard: + return UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X + case .compact: + return UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X + } + } + + 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 + //-------------------------------------------------- + + open var striped: Bool = false { didSet { setNeedsUpdate() } } + + open var padding: Padding = .standard { didSet { setNeedsUpdate() } } + + open var headerBottomLine: Bool = false { didSet { setNeedsUpdate() } } + + open var rowBottomLine: Bool = false { didSet { setNeedsUpdate() } } + + open var headerBottomLineType: Line.Style = .primary { didSet { setNeedsUpdate() } } + + open var rowBottomLineType: Line.Style = .secondary { didSet { setNeedsUpdate() } } + + open var tableData: [[Any]]? { didSet { setNeedsUpdate() } } + + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + + open override func initialSetup() { + super.initialSetup() + addSubview(matrixView) + matrixView.pinToSuperView() + } + + open override func updateView() { + super.updateView() + matrixView.reloadData() + } +} + +extension Table : UICollectionViewDelegate, UICollectionViewDataSource { + + public func numberOfSections(in collectionView: UICollectionView) -> Int { + return tableData?.count ?? 0 + } + public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return tableData?[section].count ?? 0 + } + + public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TableCellItem.Identifier, for: indexPath) as? TableCellItem, + let currentItem = tableData?[indexPath.section][indexPath.row] + else { return UICollectionViewCell() } + let shouldStrip = striped ? (indexPath.section % 2 != 0) : false + let style = indexPath.section == 0 ? headerBottomLineType : rowBottomLineType + let hideSeparator = indexPath.section == 0 ? headerBottomLine : rowBottomLine + cell.updateCell(content: currentItem, surface: surface, separatorStyle: style, isHeader: indexPath.section == 0, hideSeparator: hideSeparator, striped: shouldStrip) + return cell + } +} + +extension Table: UICollectionViewDelegateFlowLayout { + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + guard let sectionCount = tableData?[indexPath.section].count else { return CGSize.zero } + let width = Int(collectionView.frame.width) / sectionCount + return CGSize(width: width, height: 50) + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat { + //return padding.verticalValue() + return 0 + } + + public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat { + //return padding.horizontalValue() + return 0 + } + +} + +final class MatrixFlowLayout : UICollectionViewFlowLayout { + + override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + guard let layoutAttributesObjects = super.layoutAttributesForElements(in: rect) else { return nil } + layoutAttributesObjects.forEach({ layoutAttributes in + if layoutAttributes.representedElementCategory == .cell, + let newFrame = layoutAttributesForItem(at: layoutAttributes.indexPath)?.frame { + layoutAttributes.frame = newFrame + } + }) + return layoutAttributesObjects + } + + override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + guard let collectionView = collectionView, + let layoutAttributes = super.layoutAttributesForItem(at: indexPath) else { return nil } + let itemsCount = CGFloat(collectionView.numberOfItems(inSection: indexPath.section)) + layoutAttributes.frame.size.width = ceil(collectionView.safeAreaLayoutGuide.layoutFrame.width / itemsCount) + return layoutAttributes + } +} diff --git a/VDS/Components/Table/TableCellItem.swift b/VDS/Components/Table/TableCellItem.swift new file mode 100644 index 00000000..da5a6ee1 --- /dev/null +++ b/VDS/Components/Table/TableCellItem.swift @@ -0,0 +1,80 @@ +// +// TableCellItem.swift +// VDS +// +// Created by Nadigadda, Sumanth on 25/04/24. +// + +import Foundation +import UIKit +import VDSTokens + +final class TableCellItem: UICollectionViewCell { + + static let Identifier: String = String(describing: TableCellItem.self) + + private let containerView = View().with { + $0.translatesAutoresizingMaskIntoConstraints = false + } + + private var cellLabel = Label().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) + $0.setContentHuggingPriority(.defaultHigh, for:.vertical) + $0.textAlignment = .left + $0.lineBreakMode = .byWordWrapping + } + + private let separator: Line = Line() + + private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) + private let stripedColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark) + + + override init(frame: CGRect) { + super.init(frame: frame) + setupCell() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupCell() + } + + private func setupCell() { + contentView.backgroundColor = .clear + + addSubview(containerView) + containerView.pinToSuperView() + + containerView.addSubview(cellLabel) + cellLabel.pinToSuperView() + + containerView.addSubview(separator) + separator.pinLeading().pinTrailing().pinBottom() + } + + func updateCell(content: Any, surface: Surface, separatorStyle: Line.Style, isHeader: Bool = false, hideSeparator: Bool = false, striped: Bool = false) { + guard let info = content as? String else { return } + cellLabel.textStyle = textStyle(for: isHeader) + cellLabel.text = info + cellLabel.surface = surface + + containerView.surface = surface + containerView.backgroundColor = striped ? stripedColorConfiguration.getColor(surface) : backgroundColorConfiguration.getColor(surface) + + separator.isHidden = hideSeparator + separator.style = separatorStyle + separator.surface = surface + } + + private func textStyle(for header:Bool) -> TextStyle { + return header ? .boldTitleSmall : UIDevice.isIPad ? .bodyLarge : .bodySmall + } + + override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { + let targetSize = CGSize(width: layoutAttributes.frame.width, height: 0) + layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) + return layoutAttributes + } +}