From 17c5ab948e59cb9594ca9661790cceed9e26cf9e Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Tue, 30 Apr 2024 16:49:45 +0530 Subject: [PATCH 01/11] 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 + } +} From 39ef411559c7c5ea1b04d4ec5ad9b92f74280a61 Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Thu, 2 May 2024 13:36:29 +0530 Subject: [PATCH 02/11] Adding padding related changes to table --- VDS/Components/Table/Table.swift | 17 ++++------------- VDS/Components/Table/TableCellItem.swift | 22 ++++++++++++++++++---- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift index ded02e63..41945763 100644 --- a/VDS/Components/Table/Table.swift +++ b/VDS/Components/Table/Table.swift @@ -31,6 +31,8 @@ open class Table: View { private let flowLayout = MatrixFlowLayout().with { $0.estimatedItemSize = UICollectionViewFlowLayout.automaticSize + $0.minimumLineSpacing = 0 + $0.minimumInteritemSpacing = 0 } //-------------------------------------------------- @@ -109,7 +111,7 @@ extension Table : UICollectionViewDelegate, UICollectionViewDataSource { 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) + cell.updateCell(content: currentItem, surface: surface, separatorStyle: style, isHeader: indexPath.section == 0, hideSeparator: hideSeparator, striped: shouldStrip, padding: padding) return cell } } @@ -119,19 +121,8 @@ 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) + return CGSize(width: width, height: 100) } - - 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 { diff --git a/VDS/Components/Table/TableCellItem.swift b/VDS/Components/Table/TableCellItem.swift index da5a6ee1..bfc5a430 100644 --- a/VDS/Components/Table/TableCellItem.swift +++ b/VDS/Components/Table/TableCellItem.swift @@ -30,6 +30,9 @@ final class TableCellItem: UICollectionViewCell { private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) private let stripedColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark) + private var labelTopConstraint: NSLayoutConstraint? + private var labelBottomConstraint: NSLayoutConstraint? + private var labelTrailingConstraint: NSLayoutConstraint? override init(frame: CGRect) { super.init(frame: frame) @@ -48,13 +51,21 @@ final class TableCellItem: UICollectionViewCell { containerView.pinToSuperView() containerView.addSubview(cellLabel) - cellLabel.pinToSuperView() + cellLabel.pinLeading() + + labelTopConstraint = cellLabel.pinTop(anchor: containerView.topAnchor, constant: 0) + labelBottomConstraint = containerView.pinBottom(anchor: cellLabel.bottomAnchor, constant: 0) + labelTrailingConstraint = containerView.pinTrailing(anchor: cellLabel.trailingAnchor, constant: 0) + + labelTopConstraint?.activate() + labelBottomConstraint?.activate() + labelTrailingConstraint?.activate() 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) { + func updateCell(content: Any, surface: Surface, separatorStyle: Line.Style, isHeader: Bool = false, hideSeparator: Bool = false, striped: Bool = false, padding: Table.Padding = .standard) { guard let info = content as? String else { return } cellLabel.textStyle = textStyle(for: isHeader) cellLabel.text = info @@ -66,6 +77,10 @@ final class TableCellItem: UICollectionViewCell { separator.isHidden = hideSeparator separator.style = separatorStyle separator.surface = surface + + labelTopConstraint?.constant = padding.verticalValue() + labelBottomConstraint?.constant = padding.verticalValue() + labelTrailingConstraint?.constant = padding.horizontalValue() } private func textStyle(for header:Bool) -> TextStyle { @@ -73,8 +88,7 @@ final class TableCellItem: UICollectionViewCell { } 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) + layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(layoutAttributes.frame.size, withHorizontalFittingPriority: .required, verticalFittingPriority: .required) return layoutAttributes } } From d94c845eb3aee196f1cd5d940042f034b7eb1f56 Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Thu, 2 May 2024 18:47:30 +0530 Subject: [PATCH 03/11] Adding models for the tab cell items --- VDS.xcodeproj/project.pbxproj | 8 +++ VDS/Components/Table/Table.swift | 2 +- VDS/Components/Table/TableCellItem.swift | 60 ++++++++++++------- .../Table/TableCellLabelModel.swift | 39 ++++++++++++ VDS/Components/Table/TableCellModel.swift | 10 ++++ 5 files changed, 96 insertions(+), 23 deletions(-) create mode 100644 VDS/Components/Table/TableCellLabelModel.swift create mode 100644 VDS/Components/Table/TableCellModel.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index f4a3f1ac..24239140 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -25,6 +25,8 @@ 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 */; }; + 44A952D92BE384C40009F874 /* TableCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952D82BE384C40009F874 /* TableCellModel.swift */; }; + 44A952DB2BE3852E0009F874 /* TableCellLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */; }; 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; }; 5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; 710607952B91A99500F2863F /* TitleletChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 710607942B91A99500F2863F /* TitleletChangeLog.txt */; }; @@ -216,6 +218,8 @@ 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 = ""; }; + 44A952D82BE384C40009F874 /* TableCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellModel.swift; sourceTree = ""; }; + 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellLabelModel.swift; sourceTree = ""; }; 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = ""; }; 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; 710607942B91A99500F2863F /* TitleletChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TitleletChangeLog.txt; sourceTree = ""; }; @@ -436,6 +440,8 @@ children = ( 440B84C92BD8E0E9004A732A /* Table.swift */, 443DBAF92BDA303F0021497E /* TableCellItem.swift */, + 44A952D82BE384C40009F874 /* TableCellModel.swift */, + 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */, ); path = Table; sourceTree = ""; @@ -1142,6 +1148,7 @@ EAACB89A2B927108006A3869 /* Valuing.swift in Sources */, EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */, EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */, + 44A952D92BE384C40009F874 /* TableCellModel.swift in Sources */, EA978EC5291D6AFE00ACC883 /* AnyLabelAttribute.swift in Sources */, 71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */, EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */, @@ -1211,6 +1218,7 @@ EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */, EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */, EAF7F0AB289B13FD00B287F5 /* TextStyleLabelAttribute.swift in Sources */, + 44A952DB2BE3852E0009F874 /* TableCellLabelModel.swift in Sources */, EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */, EA81410B2A0E8E3C004F60D2 /* ButtonIcon.swift in Sources */, EA985BE629688F6A00F2FF2E /* TileletBadgeModel.swift in Sources */, diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift index 41945763..32195467 100644 --- a/VDS/Components/Table/Table.swift +++ b/VDS/Components/Table/Table.swift @@ -77,7 +77,7 @@ open class Table: View { open var rowBottomLineType: Line.Style = .secondary { didSet { setNeedsUpdate() } } - open var tableData: [[Any]]? { didSet { setNeedsUpdate() } } + open var tableData: [[TableCellModel]]? { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Overrides diff --git a/VDS/Components/Table/TableCellItem.swift b/VDS/Components/Table/TableCellItem.swift index bfc5a430..7c2a1cdd 100644 --- a/VDS/Components/Table/TableCellItem.swift +++ b/VDS/Components/Table/TableCellItem.swift @@ -25,6 +25,10 @@ final class TableCellItem: UICollectionViewCell { $0.lineBreakMode = .byWordWrapping } + private var icon = Icon().with { + $0.size = UIDevice.isIPad ? .medium : .small + } + private let separator: Line = Line() private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) @@ -49,38 +53,50 @@ final class TableCellItem: UICollectionViewCell { addSubview(containerView) containerView.pinToSuperView() - - containerView.addSubview(cellLabel) - cellLabel.pinLeading() - - labelTopConstraint = cellLabel.pinTop(anchor: containerView.topAnchor, constant: 0) - labelBottomConstraint = containerView.pinBottom(anchor: cellLabel.bottomAnchor, constant: 0) - labelTrailingConstraint = containerView.pinTrailing(anchor: cellLabel.trailingAnchor, constant: 0) - - labelTopConstraint?.activate() - labelBottomConstraint?.activate() - labelTrailingConstraint?.activate() - - 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, padding: Table.Padding = .standard) { - guard let info = content as? String else { return } - cellLabel.textStyle = textStyle(for: isHeader) - cellLabel.text = info - cellLabel.surface = surface + func updateCell(content: TableCellModel, surface: Surface, separatorStyle: Line.Style, isHeader: Bool = false, hideSeparator: Bool = false, striped: Bool = false, padding: Table.Padding = .standard) { + + containerView.subviews.forEach({ $0.removeFromSuperview() }) containerView.surface = surface containerView.backgroundColor = striped ? stripedColorConfiguration.getColor(surface) : backgroundColorConfiguration.getColor(surface) + if let model = content as? Table.TableCellLabelModel { + addLabel(model: model, surface: surface, isHeader: isHeader, padding: padding) + } else if let model = content as? Table.TableCellImageModel { + addImage(model: model, surface: surface) + } + + containerView.addSubview(separator) + separator.pinLeading().pinTrailing().pinBottom() + separator.isHidden = hideSeparator separator.style = separatorStyle separator.surface = surface + } + + private func addLabel(model: Table.TableCellLabelModel, surface: Surface, isHeader: Bool, padding: Table.Padding) { + + containerView.addSubview(cellLabel) + cellLabel.pinLeading(VDSLayout.space1X) + NSLayoutConstraint.activate([ + cellLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: padding.verticalValue()), + containerView.bottomAnchor.constraint(equalTo: cellLabel.bottomAnchor, constant: padding.verticalValue()), + containerView.trailingAnchor.constraint(equalTo: cellLabel.trailingAnchor, constant: padding.horizontalValue()) + ]) - labelTopConstraint?.constant = padding.verticalValue() - labelBottomConstraint?.constant = padding.verticalValue() - labelTrailingConstraint?.constant = padding.horizontalValue() + cellLabel.textStyle = textStyle(for: isHeader) + cellLabel.text = model.text + cellLabel.surface = surface + } + + private func addImage(model: Table.TableCellImageModel, surface: Surface) { + containerView.addSubview(icon) + icon.pinLeading().pinCenterY() + + icon.name = model.name + icon.surface = surface } private func textStyle(for header:Bool) -> TextStyle { diff --git a/VDS/Components/Table/TableCellLabelModel.swift b/VDS/Components/Table/TableCellLabelModel.swift new file mode 100644 index 00000000..158c9bd4 --- /dev/null +++ b/VDS/Components/Table/TableCellLabelModel.swift @@ -0,0 +1,39 @@ +// +// TableCellLabelModel.swift +// VDS +// +// Created by Nadigadda, Sumanth on 02/05/24. +// + +import Foundation +extension Table { + public struct TableCellLabelModel: TableCellModel, Surfaceable { + + public var text: String + + public var accessibilityString: String? + + public var surface: Surface + + public init(text: String, accessibilityString: String? = "", surface: Surface = .light) { + self.text = text + self.accessibilityString = accessibilityString + self.surface = surface + } + } + + public struct TableCellImageModel: TableCellModel, Surfaceable { + + public var name: Icon.Name + + public var size: Icon.Size + + public var surface: Surface + + public init(name: Icon.Name, size: Icon.Size, surface: Surface = .light) { + self.name = name + self.size = size + self.surface = surface + } + } +} diff --git a/VDS/Components/Table/TableCellModel.swift b/VDS/Components/Table/TableCellModel.swift new file mode 100644 index 00000000..38ac8a3f --- /dev/null +++ b/VDS/Components/Table/TableCellModel.swift @@ -0,0 +1,10 @@ +// +// TableCellModel.swift +// VDS +// +// Created by Nadigadda, Sumanth on 02/05/24. +// + +import Foundation + +public protocol TableCellModel { } From e1589577fef2a1f69e42f5a0b03b778e6f06df31 Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Thu, 2 May 2024 19:59:22 +0530 Subject: [PATCH 04/11] Moving flow layout to a new class --- VDS.xcodeproj/project.pbxproj | 4 +++ VDS/Components/Table/Table.swift | 22 ---------------- VDS/Components/Table/TableFlowLayout.swift | 30 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 22 deletions(-) create mode 100644 VDS/Components/Table/TableFlowLayout.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 24239140..0d91c8f8 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -27,6 +27,7 @@ 44604AD729CE196600E62B51 /* Line.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44604AD629CE196600E62B51 /* Line.swift */; }; 44A952D92BE384C40009F874 /* TableCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952D82BE384C40009F874 /* TableCellModel.swift */; }; 44A952DB2BE3852E0009F874 /* TableCellLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */; }; + 44A952DD2BE3DA820009F874 /* TableFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */; }; 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; }; 5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; 710607952B91A99500F2863F /* TitleletChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 710607942B91A99500F2863F /* TitleletChangeLog.txt */; }; @@ -220,6 +221,7 @@ 44604AD629CE196600E62B51 /* Line.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Line.swift; sourceTree = ""; }; 44A952D82BE384C40009F874 /* TableCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellModel.swift; sourceTree = ""; }; 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellLabelModel.swift; sourceTree = ""; }; + 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableFlowLayout.swift; sourceTree = ""; }; 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = ""; }; 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; 710607942B91A99500F2863F /* TitleletChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TitleletChangeLog.txt; sourceTree = ""; }; @@ -440,6 +442,7 @@ children = ( 440B84C92BD8E0E9004A732A /* Table.swift */, 443DBAF92BDA303F0021497E /* TableCellItem.swift */, + 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */, 44A952D82BE384C40009F874 /* TableCellModel.swift */, 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */, ); @@ -1226,6 +1229,7 @@ EA985BF02968A93600F2FF2E /* TitleLockupEyebrowModel.swift in Sources */, EA5E30532950DDA60082B959 /* TitleLockup.swift in Sources */, EAD062B02A3B873E0015965D /* BadgeIndicator.swift in Sources */, + 44A952DD2BE3DA820009F874 /* TableFlowLayout.swift in Sources */, EAA5EEB528ECBFB4003B3210 /* ImageLabelAttribute.swift in Sources */, 18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */, EA0B18062A9E2D2D00F2D0CD /* SelectorItemBase.swift in Sources */, diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift index 32195467..267ae6a5 100644 --- a/VDS/Components/Table/Table.swift +++ b/VDS/Components/Table/Table.swift @@ -124,25 +124,3 @@ extension Table: UICollectionViewDelegateFlowLayout { return CGSize(width: width, height: 100) } } - -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/TableFlowLayout.swift b/VDS/Components/Table/TableFlowLayout.swift new file mode 100644 index 00000000..8cb92de2 --- /dev/null +++ b/VDS/Components/Table/TableFlowLayout.swift @@ -0,0 +1,30 @@ +// +// TableFlowLayout.swift +// VDS +// +// Created by Nadigadda, Sumanth on 02/05/24. +// + +import UIKit + +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 + } +} From ed684acd620b6a51c6b76d879e25bc571b4887db Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Mon, 6 May 2024 16:00:35 +0530 Subject: [PATCH 05/11] Table component, changing the way collectionViewlayout measures the sizes of the cell & minor improvements --- VDS.xcodeproj/project.pbxproj | 4 + VDS/Components/Table/Table.swift | 30 ++--- .../Table/TableCellImageModel.swift | 28 +++++ VDS/Components/Table/TableCellItem.swift | 24 ++-- .../Table/TableCellLabelModel.swift | 18 +-- VDS/Components/Table/TableCellModel.swift | 4 +- VDS/Components/Table/TableFlowLayout.swift | 117 ++++++++++++++++-- 7 files changed, 166 insertions(+), 59 deletions(-) create mode 100644 VDS/Components/Table/TableCellImageModel.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 0d91c8f8..c9df4b12 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 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 */; }; + 446209482BE8E3AF003EBC19 /* TableCellImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 446209472BE8E3AF003EBC19 /* TableCellImageModel.swift */; }; 44A952D92BE384C40009F874 /* TableCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952D82BE384C40009F874 /* TableCellModel.swift */; }; 44A952DB2BE3852E0009F874 /* TableCellLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */; }; 44A952DD2BE3DA820009F874 /* TableFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */; }; @@ -219,6 +220,7 @@ 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 = ""; }; + 446209472BE8E3AF003EBC19 /* TableCellImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellImageModel.swift; sourceTree = ""; }; 44A952D82BE384C40009F874 /* TableCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellModel.swift; sourceTree = ""; }; 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellLabelModel.swift; sourceTree = ""; }; 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableFlowLayout.swift; sourceTree = ""; }; @@ -445,6 +447,7 @@ 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */, 44A952D82BE384C40009F874 /* TableCellModel.swift */, 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */, + 446209472BE8E3AF003EBC19 /* TableCellImageModel.swift */, ); path = Table; sourceTree = ""; @@ -1242,6 +1245,7 @@ EA0D1C392A6AD4DF00E5C127 /* Typography+SpacingConfig.swift in Sources */, EAB2376629E9952D00AABE9A /* UIApplication.swift in Sources */, EAB5FED429267EB300998C17 /* UIView+NSLayoutConstraint.swift in Sources */, + 446209482BE8E3AF003EBC19 /* TableCellImageModel.swift in Sources */, EAB2376829E9992800AABE9A /* TooltipAlertViewController.swift in Sources */, EA33623E2892EE950071C351 /* UIDevice.swift in Sources */, EA985C692971B90B00F2FF2E /* IconSize.swift in Sources */, diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift index 267ae6a5..40b3e089 100644 --- a/VDS/Components/Table/Table.swift +++ b/VDS/Components/Table/Table.swift @@ -29,10 +29,8 @@ open class Table: View { $0.backgroundColor = .clear } - private let flowLayout = MatrixFlowLayout().with { - $0.estimatedItemSize = UICollectionViewFlowLayout.automaticSize - $0.minimumLineSpacing = 0 - $0.minimumInteritemSpacing = 0 + private lazy var flowLayout = MatrixFlowLayout().with { + $0.delegate = self } //-------------------------------------------------- @@ -77,7 +75,7 @@ open class Table: View { open var rowBottomLineType: Line.Style = .secondary { didSet { setNeedsUpdate() } } - open var tableData: [[TableCellModel]]? { didSet { setNeedsUpdate() } } + open var tableData: [[TableCellModel]] = [] { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Overrides @@ -91,36 +89,32 @@ open class Table: View { open override func updateView() { super.updateView() + flowLayout.layoutPadding = padding matrixView.reloadData() + matrixView.collectionViewLayout.invalidateLayout() } } -extension Table : UICollectionViewDelegate, UICollectionViewDataSource { +extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableCollectionViewLayoutDataDelegate { public func numberOfSections(in collectionView: UICollectionView) -> Int { - return tableData?.count ?? 0 + return tableData.count } public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return tableData?[section].count ?? 0 + return tableData[section].count } 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() } + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TableCellItem.Identifier, for: indexPath) as? TableCellItem else { return UICollectionViewCell() } + let currentItem = tableData[indexPath.section][indexPath.row] 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, padding: padding) 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: 100) + func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableCellModel { + return tableData[indexPath.section][indexPath.row] } } diff --git a/VDS/Components/Table/TableCellImageModel.swift b/VDS/Components/Table/TableCellImageModel.swift new file mode 100644 index 00000000..883315f3 --- /dev/null +++ b/VDS/Components/Table/TableCellImageModel.swift @@ -0,0 +1,28 @@ +// +// TableCellImageModel.swift +// VDS +// +// Created by Nadigadda, Sumanth on 06/05/24. +// + +import Foundation + +extension Table { + + public struct TableCellImageModel: TableCellModel, Surfaceable { + + public var defaultHeight: CGFloat { return 50.0 } + + public var name: Icon.Name + + public var size: Icon.Size + + public var surface: Surface + + public init(name: Icon.Name, size: Icon.Size, surface: Surface = .light) { + self.name = name + self.size = size + self.surface = surface + } + } +} diff --git a/VDS/Components/Table/TableCellItem.swift b/VDS/Components/Table/TableCellItem.swift index 7c2a1cdd..1d1c9255 100644 --- a/VDS/Components/Table/TableCellItem.swift +++ b/VDS/Components/Table/TableCellItem.swift @@ -13,9 +13,7 @@ final class TableCellItem: UICollectionViewCell { static let Identifier: String = String(describing: TableCellItem.self) - private let containerView = View().with { - $0.translatesAutoresizingMaskIntoConstraints = false - } + private let containerView = View() private var cellLabel = Label().with { $0.translatesAutoresizingMaskIntoConstraints = false @@ -34,9 +32,7 @@ final class TableCellItem: UICollectionViewCell { private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) private let stripedColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark) - private var labelTopConstraint: NSLayoutConstraint? - private var labelBottomConstraint: NSLayoutConstraint? - private var labelTrailingConstraint: NSLayoutConstraint? + private var padding: Table.Padding = .standard override init(frame: CGRect) { super.init(frame: frame) @@ -58,7 +54,7 @@ final class TableCellItem: UICollectionViewCell { func updateCell(content: TableCellModel, surface: Surface, separatorStyle: Line.Style, isHeader: Bool = false, hideSeparator: Bool = false, striped: Bool = false, padding: Table.Padding = .standard) { containerView.subviews.forEach({ $0.removeFromSuperview() }) - + self.padding = padding containerView.surface = surface containerView.backgroundColor = striped ? stripedColorConfiguration.getColor(surface) : backgroundColorConfiguration.getColor(surface) @@ -71,7 +67,7 @@ final class TableCellItem: UICollectionViewCell { containerView.addSubview(separator) separator.pinLeading().pinTrailing().pinBottom() - separator.isHidden = hideSeparator + separator.isHidden = !hideSeparator separator.style = separatorStyle separator.surface = surface } @@ -103,8 +99,12 @@ final class TableCellItem: UICollectionViewCell { return header ? .boldTitleSmall : UIDevice.isIPad ? .bodyLarge : .bodySmall } - override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { - layoutAttributes.frame.size = contentView.systemLayoutSizeFitting(layoutAttributes.frame.size, withHorizontalFittingPriority: .required, verticalFittingPriority: .required) - return layoutAttributes - } +// override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { +// let labelWidth = layoutAttributes.frame.size.width - (2 * padding.horizontalValue()) +// let maxbounds = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude) +// let labelSize = cellLabel.textRect(forBounds: CGRect(origin: .zero, size: maxbounds), limitedToNumberOfLines: cellLabel.numberOfLines) +// var labelHeight = max(labelSize.height, layoutAttributes.frame.size.height) + (2 * padding.horizontalValue()) +// layoutAttributes.frame.size = CGSize(width: layoutAttributes.frame.size.width, height: labelHeight) +// return layoutAttributes +// } } diff --git a/VDS/Components/Table/TableCellLabelModel.swift b/VDS/Components/Table/TableCellLabelModel.swift index 158c9bd4..2f2d4297 100644 --- a/VDS/Components/Table/TableCellLabelModel.swift +++ b/VDS/Components/Table/TableCellLabelModel.swift @@ -6,9 +6,12 @@ // import Foundation + extension Table { public struct TableCellLabelModel: TableCellModel, Surfaceable { + public var defaultHeight: CGFloat { return 50.0 } + public var text: String public var accessibilityString: String? @@ -21,19 +24,4 @@ extension Table { self.surface = surface } } - - public struct TableCellImageModel: TableCellModel, Surfaceable { - - public var name: Icon.Name - - public var size: Icon.Size - - public var surface: Surface - - public init(name: Icon.Name, size: Icon.Size, surface: Surface = .light) { - self.name = name - self.size = size - self.surface = surface - } - } } diff --git a/VDS/Components/Table/TableCellModel.swift b/VDS/Components/Table/TableCellModel.swift index 38ac8a3f..5e92ebe8 100644 --- a/VDS/Components/Table/TableCellModel.swift +++ b/VDS/Components/Table/TableCellModel.swift @@ -7,4 +7,6 @@ import Foundation -public protocol TableCellModel { } +public protocol TableCellModel { + var defaultHeight: CGFloat { get } +} diff --git a/VDS/Components/Table/TableFlowLayout.swift b/VDS/Components/Table/TableFlowLayout.swift index 8cb92de2..8b6f313b 100644 --- a/VDS/Components/Table/TableFlowLayout.swift +++ b/VDS/Components/Table/TableFlowLayout.swift @@ -6,25 +6,116 @@ // import UIKit +import VDSTokens -final class MatrixFlowLayout : UICollectionViewFlowLayout { +protocol TableCollectionViewLayoutDataDelegate: AnyObject { + func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableCellModel +} + +class MatrixFlowLayout : UICollectionViewLayout { + + weak var delegate: TableCollectionViewLayoutDataDelegate? + + var layoutPadding: Table.Padding = .standard + + var itemCache: [UICollectionViewLayoutAttributes] = [] + + var layoutHeight: CGFloat = 0.0 + + override func prepare() { + super.prepare() + + itemCache.removeAll() + + layoutHeight = 0.0 + + guard let collectionView, let delegate else { return } + + let sections = collectionView.numberOfSections + + var yPos: CGFloat = 0.0 + + for currentSection in 0.. $1.frame.size.height }).first?.frame.size.height ?? 0.0 + + itemCache.filter({$0.indexPath.section == currentSection}).forEach { attributes in + attributes.frame.size.height = highestHeightForSection + } + + yPos += highestHeightForSection + } + + layoutHeight = yPos + } + + + private func estimateHeightFor(item: TableCellModel, with width: CGFloat, padding: Table.Padding, isHeader: Bool) -> CGFloat { + + if let model = item as? Table.TableCellLabelModel { + return estimatedHeightForLabel(item: model, with: width, padding: padding, isHeader: isHeader) + } + return (item.defaultHeight + (2 * padding.verticalValue())) + } 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 + var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = [] + for attributes in itemCache { + if attributes.frame.intersects(rect) { + visibleLayoutAttributes.append(attributes) } - }) - return layoutAttributesObjects + } + return visibleLayoutAttributes } 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 + return itemCache.filter({ $0.indexPath == indexPath}).first + } + + override var collectionViewContentSize: CGSize { + return CGSize(width: contentWidth, height: layoutHeight) + } + + private var contentWidth: CGFloat { + guard let collectionView = collectionView else { return 0 } + return collectionView.bounds.width + } +} + +extension MatrixFlowLayout { + + private func estimatedHeightForLabel(item: Table.TableCellLabelModel, with width: CGFloat, padding: Table.Padding, isHeader: Bool) -> CGFloat { + + let cellLabel = Label() + cellLabel.textStyle = isHeader ? .boldTitleSmall : UIDevice.isIPad ? .bodyLarge : .bodySmall + cellLabel.text = item.text + + let labelWidth = width - padding.horizontalValue() - VDSLayout.space1X + let maxbounds = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude) + let labelSize = cellLabel.sizeThatFits(maxbounds) + return labelSize.height + (2 * padding.verticalValue()) } } From 0d421190a203444f3604bc066a3a3ef56914784d Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Thu, 9 May 2024 18:24:02 +0530 Subject: [PATCH 06/11] Table component refactoring --- VDS.xcodeproj/project.pbxproj | 8 --- VDS/Components/Table/Table.swift | 20 +++--- .../Table/TableCellImageModel.swift | 28 -------- VDS/Components/Table/TableCellItem.swift | 70 ++++--------------- .../Table/TableCellLabelModel.swift | 27 ------- VDS/Components/Table/TableCellModel.swift | 35 +++++++++- VDS/Components/Table/TableFlowLayout.swift | 28 ++------ 7 files changed, 62 insertions(+), 154 deletions(-) delete mode 100644 VDS/Components/Table/TableCellImageModel.swift delete mode 100644 VDS/Components/Table/TableCellLabelModel.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index c9df4b12..9cc1431b 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -25,9 +25,7 @@ 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 */; }; - 446209482BE8E3AF003EBC19 /* TableCellImageModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 446209472BE8E3AF003EBC19 /* TableCellImageModel.swift */; }; 44A952D92BE384C40009F874 /* TableCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952D82BE384C40009F874 /* TableCellModel.swift */; }; - 44A952DB2BE3852E0009F874 /* TableCellLabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */; }; 44A952DD2BE3DA820009F874 /* TableFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */; }; 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; }; 5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; @@ -220,9 +218,7 @@ 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 = ""; }; - 446209472BE8E3AF003EBC19 /* TableCellImageModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellImageModel.swift; sourceTree = ""; }; 44A952D82BE384C40009F874 /* TableCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellModel.swift; sourceTree = ""; }; - 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellLabelModel.swift; sourceTree = ""; }; 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableFlowLayout.swift; sourceTree = ""; }; 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = ""; }; 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; @@ -446,8 +442,6 @@ 443DBAF92BDA303F0021497E /* TableCellItem.swift */, 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */, 44A952D82BE384C40009F874 /* TableCellModel.swift */, - 44A952DA2BE3852E0009F874 /* TableCellLabelModel.swift */, - 446209472BE8E3AF003EBC19 /* TableCellImageModel.swift */, ); path = Table; sourceTree = ""; @@ -1224,7 +1218,6 @@ EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */, EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */, EAF7F0AB289B13FD00B287F5 /* TextStyleLabelAttribute.swift in Sources */, - 44A952DB2BE3852E0009F874 /* TableCellLabelModel.swift in Sources */, EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */, EA81410B2A0E8E3C004F60D2 /* ButtonIcon.swift in Sources */, EA985BE629688F6A00F2FF2E /* TileletBadgeModel.swift in Sources */, @@ -1245,7 +1238,6 @@ EA0D1C392A6AD4DF00E5C127 /* Typography+SpacingConfig.swift in Sources */, EAB2376629E9952D00AABE9A /* UIApplication.swift in Sources */, EAB5FED429267EB300998C17 /* UIView+NSLayoutConstraint.swift in Sources */, - 446209482BE8E3AF003EBC19 /* TableCellImageModel.swift in Sources */, EAB2376829E9992800AABE9A /* TooltipAlertViewController.swift in Sources */, EA33623E2892EE950071C351 /* UIDevice.swift in Sources */, EA985C692971B90B00F2FF2E /* IconSize.swift in Sources */, diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift index 40b3e089..a7f89a7d 100644 --- a/VDS/Components/Table/Table.swift +++ b/VDS/Components/Table/Table.swift @@ -32,6 +32,10 @@ open class Table: View { private lazy var flowLayout = MatrixFlowLayout().with { $0.delegate = self } + + private var tableData: [[TableItemModel]] { + return tableHeader + tableRows + } //-------------------------------------------------- // MARK: - Enums @@ -67,15 +71,9 @@ open class Table: View { open var padding: Padding = .standard { didSet { setNeedsUpdate() } } - open var headerBottomLine: Bool = false { didSet { setNeedsUpdate() } } + open var tableHeader: [[TableItemModel]] = [] { 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: [[TableCellModel]] = [] { didSet { setNeedsUpdate() } } + open var tableRows: [[TableItemModel]] = [] { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Overrides @@ -108,13 +106,11 @@ extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableColl guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TableCellItem.Identifier, for: indexPath) as? TableCellItem else { return UICollectionViewCell() } let currentItem = tableData[indexPath.section][indexPath.row] 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, padding: padding) + cell.updateCell(content: currentItem, surface: surface, striped: shouldStrip, padding: padding) return cell } - func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableCellModel { + func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableItemModel { return tableData[indexPath.section][indexPath.row] } } diff --git a/VDS/Components/Table/TableCellImageModel.swift b/VDS/Components/Table/TableCellImageModel.swift deleted file mode 100644 index 883315f3..00000000 --- a/VDS/Components/Table/TableCellImageModel.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// TableCellImageModel.swift -// VDS -// -// Created by Nadigadda, Sumanth on 06/05/24. -// - -import Foundation - -extension Table { - - public struct TableCellImageModel: TableCellModel, Surfaceable { - - public var defaultHeight: CGFloat { return 50.0 } - - public var name: Icon.Name - - public var size: Icon.Size - - public var surface: Surface - - public init(name: Icon.Name, size: Icon.Size, surface: Surface = .light) { - self.name = name - self.size = size - self.surface = surface - } - } -} diff --git a/VDS/Components/Table/TableCellItem.swift b/VDS/Components/Table/TableCellItem.swift index 1d1c9255..989e8eef 100644 --- a/VDS/Components/Table/TableCellItem.swift +++ b/VDS/Components/Table/TableCellItem.swift @@ -15,18 +15,6 @@ final class TableCellItem: UICollectionViewCell { private let containerView = View() - 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 var icon = Icon().with { - $0.size = UIDevice.isIPad ? .medium : .small - } - private let separator: Line = Line() private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) @@ -51,60 +39,32 @@ final class TableCellItem: UICollectionViewCell { containerView.pinToSuperView() } - func updateCell(content: TableCellModel, surface: Surface, separatorStyle: Line.Style, isHeader: Bool = false, hideSeparator: Bool = false, striped: Bool = false, padding: Table.Padding = .standard) { + func updateCell(content: TableItemModel, surface: Surface, striped: Bool = false, padding: Table.Padding = .standard) { containerView.subviews.forEach({ $0.removeFromSuperview() }) self.padding = padding containerView.surface = surface containerView.backgroundColor = striped ? stripedColorConfiguration.getColor(surface) : backgroundColorConfiguration.getColor(surface) - if let model = content as? Table.TableCellLabelModel { - addLabel(model: model, surface: surface, isHeader: isHeader, padding: padding) - } else if let model = content as? Table.TableCellImageModel { - addImage(model: model, surface: surface) + containerView.addSubview(content.component) + + if var surfacedView = content.component as? Surfaceable { + surfacedView.surface = surface } + NSLayoutConstraint.activate([ + content.component.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: VDSLayout.space1X), + content.component.topAnchor.constraint(greaterThanOrEqualTo: containerView.topAnchor, constant: padding.verticalValue()), + containerView.bottomAnchor.constraint(greaterThanOrEqualTo: content.component.bottomAnchor, constant: padding.verticalValue()), + containerView.trailingAnchor.constraint(greaterThanOrEqualTo: content.component.trailingAnchor, constant: padding.horizontalValue()), + containerView.centerYAnchor.constraint(equalTo: content.component.centerYAnchor) + ]) + containerView.addSubview(separator) separator.pinLeading().pinTrailing().pinBottom() - separator.isHidden = !hideSeparator - separator.style = separatorStyle + separator.style = content.bottomLine ?? .primary + separator.isHidden = content.bottomLine == nil separator.surface = surface } - - private func addLabel(model: Table.TableCellLabelModel, surface: Surface, isHeader: Bool, padding: Table.Padding) { - - containerView.addSubview(cellLabel) - cellLabel.pinLeading(VDSLayout.space1X) - NSLayoutConstraint.activate([ - cellLabel.topAnchor.constraint(equalTo: containerView.topAnchor, constant: padding.verticalValue()), - containerView.bottomAnchor.constraint(equalTo: cellLabel.bottomAnchor, constant: padding.verticalValue()), - containerView.trailingAnchor.constraint(equalTo: cellLabel.trailingAnchor, constant: padding.horizontalValue()) - ]) - - cellLabel.textStyle = textStyle(for: isHeader) - cellLabel.text = model.text - cellLabel.surface = surface - } - - private func addImage(model: Table.TableCellImageModel, surface: Surface) { - containerView.addSubview(icon) - icon.pinLeading().pinCenterY() - - icon.name = model.name - icon.surface = surface - } - - private func textStyle(for header:Bool) -> TextStyle { - return header ? .boldTitleSmall : UIDevice.isIPad ? .bodyLarge : .bodySmall - } - -// override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes { -// let labelWidth = layoutAttributes.frame.size.width - (2 * padding.horizontalValue()) -// let maxbounds = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude) -// let labelSize = cellLabel.textRect(forBounds: CGRect(origin: .zero, size: maxbounds), limitedToNumberOfLines: cellLabel.numberOfLines) -// var labelHeight = max(labelSize.height, layoutAttributes.frame.size.height) + (2 * padding.horizontalValue()) -// layoutAttributes.frame.size = CGSize(width: layoutAttributes.frame.size.width, height: labelHeight) -// return layoutAttributes -// } } diff --git a/VDS/Components/Table/TableCellLabelModel.swift b/VDS/Components/Table/TableCellLabelModel.swift deleted file mode 100644 index 2f2d4297..00000000 --- a/VDS/Components/Table/TableCellLabelModel.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// TableCellLabelModel.swift -// VDS -// -// Created by Nadigadda, Sumanth on 02/05/24. -// - -import Foundation - -extension Table { - public struct TableCellLabelModel: TableCellModel, Surfaceable { - - public var defaultHeight: CGFloat { return 50.0 } - - public var text: String - - public var accessibilityString: String? - - public var surface: Surface - - public init(text: String, accessibilityString: String? = "", surface: Surface = .light) { - self.text = text - self.accessibilityString = accessibilityString - self.surface = surface - } - } -} diff --git a/VDS/Components/Table/TableCellModel.swift b/VDS/Components/Table/TableCellModel.swift index 5e92ebe8..82e33500 100644 --- a/VDS/Components/Table/TableCellModel.swift +++ b/VDS/Components/Table/TableCellModel.swift @@ -6,7 +6,38 @@ // import Foundation +import UIKit +import VDSTokens -public protocol TableCellModel { - var defaultHeight: CGFloat { get } +public protocol ComponentHeight { + func estimatedHeight(for width: CGFloat) -> CGFloat? +} + +public struct TableItemModel { + + public var defaultHeight: CGFloat { return 50.0 } + + public var bottomLine: Line.Style? + + public var component: UIView & ComponentHeight + + public init(bottomLine: Line.Style? = nil, component: UIView & ComponentHeight) { + self.bottomLine = bottomLine + self.component = component + } +} + +extension Label: ComponentHeight { + + public func estimatedHeight(for width: CGFloat) -> CGFloat? { + let maxbounds = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude) + return self.sizeThatFits(maxbounds).height + } +} + +extension Icon: ComponentHeight { + + public func estimatedHeight(for width: CGFloat) -> CGFloat? { + return intrinsicContentSize.height + } } diff --git a/VDS/Components/Table/TableFlowLayout.swift b/VDS/Components/Table/TableFlowLayout.swift index 8b6f313b..a3d8e817 100644 --- a/VDS/Components/Table/TableFlowLayout.swift +++ b/VDS/Components/Table/TableFlowLayout.swift @@ -9,7 +9,7 @@ import UIKit import VDSTokens protocol TableCollectionViewLayoutDataDelegate: AnyObject { - func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableCellModel + func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableItemModel } class MatrixFlowLayout : UICollectionViewLayout { @@ -47,7 +47,7 @@ class MatrixFlowLayout : UICollectionViewLayout { let selectedItem = delegate.collectionView(collectionView, dataForItemAt: indexPath) - let itemHeight = estimateHeightFor(item: selectedItem, with: itemWidth, padding: layoutPadding, isHeader: currentSection == 0) + let itemHeight = estimateHeightFor(item: selectedItem, with: itemWidth) let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath) @@ -73,12 +73,11 @@ class MatrixFlowLayout : UICollectionViewLayout { } - private func estimateHeightFor(item: TableCellModel, with width: CGFloat, padding: Table.Padding, isHeader: Bool) -> CGFloat { + private func estimateHeightFor(item: TableItemModel, with width: CGFloat) -> CGFloat { - if let model = item as? Table.TableCellLabelModel { - return estimatedHeightForLabel(item: model, with: width, padding: padding, isHeader: isHeader) - } - return (item.defaultHeight + (2 * padding.verticalValue())) + let itemWidth = width - layoutPadding.horizontalValue() - VDSLayout.space1X + let height = item.component.estimatedHeight(for: itemWidth) ?? 0 + return height + (2 * layoutPadding.verticalValue()) } override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { @@ -104,18 +103,3 @@ class MatrixFlowLayout : UICollectionViewLayout { return collectionView.bounds.width } } - -extension MatrixFlowLayout { - - private func estimatedHeightForLabel(item: Table.TableCellLabelModel, with width: CGFloat, padding: Table.Padding, isHeader: Bool) -> CGFloat { - - let cellLabel = Label() - cellLabel.textStyle = isHeader ? .boldTitleSmall : UIDevice.isIPad ? .bodyLarge : .bodySmall - cellLabel.text = item.text - - let labelWidth = width - padding.horizontalValue() - VDSLayout.space1X - let maxbounds = CGSize(width: labelWidth, height: CGFloat.greatestFiniteMagnitude) - let labelSize = cellLabel.sizeThatFits(maxbounds) - return labelSize.height + (2 * padding.verticalValue()) - } -} From d4957e5aecd27f18b96d43ee35262bf7b33dd8ea Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Tue, 14 May 2024 19:09:03 +0530 Subject: [PATCH 07/11] Adding comments to the Table components implementation. --- VDS/Components/Table/Table.swift | 32 +++++++++++++++- VDS/Components/Table/TableCellItem.swift | 21 ++++++++++- VDS/Components/Table/TableCellModel.swift | 5 +++ VDS/Components/Table/TableFlowLayout.swift | 44 ++++++++++++++++++---- 4 files changed, 93 insertions(+), 9 deletions(-) diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift index a7f89a7d..3c290a80 100644 --- a/VDS/Components/Table/Table.swift +++ b/VDS/Components/Table/Table.swift @@ -9,13 +9,15 @@ import Foundation import UIKit 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) 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 @@ -29,10 +31,12 @@ open class Table: View { $0.backgroundColor = .clear } + /// Custom flow layout to manage the height of the cells private lazy var flowLayout = MatrixFlowLayout().with { $0.delegate = self } + /// Array of ``TableItemModel`` by combining Header & Row items private var tableData: [[TableItemModel]] { return tableHeader + tableRows } @@ -41,6 +45,7 @@ open class Table: View { // MARK: - Enums //-------------------------------------------------- + /// Enums used to define the padding for the cell edge spacing. public enum Padding: String, CaseIterable { case standard, compact @@ -67,34 +72,55 @@ open class Table: View { // 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: [[TableItemModel]] = [] { didSet { setNeedsUpdate() } } + /// Parameter to show the all table rows open var tableRows: [[TableItemModel]] = [] { didSet { setNeedsUpdate() } } + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- + ///Called upon initializing the table view open override func initialSetup() { super.initialSetup() addSubview(matrixView) matrixView.pinToSuperView() } + /// Will update the table view, when called becasue of any changes in component parameters open override func updateView() { super.updateView() flowLayout.layoutPadding = padding matrixView.reloadData() 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 { + //-------------------------------------------------- + // MARK: - UICollectionViewDelegate & UICollectionViewDataSource + //-------------------------------------------------- + public func numberOfSections(in collectionView: UICollectionView) -> Int { return tableData.count } @@ -110,6 +136,10 @@ extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableColl return cell } + //-------------------------------------------------- + // MARK: - TableCollectionViewLayoutDataDelegate + //-------------------------------------------------- + func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableItemModel { return tableData[indexPath.section][indexPath.row] } diff --git a/VDS/Components/Table/TableCellItem.swift b/VDS/Components/Table/TableCellItem.swift index 989e8eef..1366b0c7 100644 --- a/VDS/Components/Table/TableCellItem.swift +++ b/VDS/Components/Table/TableCellItem.swift @@ -11,17 +11,31 @@ import VDSTokens final class TableCellItem: UICollectionViewCell { + /// Identifier for TableCellItem static let Identifier: String = String(describing: TableCellItem.self) + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + + /// Main view which holds the content of the cell private let containerView = View() + /// Line seperator for cell private let separator: Line = Line() + /// Color configuration for default background color private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) + + /// Color configuration for striped background color private let stripedColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundSecondaryLight, VDSColor.backgroundSecondaryDark) + /// Padding parameter to maintain the edge spacing of the containerView private var padding: Table.Padding = .standard + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- override init(frame: CGRect) { super.init(frame: frame) setupCell() @@ -39,7 +53,12 @@ final class TableCellItem: UICollectionViewCell { 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() }) self.padding = padding diff --git a/VDS/Components/Table/TableCellModel.swift b/VDS/Components/Table/TableCellModel.swift index 82e33500..5253c67f 100644 --- a/VDS/Components/Table/TableCellModel.swift +++ b/VDS/Components/Table/TableCellModel.swift @@ -9,16 +9,20 @@ import Foundation import UIKit import VDSTokens +/// Fetches the estimated height of component for said width public protocol ComponentHeight { func estimatedHeight(for width: CGFloat) -> CGFloat? } +/// Model that represent the content of each cell of Table component public struct TableItemModel { + /// Default cell height public var defaultHeight: CGFloat { return 50.0 } public var bottomLine: Line.Style? + /// Component to be show in the Table cell public var 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 { public func estimatedHeight(for width: CGFloat) -> CGFloat? { diff --git a/VDS/Components/Table/TableFlowLayout.swift b/VDS/Components/Table/TableFlowLayout.swift index a3d8e817..c66fa89e 100644 --- a/VDS/Components/Table/TableFlowLayout.swift +++ b/VDS/Components/Table/TableFlowLayout.swift @@ -14,14 +14,33 @@ protocol TableCollectionViewLayoutDataDelegate: AnyObject { 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? + ///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 itemCache: [UICollectionViewLayoutAttributes] = [] - - var layoutHeight: CGFloat = 0.0 + //-------------------------------------------------- + // MARK: - Overrides + //-------------------------------------------------- + /// Calculates the layout attribute properties & total height of the collectionView override func prepare() { super.prepare() @@ -35,18 +54,22 @@ class MatrixFlowLayout : UICollectionViewLayout { var yPos: CGFloat = 0.0 + ///Looping through all the sections of the collectionView, visually these are rows for currentSection in 0.. $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 attributes.frame.size.height = highestHeightForSection } + ///Adds the height to y position for the next section yPos += highestHeightForSection } 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 { - let itemWidth = width - layoutPadding.horizontalValue() - VDSLayout.space1X - let height = item.component.estimatedHeight(for: itemWidth) ?? 0 + let itemWidth = width - layoutPadding.horizontalValue() - defaultLeadingPadding + let height = item.component.estimatedHeight(for: itemWidth) ?? item.defaultHeight 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]? { var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = [] for attributes in itemCache { @@ -89,15 +116,18 @@ class MatrixFlowLayout : UICollectionViewLayout { } return visibleLayoutAttributes } - + + ///This will return the layout attributes at particular indexPath override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { return itemCache.filter({ $0.indexPath == indexPath}).first } + ///Returns the collectionview content size override var collectionViewContentSize: CGSize { return CGSize(width: contentWidth, height: layoutHeight) } + /// Returns the width collectionView width private var contentWidth: CGFloat { guard let collectionView = collectionView else { return 0 } return collectionView.bounds.width From 7748a5eae408b231f31d7ba0fc034cdcb2ea5209 Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Fri, 24 May 2024 20:49:58 +0530 Subject: [PATCH 08/11] Changes to accommodate any ui element into table cell and make its size dynamic. --- VDS/Components/Table/Table.swift | 21 ++++++++++++++- VDS/Components/Table/TableCellModel.swift | 30 +++------------------- VDS/Components/Table/TableFlowLayout.swift | 28 +++++++++++--------- 3 files changed, 39 insertions(+), 40 deletions(-) diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift index 3c290a80..de03e8fc 100644 --- a/VDS/Components/Table/Table.swift +++ b/VDS/Components/Table/Table.swift @@ -24,7 +24,6 @@ open class Table: View { $0.delegate = self $0.translatesAutoresizingMaskIntoConstraints = false $0.allowsSelection = false - $0.isScrollEnabled = false $0.showsVerticalScrollIndicator = false $0.showsHorizontalScrollIndicator = false $0.isAccessibilityElement = true @@ -34,6 +33,7 @@ open class Table: View { /// 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 @@ -84,6 +84,9 @@ open class Table: View { /// Parameter to show the all table rows open var tableRows: [[TableItemModel]] = [] { didSet { setNeedsUpdate() } } + open var fillContainer: Bool = true { didSet { setNeedsUpdate() } } + + open var columnWidths: [CGFloat]? { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Overrides @@ -99,6 +102,9 @@ open class Table: View { /// 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() @@ -111,8 +117,17 @@ open class Table: View { padding = .standard tableHeader = [] tableRows = [] + fillContainer = true + columnWidths = nil setNeedsUpdate() } + + func calculateColumnWidths() -> [CGFloat] { + guard let noOfColumns = tableData.first?.count else { return [] } + let itemWidth = floor(matrixView.safeAreaLayoutGuide.layoutFrame.width / CGFloat(noOfColumns)) + return Array(repeating: itemWidth, count: noOfColumns) + } + } extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableCollectionViewLayoutDataDelegate { @@ -143,4 +158,8 @@ extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableColl func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableItemModel { return tableData[indexPath.section][indexPath.row] } + + func collectionView(_ collectionView: UICollectionView, widthForItemAt indexPath: IndexPath) -> CGFloat { + return columnWidths?[indexPath.row] ?? 0.0 + } } diff --git a/VDS/Components/Table/TableCellModel.swift b/VDS/Components/Table/TableCellModel.swift index 5253c67f..01f03515 100644 --- a/VDS/Components/Table/TableCellModel.swift +++ b/VDS/Components/Table/TableCellModel.swift @@ -9,40 +9,16 @@ import Foundation import UIKit import VDSTokens -/// Fetches the estimated height of component for said width -public protocol ComponentHeight { - func estimatedHeight(for width: CGFloat) -> CGFloat? -} - /// Model that represent the content of each cell of Table component public struct TableItemModel { - - /// Default cell height - public var defaultHeight: CGFloat { return 50.0 } - + public var bottomLine: Line.Style? /// Component to be show in the Table cell - public var component: UIView & ComponentHeight + public var component: UIView - public init(bottomLine: Line.Style? = nil, component: UIView & ComponentHeight) { + public init(bottomLine: Line.Style? = nil, component: UIView) { self.bottomLine = bottomLine self.component = component } } - -/// Confirming protocol ``ComponentHeight`` to determine the height, based on the width -extension Label: ComponentHeight { - - public func estimatedHeight(for width: CGFloat) -> CGFloat? { - let maxbounds = CGSize(width: width, height: CGFloat.greatestFiniteMagnitude) - return self.sizeThatFits(maxbounds).height - } -} - -extension Icon: ComponentHeight { - - public func estimatedHeight(for width: CGFloat) -> CGFloat? { - return intrinsicContentSize.height - } -} diff --git a/VDS/Components/Table/TableFlowLayout.swift b/VDS/Components/Table/TableFlowLayout.swift index c66fa89e..0f3e231f 100644 --- a/VDS/Components/Table/TableFlowLayout.swift +++ b/VDS/Components/Table/TableFlowLayout.swift @@ -10,9 +10,10 @@ import VDSTokens protocol TableCollectionViewLayoutDataDelegate: AnyObject { func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableItemModel + func collectionView(_ collectionView: UICollectionView, widthForItemAt indexPath: IndexPath) -> CGFloat } -class MatrixFlowLayout : UICollectionViewLayout { +class MatrixFlowLayout : UICollectionViewFlowLayout { //-------------------------------------------------- // MARK: - Private Properties @@ -27,6 +28,9 @@ class MatrixFlowLayout : UICollectionViewLayout { /// Parameter to store the total height of the collectionView private var layoutHeight: CGFloat = 0.0 + /// Parameter to store the total width of the collectionView + private var layoutWidth: CGFloat = 0.0 + //-------------------------------------------------- // MARK: - Internal Properties //-------------------------------------------------- @@ -57,6 +61,9 @@ class MatrixFlowLayout : UICollectionViewLayout { ///Looping through all the sections of the collectionView, visually these are rows for currentSection in 0.. $1.frame.size.height }).first?.frame.size.height ?? 0.0 + let highestHeightForSection = itemCache.filter({$0.indexPath.section == currentSection}).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 @@ -102,8 +111,9 @@ class MatrixFlowLayout : UICollectionViewLayout { private func estimateHeightFor(item: TableItemModel, with width: CGFloat) -> CGFloat { let itemWidth = width - layoutPadding.horizontalValue() - defaultLeadingPadding - let height = item.component.estimatedHeight(for: itemWidth) ?? item.defaultHeight - return height + (2 * layoutPadding.verticalValue()) + let maxSize = CGSize(width: itemWidth, height: CGFloat.greatestFiniteMagnitude) + let estItemSize = item.component.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) + return estItemSize.height + (2 * layoutPadding.verticalValue()) } ///This will return the layout attributes for the elements in the defined rect @@ -124,12 +134,6 @@ class MatrixFlowLayout : UICollectionViewLayout { ///Returns the collectionview content size override var collectionViewContentSize: CGSize { - return CGSize(width: contentWidth, height: layoutHeight) - } - - /// Returns the width collectionView width - private var contentWidth: CGFloat { - guard let collectionView = collectionView else { return 0 } - return collectionView.bounds.width + return CGSize(width: layoutWidth, height: layoutHeight) } } From b88d76da419fa69ebcefe05cddc2d4babc016c73 Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Mon, 27 May 2024 15:02:49 +0530 Subject: [PATCH 09/11] Table component refactoring, TableRowModel creation & table item will be able to take a empty view. --- VDS.xcodeproj/project.pbxproj | 12 ++++++--- VDS/Components/Table/Table.swift | 14 +++++------ VDS/Components/Table/TableCellItem.swift | 29 ++++++++++++---------- VDS/Components/Table/TableFlowLayout.swift | 2 +- VDS/Components/Table/TableItemModel.swift | 26 +++++++++++++++++++ VDS/Components/Table/TableRowModel.swift | 21 ++++++++++++++++ 6 files changed, 79 insertions(+), 25 deletions(-) create mode 100644 VDS/Components/Table/TableItemModel.swift create mode 100644 VDS/Components/Table/TableRowModel.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 9cc1431b..d0a6b550 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -25,8 +25,9 @@ 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 */; }; - 44A952D92BE384C40009F874 /* TableCellModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952D82BE384C40009F874 /* TableCellModel.swift */; }; + 44A952D92BE384C40009F874 /* TableItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952D82BE384C40009F874 /* TableItemModel.swift */; }; 44A952DD2BE3DA820009F874 /* TableFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */; }; + 44BD43B62C04866600644F87 /* TableRowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BD43B52C04866600644F87 /* TableRowModel.swift */; }; 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; }; 5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; 710607952B91A99500F2863F /* TitleletChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 710607942B91A99500F2863F /* TitleletChangeLog.txt */; }; @@ -218,8 +219,9 @@ 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 = ""; }; - 44A952D82BE384C40009F874 /* TableCellModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableCellModel.swift; sourceTree = ""; }; + 44A952D82BE384C40009F874 /* TableItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableItemModel.swift; sourceTree = ""; }; 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableFlowLayout.swift; sourceTree = ""; }; + 44BD43B52C04866600644F87 /* TableRowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableRowModel.swift; sourceTree = ""; }; 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = ""; }; 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; 710607942B91A99500F2863F /* TitleletChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TitleletChangeLog.txt; sourceTree = ""; }; @@ -441,7 +443,8 @@ 440B84C92BD8E0E9004A732A /* Table.swift */, 443DBAF92BDA303F0021497E /* TableCellItem.swift */, 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */, - 44A952D82BE384C40009F874 /* TableCellModel.swift */, + 44BD43B52C04866600644F87 /* TableRowModel.swift */, + 44A952D82BE384C40009F874 /* TableItemModel.swift */, ); path = Table; sourceTree = ""; @@ -1148,7 +1151,7 @@ EAACB89A2B927108006A3869 /* Valuing.swift in Sources */, EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */, EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */, - 44A952D92BE384C40009F874 /* TableCellModel.swift in Sources */, + 44A952D92BE384C40009F874 /* TableItemModel.swift in Sources */, EA978EC5291D6AFE00ACC883 /* AnyLabelAttribute.swift in Sources */, 71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */, EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */, @@ -1178,6 +1181,7 @@ EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */, EAD068922A560B65002E3A2D /* LoaderViewController.swift in Sources */, 443DBAFA2BDA303F0021497E /* TableCellItem.swift in Sources */, + 44BD43B62C04866600644F87 /* TableRowModel.swift in Sources */, 71FC86DA2B96F44C00700965 /* PaginationButton.swift in Sources */, EABFEB642A26473700C4C106 /* NSAttributedString.swift in Sources */, EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */, diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift index de03e8fc..97452db9 100644 --- a/VDS/Components/Table/Table.swift +++ b/VDS/Components/Table/Table.swift @@ -37,7 +37,7 @@ open class Table: View { } /// Array of ``TableItemModel`` by combining Header & Row items - private var tableData: [[TableItemModel]] { + private var tableData: [TableRowModel] { return tableHeader + tableRows } @@ -79,10 +79,10 @@ open class Table: View { open var padding: Padding = .standard { didSet { setNeedsUpdate() } } /// Parameter to show the table header row - open var tableHeader: [[TableItemModel]] = [] { didSet { setNeedsUpdate() } } + open var tableHeader: [TableRowModel] = [] { didSet { setNeedsUpdate() } } /// Parameter to show the all table rows - open var tableRows: [[TableItemModel]] = [] { didSet { setNeedsUpdate() } } + open var tableRows: [TableRowModel] = [] { didSet { setNeedsUpdate() } } open var fillContainer: Bool = true { didSet { setNeedsUpdate() } } @@ -123,7 +123,7 @@ open class Table: View { } func calculateColumnWidths() -> [CGFloat] { - guard let noOfColumns = tableData.first?.count else { return [] } + guard let noOfColumns = tableData.first?.columnsCount else { return [] } let itemWidth = floor(matrixView.safeAreaLayoutGuide.layoutFrame.width / CGFloat(noOfColumns)) return Array(repeating: itemWidth, count: noOfColumns) } @@ -140,12 +140,12 @@ extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableColl return tableData.count } public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return tableData[section].count + 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][indexPath.row] + let currentItem = tableData[indexPath.section].columns[indexPath.row] let shouldStrip = striped ? (indexPath.section % 2 != 0) : false cell.updateCell(content: currentItem, surface: surface, striped: shouldStrip, padding: padding) return cell @@ -156,7 +156,7 @@ extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableColl //-------------------------------------------------- func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> TableItemModel { - return tableData[indexPath.section][indexPath.row] + return tableData[indexPath.section].columns[indexPath.row] } func collectionView(_ collectionView: UICollectionView, widthForItemAt indexPath: IndexPath) -> CGFloat { diff --git a/VDS/Components/Table/TableCellItem.swift b/VDS/Components/Table/TableCellItem.swift index 1366b0c7..88b6c2d0 100644 --- a/VDS/Components/Table/TableCellItem.swift +++ b/VDS/Components/Table/TableCellItem.swift @@ -65,19 +65,6 @@ final class TableCellItem: UICollectionViewCell { containerView.surface = surface containerView.backgroundColor = striped ? stripedColorConfiguration.getColor(surface) : backgroundColorConfiguration.getColor(surface) - containerView.addSubview(content.component) - - if var surfacedView = content.component as? Surfaceable { - surfacedView.surface = surface - } - - NSLayoutConstraint.activate([ - content.component.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: VDSLayout.space1X), - content.component.topAnchor.constraint(greaterThanOrEqualTo: containerView.topAnchor, constant: padding.verticalValue()), - containerView.bottomAnchor.constraint(greaterThanOrEqualTo: content.component.bottomAnchor, constant: padding.verticalValue()), - containerView.trailingAnchor.constraint(greaterThanOrEqualTo: content.component.trailingAnchor, constant: padding.horizontalValue()), - containerView.centerYAnchor.constraint(equalTo: content.component.centerYAnchor) - ]) containerView.addSubview(separator) separator.pinLeading().pinTrailing().pinBottom() @@ -85,5 +72,21 @@ final class TableCellItem: UICollectionViewCell { separator.style = content.bottomLine ?? .primary separator.isHidden = content.bottomLine == nil separator.surface = surface + + guard let component = content.component else { return } + + containerView.addSubview(component) + + if var surfacedView = component as? Surfaceable { + surfacedView.surface = surface + } + + NSLayoutConstraint.activate([ + component.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: VDSLayout.space1X), + component.topAnchor.constraint(greaterThanOrEqualTo: containerView.topAnchor, constant: padding.verticalValue()), + containerView.bottomAnchor.constraint(greaterThanOrEqualTo: component.bottomAnchor, constant: padding.verticalValue()), + containerView.trailingAnchor.constraint(greaterThanOrEqualTo: component.trailingAnchor, constant: padding.horizontalValue()), + containerView.centerYAnchor.constraint(equalTo: component.centerYAnchor) + ]) } } diff --git a/VDS/Components/Table/TableFlowLayout.swift b/VDS/Components/Table/TableFlowLayout.swift index 0f3e231f..2e447187 100644 --- a/VDS/Components/Table/TableFlowLayout.swift +++ b/VDS/Components/Table/TableFlowLayout.swift @@ -112,7 +112,7 @@ class MatrixFlowLayout : UICollectionViewFlowLayout { let itemWidth = width - layoutPadding.horizontalValue() - defaultLeadingPadding let maxSize = CGSize(width: itemWidth, height: CGFloat.greatestFiniteMagnitude) - let estItemSize = item.component.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) + let estItemSize = item.component?.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) ?? CGSize(width: itemWidth, height: item.defaultHeight) return estItemSize.height + (2 * layoutPadding.verticalValue()) } diff --git a/VDS/Components/Table/TableItemModel.swift b/VDS/Components/Table/TableItemModel.swift new file mode 100644 index 00000000..bd299d1b --- /dev/null +++ b/VDS/Components/Table/TableItemModel.swift @@ -0,0 +1,26 @@ +// +// TableItemModel.swift +// VDS +// +// Created by Nadigadda, Sumanth on 02/05/24. +// + +import Foundation +import UIKit +import VDSTokens + +/// Model that represent the content of each cell of Table component +public struct TableItemModel { + + public let defaultHeight: CGFloat = 50.0 + + public var bottomLine: Line.Style? + + /// Component to be show in the Table cell + public var component: UIView? + + public init(bottomLine: Line.Style? = nil, component: UIView? = nil) { + self.bottomLine = bottomLine + self.component = component + } +} diff --git a/VDS/Components/Table/TableRowModel.swift b/VDS/Components/Table/TableRowModel.swift new file mode 100644 index 00000000..a838438f --- /dev/null +++ b/VDS/Components/Table/TableRowModel.swift @@ -0,0 +1,21 @@ +// +// TableRowModel.swift +// VDS +// +// Created by Sumanth Nadigadda on 27/05/24. +// + +import Foundation + +public struct TableRowModel { + + public var columns: [TableItemModel] + + public var columnsCount: Int { + return columns.count + } + + public init(columns: [TableItemModel]) { + self.columns = columns + } +} From 35d3a7cdf79143e56eddba0eedb8d31e6a41a4b6 Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Mon, 27 May 2024 15:03:29 +0530 Subject: [PATCH 10/11] Renaming file. --- VDS/Components/Table/TableCellModel.swift | 24 ----------------------- 1 file changed, 24 deletions(-) delete mode 100644 VDS/Components/Table/TableCellModel.swift diff --git a/VDS/Components/Table/TableCellModel.swift b/VDS/Components/Table/TableCellModel.swift deleted file mode 100644 index 01f03515..00000000 --- a/VDS/Components/Table/TableCellModel.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// TableCellModel.swift -// VDS -// -// Created by Nadigadda, Sumanth on 02/05/24. -// - -import Foundation -import UIKit -import VDSTokens - -/// Model that represent the content of each cell of Table component -public struct TableItemModel { - - public var bottomLine: Line.Style? - - /// Component to be show in the Table cell - public var component: UIView - - public init(bottomLine: Line.Style? = nil, component: UIView) { - self.bottomLine = bottomLine - self.component = component - } -} From d5b5285ec12b7322eccdc4fe94dcd7da697a6204 Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Mon, 27 May 2024 15:44:31 +0530 Subject: [PATCH 11/11] Adding Table change log file --- VDS.xcodeproj/project.pbxproj | 4 +++ VDS/Components/Table/TableChangeLog.txt | 33 +++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 VDS/Components/Table/TableChangeLog.txt diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 301c60d0..ffdb82c8 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 44A952D92BE384C40009F874 /* TableItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952D82BE384C40009F874 /* TableItemModel.swift */; }; 44A952DD2BE3DA820009F874 /* TableFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */; }; 44BD43B62C04866600644F87 /* TableRowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 44BD43B52C04866600644F87 /* TableRowModel.swift */; }; + 44CCF4952C0493A1005C9C5E /* TableChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 44CCF4942C0493A1005C9C5E /* TableChangeLog.txt */; }; 5F21D7BF28DCEB3D003E7CD6 /* Useable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */; }; 5FC35BE328D51405004EBEAC /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5FC35BE228D51405004EBEAC /* Button.swift */; }; 710607952B91A99500F2863F /* TitleletChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 710607942B91A99500F2863F /* TitleletChangeLog.txt */; }; @@ -255,6 +256,7 @@ 44A952D82BE384C40009F874 /* TableItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableItemModel.swift; sourceTree = ""; }; 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableFlowLayout.swift; sourceTree = ""; }; 44BD43B52C04866600644F87 /* TableRowModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TableRowModel.swift; sourceTree = ""; }; + 44CCF4942C0493A1005C9C5E /* TableChangeLog.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = TableChangeLog.txt; sourceTree = ""; }; 5F21D7BE28DCEB3D003E7CD6 /* Useable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Useable.swift; sourceTree = ""; }; 5FC35BE228D51405004EBEAC /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; 710607942B91A99500F2863F /* TitleletChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = TitleletChangeLog.txt; sourceTree = ""; }; @@ -511,6 +513,7 @@ 44A952DC2BE3DA820009F874 /* TableFlowLayout.swift */, 44BD43B52C04866600644F87 /* TableRowModel.swift */, 44A952D82BE384C40009F874 /* TableItemModel.swift */, + 44CCF4942C0493A1005C9C5E /* TableChangeLog.txt */, ); path = Table; sourceTree = ""; @@ -1176,6 +1179,7 @@ EAEEECA42B1F934600531FC2 /* IconChangeLog.txt in Resources */, 7115BD3C2B84C0C200E0A610 /* TileContainerChangeLog.txt in Resources */, EA3362042891E14D0071C351 /* VerizonNHGeTX-Bold.otf in Resources */, + 44CCF4952C0493A1005C9C5E /* TableChangeLog.txt in Resources */, 71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */, EAEEECA72B1F952000531FC2 /* TabsChangeLog.txt in Resources */, 186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */, diff --git a/VDS/Components/Table/TableChangeLog.txt b/VDS/Components/Table/TableChangeLog.txt new file mode 100644 index 00000000..36f168af --- /dev/null +++ b/VDS/Components/Table/TableChangeLog.txt @@ -0,0 +1,33 @@ +03/31/2022 +---------------- +- Initial Brand 3.0 handoff + +08/02/2022 +---------------- +- Included a VDS Note about the Padding prop name rationale + +08/10/2022 +---------------- +- Updated default and inverted prop to light and dark surface. + +08/29/2022 +---------------- +- Noted that Striped style is set to false as default. Clarified Anatomy description of line elements to say that both are configurable at both group and item level via bottomLine prop. + +09/02/2022 +---------------- +- Added dev note enhancment to fix vertical 1px height jumping + +09/13/2022 +---------------- +- Updated Anatomy element names per decisions made for design/dev docs. + +10/04/2022 +---------------- +- Added dev note to Viewport > Striped > Compact padding to specify that auto-indent also applies to striped tables with default padding. + +12/16/2022 +---------------- +- Updated border color values to use element tokens. +- Removed Line section from first position of Configurations. +- Replaced spacing values with tokens.