From 17c5ab948e59cb9594ca9661790cceed9e26cf9e Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Tue, 30 Apr 2024 16:49:45 +0530 Subject: [PATCH 01/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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. From 9aa026ee9453e4e5f0c1e5f41dde88beba66d0e4 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 29 May 2024 09:40:57 -0500 Subject: [PATCH 12/41] Selector called 2x for the items/group Signed-off-by: Matt Bruce --- VDS/BaseClasses/Selector/SelectorItemBase.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/VDS/BaseClasses/Selector/SelectorItemBase.swift b/VDS/BaseClasses/Selector/SelectorItemBase.swift index 75f832ed..66772ce0 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -161,7 +161,8 @@ open class SelectorItemBase: Control, Errorable, /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() - + + selectorView.isAccessibilityElement = false isAccessibilityElement = true accessibilityTraits = .button addSubview(mainStackView) From ba4c27cdc67e9377b59c8463cb6507a9332f46db Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 29 May 2024 11:29:48 -0500 Subject: [PATCH 13/41] =?UTF-8?q?CXTDT-552834=20=E2=80=93=20Tile=20Contain?= =?UTF-8?q?er=20=E2=80=93=20Accessibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matt Bruce --- .../TileContainer/TileContainer.swift | 17 ++++++--- VDS/Components/Tilelet/Tilelet.swift | 38 +++++++++++-------- VDS/Extensions/UIView+Accessibility.swift | 12 ++++++ VDS/SupportingFiles/ReleaseNotes.txt | 1 + 4 files changed, 47 insertions(+), 21 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 7abac288..a2f20ffe 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -108,7 +108,7 @@ open class TileContainerBase: Control where Padding $0.clipsToBounds = true } - private var containerView = View() + internal var containerView = View() //-------------------------------------------------- // MARK: - Public Properties @@ -342,9 +342,6 @@ open class TileContainerBase: Control where Padding containerView.isAccessibilityElement = onClickSubscriber != nil containerView.accessibilityHint = "Double tap to open." containerView.accessibilityLabel = nil - if let views = accessibilityElements?.compactMap({ $0 as? UIView }), !views.isEmpty { - containerView.setAccessibilityLabel(for: views) - } } open override var accessibilityElements: [Any]? { @@ -358,10 +355,18 @@ open class TileContainerBase: Control where Padding } items.append(containerView) } - items.append(contentsOf: contentView.subviews.filter({ $0.isAccessibilityElement == true })) + + let elements = gatherAccessibilityElements(from: contentView) + let views = elements.compactMap({ $0 as? UIView }) + + //update accessibilityLabel + containerView.setAccessibilityLabel(for: views) + + //append all children that are accessible + items.append(contentsOf: elements) + return items } - set {} } diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index e905560c..9c4f33da 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -415,29 +415,37 @@ open class Tilelet: TileContainerBase { /// Used to update any Accessibility properties. open override var accessibilityElements: [Any]? { - get { - var elements = [Any]() - if let superElements = super.accessibilityElements { - elements.append(contentsOf: superElements) - } + var views = [UIView]() + + // grab the available views in order if badgeModel != nil { - elements.append(badge) + views.append(badge) } + if titleModel != nil || subTitleModel != nil || eyebrowModel != nil { - elements.append(titleLockup) + views.append(titleLockup) } - if descriptiveIconModel != nil { - elements.append(descriptiveIcon) + + containerView.setAccessibilityLabel(for: views) + + // get the views to return + var items = [Any]() + if containerView.isAccessibilityElement { + if !accessibilityTraits.contains(.button) && !accessibilityTraits.contains(.link) { + containerView.accessibilityTraits = .button + } else { + containerView.accessibilityTraits = accessibilityTraits + } + items.append(containerView) } - if directionalIconModel != nil { - elements.append(directionalIcon) - } - return elements + + //append all other accessible views to traverse + items.append(contentsOf: views) + + return items } - set {} - } //-------------------------------------------------- diff --git a/VDS/Extensions/UIView+Accessibility.swift b/VDS/Extensions/UIView+Accessibility.swift index e85b3f74..8710032f 100644 --- a/VDS/Extensions/UIView+Accessibility.swift +++ b/VDS/Extensions/UIView+Accessibility.swift @@ -50,4 +50,16 @@ extension UIView { return isIntersecting } + public func gatherAccessibilityElements(from view: UIView) -> [Any] { + var elements: [Any] = [] + + for subview in view.subviews { + if subview.isAccessibilityElement && subview.isVisibleOnScreen { + elements.append(subview) + } + elements.append(contentsOf: gatherAccessibilityElements(from: subview)) + } + + return elements + } } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 265c65ba..165ab44b 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -7,6 +7,7 @@ - CXTDT-563189 - Dropdown Select Readonly Border color - CXTDT-555854 - Dropdown Select - spacing issues - CXTDT-563194 - Dropdown Select - missing transparentBackground option +- CXTDT-552834 – TileContainer – Voice over is not rendering the information. 1.0.64 ---------------- From 1378d9722ac61b2738e5bff17794db4643fa9b0e Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 29 May 2024 11:51:02 -0500 Subject: [PATCH 14/41] added radiobox to the accessibilitylabel Signed-off-by: Matt Bruce --- VDS/Components/RadioBox/RadioBoxItem.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/VDS/Components/RadioBox/RadioBoxItem.swift b/VDS/Components/RadioBox/RadioBoxItem.swift index f102b7c2..15f7c55a 100644 --- a/VDS/Components/RadioBox/RadioBoxItem.swift +++ b/VDS/Components/RadioBox/RadioBoxItem.swift @@ -243,6 +243,11 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { open override func updateAccessibility() { super.updateAccessibility() setAccessibilityLabel(for: [textLabel, subTextLabel, subTextRightLabel]) + if let currentAccessibilityLabel = accessibilityLabel { + accessibilityLabel = "Radiobox, \(currentAccessibilityLabel)" + } else { + accessibilityLabel = "Radiobox" + } if let accessibilityValueText { accessibilityValue = strikethrough ? "\(strikethroughAccessibilityText), \(accessibilityValueText)" From 1b7480fa3e4d09e1395e39b70a51f98c0934a2c2 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 29 May 2024 11:51:34 -0500 Subject: [PATCH 15/41] updated version Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 8f6878a0..39a21260 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -1513,7 +1513,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 64; + CURRENT_PROJECT_VERSION = 65; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1550,7 +1550,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 64; + CURRENT_PROJECT_VERSION = 65; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; From 756aa17e5d2cbc9ca47b34e110a43ab376547e4b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 29 May 2024 13:09:38 -0500 Subject: [PATCH 16/41] removed breeadcrumb disabled stated, now breadcrumbs are always enabled. Signed-off-by: Matt Bruce --- VDS/Components/Breadcrumbs/BreadcrumbItem.swift | 1 - VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift | 6 +----- VDS/Components/Breadcrumbs/Breadcrumbs.swift | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/VDS/Components/Breadcrumbs/BreadcrumbItem.swift b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift index f12d9646..85352e95 100644 --- a/VDS/Components/Breadcrumbs/BreadcrumbItem.swift +++ b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift @@ -58,7 +58,6 @@ open class BreadcrumbItem: ButtonBase { //-------------------------------------------------- private var textColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal) - $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .disabled) $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected) } diff --git a/VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift b/VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift index 75c97c86..2c73f24f 100644 --- a/VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift +++ b/VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift @@ -12,9 +12,6 @@ extension Breadcrumbs { ///Text that goes in the breadcrumb item public var text: String - - /// Whether the Item can be clicked. - public var enabled: Bool /// The Breadcrumb link to links to its respective page. public var selected: Bool @@ -22,9 +19,8 @@ extension Breadcrumbs { ///Click event when you click on a breadcrumb item public var onClick: ((BreadcrumbItem) -> Void)? - public init(text: String, enabeled: Bool = true, selected: Bool = false, onClick: ((BreadcrumbItem) -> Void)? = nil) { + public init(text: String, selected: Bool = false, onClick: ((BreadcrumbItem) -> Void)? = nil) { self.text = text - self.enabled = enabeled self.selected = selected self.onClick = onClick } diff --git a/VDS/Components/Breadcrumbs/Breadcrumbs.swift b/VDS/Components/Breadcrumbs/Breadcrumbs.swift index 7c88e18a..d3a8111a 100644 --- a/VDS/Components/Breadcrumbs/Breadcrumbs.swift +++ b/VDS/Components/Breadcrumbs/Breadcrumbs.swift @@ -99,7 +99,6 @@ open class Breadcrumbs: View { breadcrumbs = breadcrumbModels.compactMap({ model in let breadcrumbItem = BreadcrumbItem() breadcrumbItem.text = model.text - breadcrumbItem.isEnabled = model.enabled breadcrumbItem.isSelected = model.selected breadcrumbItem.onClick = { [weak self] breadcrumb in guard let self, breadcrumb.isEnabled else { return } From 66a386a3001715f42db4e907f14ccda333621727 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 29 May 2024 15:17:07 -0500 Subject: [PATCH 17/41] refactored for breadcrumbs wordwrap Signed-off-by: Matt Bruce --- .../Breadcrumbs/BreadcrumbCellItem.swift | 74 ++----------------- .../Breadcrumbs/BreadcrumbItem.swift | 68 +++++++++++++---- VDS/Components/Breadcrumbs/Breadcrumbs.swift | 24 +++--- 3 files changed, 71 insertions(+), 95 deletions(-) diff --git a/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift b/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift index 424ebcf3..f9dc98fb 100644 --- a/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift +++ b/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift @@ -13,78 +13,14 @@ final class BreadcrumbCellItem: UICollectionViewCell { ///Identifier for the BreadcrumbCellItem static let identifier: String = String(describing: BreadcrumbCellItem.self) - - //-------------------------------------------------- - // MARK: - Private Properties - //-------------------------------------------------- - internal var stackView: UIStackView = { - return UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.axis = .horizontal - $0.distribution = .fill - $0.alignment = .fill - $0.spacing = VDSLayout.space1X - $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) - $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) - } - }() - - internal var breadCrumbItem: BreadcrumbItem? - - ///separator label - private var separator: Label = Label().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.textAlignment = .left - $0.numberOfLines = 1 - $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) - $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) - $0.text = "/" - } - - private let textColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) - - //-------------------------------------------------- - // MARK: - Initializers - //-------------------------------------------------- - override init(frame: CGRect) { - super.init(frame: frame) - setUp() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - setUp() - } - - ///Configuring the cell with default setup - private func setUp() { - separator.textColorConfiguration = textColorConfiguration.eraseToAnyColorable() - contentView.addSubview(stackView) - stackView.pinToSuperView() - separator.backgroundColor = .clear - } - + ///Updating the breadCrumbItem and UI based on the selected flag along with the surface - func update(surface: Surface, hideSlash: Bool, breadCrumbItem: BreadcrumbItem) { - //update surface - separator.surface = surface - breadCrumbItem.surface = surface + func update(breadCrumbItem: BreadcrumbItem) { + contentView.subviews.forEach{$0.removeFromSuperview()} + contentView.addSubview(breadCrumbItem) + breadCrumbItem.pinToSuperView() breadCrumbItem.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) breadCrumbItem.setContentHuggingPriority(.defaultLow, for: .horizontal) - - //remove previous views - stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - - //add to stack - stackView.addArrangedSubview(separator) - stackView.addArrangedSubview(breadCrumbItem) - stackView.setCustomSpacing(VDSLayout.space1X, after: separator) - - //update separator - separator.textColor = textColorConfiguration.getColor(surface) - separator.isHidden = hideSlash - self.breadCrumbItem = breadCrumbItem - setNeedsLayout() } } diff --git a/VDS/Components/Breadcrumbs/BreadcrumbItem.swift b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift index 85352e95..263a4cba 100644 --- a/VDS/Components/Breadcrumbs/BreadcrumbItem.swift +++ b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift @@ -42,17 +42,23 @@ open class BreadcrumbItem: ButtonBase { textColorConfiguration.getColor(self) } - /// The natural size for the receiving view, considering only properties of the view itself. - open override var intrinsicContentSize: CGSize { - guard let titleLabel else { return super.intrinsicContentSize } - // Calculate the titleLabel's intrinsic content size - let labelSize = titleLabel.sizeThatFits(CGSize(width: self.frame.width, height: CGFloat.greatestFiniteMagnitude)) - // Adjust the size if needed (add any additional padding if your design requires) - let adjustedSize = CGSize(width: labelSize.width + contentEdgeInsets.left + contentEdgeInsets.right, - height: labelSize.height + contentEdgeInsets.top + contentEdgeInsets.bottom) - return adjustedSize + /// Determines if a slash is predended or not. + open var hideSlash: Bool = false { didSet { setNeedsUpdate() } } + + private var slashText = "/ " + + open override var textAttributes: [any LabelAttributeModel]? { + hideSlash + ? nil + : [ColorLabelAttribute(location: 0, + length: 1, + color: surface == .light ? VDSColor.elementsPrimaryOnlight : VDSColor.elementsPrimaryOndark), + TextStyleLabelAttribute(location: 0, + length: 1, + textStyle: .bodySmall) + ] } - + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -68,15 +74,51 @@ open class BreadcrumbItem: ButtonBase { /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() + + titleLabel?.numberOfLines = 0 + titleLabel?.lineBreakMode = .byWordWrapping + contentHorizontalAlignment = .left + isAccessibilityElement = true accessibilityTraits = .link - contentHorizontalAlignment = .leading + } /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { - //always call last so the label is rendered - super.updateView() + + //clear the arrays holding actions + accessibilityCustomActions = [] + if let text, !text.isEmpty { + var updatedText = text + if updatedText.hasPrefix(slashText) && hideSlash { + updatedText = String(updatedText.dropFirst(slashText.count)) + } else if !hideSlash, !updatedText.hasPrefix(slashText) { + updatedText = slashText + updatedText + } + + //create the primary string + let mutableText = NSMutableAttributedString.mutableText(for: updatedText, + textStyle: textStyle, + useScaledFont: useScaledFont, + textColor: textColor, + alignment: titleLabel?.textAlignment ?? .center, + lineBreakMode: titleLabel?.lineBreakMode ?? .byTruncatingTail) + + //apply any attributes + if let attributes = textAttributes { + mutableText.apply(attributes: attributes) + } + + //set the attributed text + setAttributedTitle(mutableText, for: .normal) + setAttributedTitle(mutableText, for: .highlighted) + invalidateIntrinsicContentSize() + } else { + setAttributedTitle(nil, for: .normal) + setAttributedTitle(nil, for: .highlighted) + titleLabel?.text = nil + } } diff --git a/VDS/Components/Breadcrumbs/Breadcrumbs.swift b/VDS/Components/Breadcrumbs/Breadcrumbs.swift index d3a8111a..41940a52 100644 --- a/VDS/Components/Breadcrumbs/Breadcrumbs.swift +++ b/VDS/Components/Breadcrumbs/Breadcrumbs.swift @@ -32,13 +32,6 @@ open class Breadcrumbs: View { } } - /// Current Surface and this is used to pass down to child objects that implement Surfacable - override open var surface: Surface { - didSet { - breadcrumbs.forEach { $0.surface = surface } - } - } - open override var accessibilityElements: [Any]? { get { return [containerView, breadcrumbs] @@ -165,17 +158,22 @@ extension Breadcrumbs: UICollectionViewDelegate, UICollectionViewDataSource, But public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BreadcrumbCellItem.identifier, for: indexPath) as? BreadcrumbCellItem else { return UICollectionViewCell() } - let hideSlash = indexPath.row == 0 - cell.update(surface: surface, hideSlash: hideSlash, breadCrumbItem: breadcrumbs[indexPath.row]) + let breadcrumb = breadcrumbs[indexPath.row] + breadcrumb.hideSlash = breadcrumb == breadcrumbs.first + breadcrumb.surface = surface + cell.update(breadCrumbItem: breadcrumb) return cell } public func collectionView(_ collectionView: UICollectionView, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize { + let breadcrumb = breadcrumbs[indexPath.row] - let intrinsicSize = breadcrumb.intrinsicContentSize - let separatorFullWidth: CGFloat = indexPath.row == 0 ? 0 : VDSLayout.space1X + separatorWidth - let cellwidth = intrinsicSize.width + separatorFullWidth - return .init(width: min(cellwidth, collectionView.frame.width), height: intrinsicSize.height) + breadcrumb.hideSlash = breadcrumb == breadcrumbs.first + + let maxWidth = frame.width + let intrinsicSize = breadcrumb.titleLabel!.sizeThatFits(.init(width: maxWidth, height: CGFloat.greatestFiniteMagnitude)) + let cellwidth = min(maxWidth, intrinsicSize.width) + return .init(width: cellwidth, height: intrinsicSize.height) } public func collectionView(_ collectionView: UICollectionView, buttonBaseAtIndexPath indexPath: IndexPath) -> ButtonBase { From 83d6d3409357fb7c34e2797d795f20b028176b35 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 3 Jun 2024 16:06:05 -0500 Subject: [PATCH 18/41] added script for removing all logging text files with xcconfig. Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 33 ++++++++++++++++++++++++++++ VDS/SupportingFiles/vds-dev.xcconfig | 11 ++++++++++ VDS/SupportingFiles/vds.xcconfig | 11 ++++++++++ 3 files changed, 55 insertions(+) create mode 100644 VDS/SupportingFiles/vds-dev.xcconfig create mode 100644 VDS/SupportingFiles/vds.xcconfig diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 39a21260..98494e61 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -319,6 +319,8 @@ EA6642942BCEBF9500D81DC4 /* TextLinkModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLinkModel.swift; sourceTree = ""; }; EA6F330D2B911E9000BACAB9 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = ""; }; EA78C7952C00CAC200430AD1 /* Groupable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Groupable.swift; sourceTree = ""; }; + EA78C7A12C0E63D200430AD1 /* vds-dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "vds-dev.xcconfig"; sourceTree = ""; }; + EA78C7A22C0E63DD00430AD1 /* vds.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = vds.xcconfig; sourceTree = ""; }; EA81410A2A0E8E3C004F60D2 /* ButtonIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonIcon.swift; sourceTree = ""; }; EA81410F2A127066004F60D2 /* UIColor+VDSColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+VDSColor.swift"; sourceTree = ""; }; EA89200328AECF4B006B9984 /* UITextField+Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextField+Publisher.swift"; sourceTree = ""; }; @@ -770,6 +772,8 @@ EA3361FF2891E14C0071C351 /* Fonts */, EAA5EEB828ECD24B003B3210 /* Icons.xcassets */, EA5F86CB2A1D28B500BC83E4 /* ReleaseNotes.txt */, + EA78C7A12C0E63D200430AD1 /* vds-dev.xcconfig */, + EA78C7A22C0E63DD00430AD1 /* vds.xcconfig */, ); path = SupportingFiles; sourceTree = ""; @@ -1080,6 +1084,7 @@ EA336168288B19200071C351 /* Sources */, EA336169288B19200071C351 /* Frameworks */, EA33616A288B19200071C351 /* Resources */, + EA78C79E2C0E4FFB00430AD1 /* Remove ChangeLog.txt Files */, ); buildRules = ( ); @@ -1194,6 +1199,28 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + EA78C79E2C0E4FFB00430AD1 /* Remove ChangeLog.txt Files */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Remove ChangeLog.txt Files"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\necho \"TARGET_BUILD_DIR: ${TARGET_BUILD_DIR}\"\necho \"UNLOCALIZED_RESOURCES_FOLDER_PATH: ${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\necho \"Build Directory: ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\n\nenv\n\nif [ $VDS_REMOVE_CHANGELOG_FILES -eq 1 ]; then \n echo \"Removing ChangeLog.txt files...\"\n \n # Find all files matching the pattern recursively\n TXT_FILES=$(find \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\" -name \"*ChangeLog.txt\")\n\n # Remove each file \n for FILE in $TXT_FILES; do \n echo \"File: $FILE\"\n rm $FILE\n echo \"Removed $FILE\"\n done\n\n TXT_FILES=$(find \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\" -name \"ReleaseNotes.txt\")\n\n # Remove each file \n for FILE in $TXT_FILES; do \n echo \"File: $FILE\"\n rm $FILE\n echo \"Removed $FILE\"\n done\n\n\nelse \n echo \"Skipping removal of ChangeLog.txt files\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ EA336168288B19200071C351 /* Sources */ = { isa = PBXSourcesBuildPhase; @@ -1389,6 +1416,7 @@ /* Begin XCBuildConfiguration section */ EA33617E288B19210071C351 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = EA78C7A12C0E63D200430AD1 /* vds-dev.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1452,6 +1480,7 @@ }; EA33617F288B19210071C351 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = EA78C7A22C0E63DD00430AD1 /* vds.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -1509,6 +1538,7 @@ }; EA336181288B19210071C351 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = EA78C7A12C0E63D200430AD1 /* vds-dev.xcconfig */; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; @@ -1546,6 +1576,7 @@ }; EA336182288B19210071C351 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = EA78C7A22C0E63DD00430AD1 /* vds.xcconfig */; buildSettings = { BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; @@ -1583,6 +1614,7 @@ }; EA336184288B19210071C351 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = EA78C7A12C0E63D200430AD1 /* vds-dev.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; @@ -1600,6 +1632,7 @@ }; EA336185288B19210071C351 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = EA78C7A22C0E63DD00430AD1 /* vds.xcconfig */; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; diff --git a/VDS/SupportingFiles/vds-dev.xcconfig b/VDS/SupportingFiles/vds-dev.xcconfig new file mode 100644 index 00000000..a7f6cb81 --- /dev/null +++ b/VDS/SupportingFiles/vds-dev.xcconfig @@ -0,0 +1,11 @@ +// +// vds-dev.xcconfig +// VDS +// +// Created by Matt Bruce on 6/3/24. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +#include? "../../../workspaceSettings.xcconfig" diff --git a/VDS/SupportingFiles/vds.xcconfig b/VDS/SupportingFiles/vds.xcconfig new file mode 100644 index 00000000..3080486b --- /dev/null +++ b/VDS/SupportingFiles/vds.xcconfig @@ -0,0 +1,11 @@ +// +// vds.xcconfig +// VDS +// +// Created by Matt Bruce on 6/3/24. +// + +// Configuration settings file format documentation can be found at: +// https://help.apple.com/xcode/#/dev745c5c974 + +#include? "../../../workspaceSettings.xcconfig" From 7c12e53151e79200097371a948d48bc397023f8b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 3 Jun 2024 16:44:04 -0500 Subject: [PATCH 19/41] changed to the opposite Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 54 +---------------------------------- 1 file changed, 1 insertion(+), 53 deletions(-) diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 98494e61..1202daae 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -8,38 +8,28 @@ /* Begin PBXBuildFile section */ 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; }; - 1808BEC02BA456B700129230 /* CarouselScrollbarChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */; }; 1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; }; 1842B1DF2BECE28B0021AFCA /* CalendarDateViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */; }; 1842B1E12BECE7B70021AFCA /* CalendarHeaderReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1E02BECE7B70021AFCA /* CalendarHeaderReusableView.swift */; }; 1842B1E32BECF0A20021AFCA /* CalendarFooterReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1E22BECF0A10021AFCA /* CalendarFooterReusableView.swift */; }; - 18450CF12BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18450CF02BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt */; }; 1855EC662BAABF2A002ACAC2 /* BreadcrumbItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1855EC652BAABF2A002ACAC2 /* BreadcrumbItemModel.swift */; }; - 186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186B2A892B88DA7F001AB71F /* TextAreaChangeLog.txt */; }; 186D13CB2BBA8B1500986B53 /* DropdownSelect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 186D13CA2BBA8B1500986B53 /* DropdownSelect.swift */; }; - 186D13CF2BBC36EF00986B53 /* DropdownSelectChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 186D13CE2BBC36EE00986B53 /* DropdownSelectChangeLog.txt */; }; 18792A902B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18792A8F2B7431F2008C0D29 /* ButtonIconBadgeIndicatorModel.swift */; }; 18A3F12A2BD9298900498E4A /* Calendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A3F1292BD9298900498E4A /* Calendar.swift */; }; 18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A012B96E848006602CC /* Breadcrumbs.swift */; }; 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 */; }; 18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */; }; 18FEA1B52BE0E63600A56439 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */; }; - 18FEA1B92BE1301700A56439 /* CalendarChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18FEA1B82BE1301700A56439 /* CalendarChangeLog.txt */; }; 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 */; }; 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 */; }; - 7115BD3C2B84C0C200E0A610 /* TileContainerChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 7115BD3B2B84C0C200E0A610 /* TileContainerChangeLog.txt */; }; 71ACE89C2BA0451200FB6ADC /* PaginationContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71ACE89B2BA0451200FB6ADC /* PaginationContainer.swift */; }; 71ACE89E2BA1CC1700FB6ADC /* TiletEyebrowModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71ACE89D2BA1CC1700FB6ADC /* TiletEyebrowModel.swift */; }; 71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71B23C2C2B91FA690027F7D9 /* Pagination.swift */; }; - 71B5FCBB2B95A0CA00269BCC /* PaginationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71B5FCBA2B95A0CA00269BCC /* PaginationChangeLog.txt */; }; 71BFA70A2B7F70E6000DCE33 /* DropShadowable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BFA7092B7F70E6000DCE33 /* DropShadowable.swift */; }; - 71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 71C02B372B7BD98F00E93E66 /* NotificationChangeLog.txt */; }; 71FC86DA2B96F44C00700965 /* PaginationButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FC86D92B96F44C00700965 /* PaginationButton.swift */; }; 71FC86DC2B96F4C800700965 /* PaginationCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FC86DB2B96F4C800700965 /* PaginationCellItem.swift */; }; 71FC86DE2B9738B900700965 /* SurfaceConfigurationValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71FC86DD2B9738B900700965 /* SurfaceConfigurationValue.swift */; }; @@ -98,7 +88,6 @@ EA5E3058295105A40082B959 /* Tilelet.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5E3057295105A40082B959 /* Tilelet.swift */; }; EA5E305A29510F8B0082B959 /* EnumSubset.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5E305929510F8B0082B959 /* EnumSubset.swift */; }; EA5F86C82A1BD99100BC83E4 /* TabModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5F86C72A1BD99100BC83E4 /* TabModel.swift */; }; - EA5F86CC2A1D28B500BC83E4 /* ReleaseNotes.txt in Resources */ = {isa = PBXBuildFile; fileRef = EA5F86CB2A1D28B500BC83E4 /* ReleaseNotes.txt */; }; EA5F86D02A1F936100BC83E4 /* TabsContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5F86CF2A1F936100BC83E4 /* TabsContainer.swift */; }; EA6642952BCEBF9500D81DC4 /* TextLinkModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6642942BCEBF9500D81DC4 /* TextLinkModel.swift */; }; EA6F330E2B911E9000BACAB9 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6F330D2B911E9000BACAB9 /* TextView.swift */; }; @@ -159,7 +148,6 @@ EAC58C162BED0E0300BA39FA /* InlineAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C152BED0E0300BA39FA /* InlineAction.swift */; }; EAC58C182BED0E2300BA39FA /* SecurityCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C172BED0E2300BA39FA /* SecurityCode.swift */; }; EAC58C232BF2824200BA39FA /* DatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C222BF2824200BA39FA /* DatePicker.swift */; }; - EAC58C252BF2A7FB00BA39FA /* DatePickerChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */; }; EAC58C272BF4116200BA39FA /* DatePickerCalendarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */; }; EAC58C292BF4118C00BA39FA /* DatePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */; }; EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */; }; @@ -177,20 +165,6 @@ EAD068942A560C13002E3A2D /* LoaderLaunchable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD068932A560C13002E3A2D /* LoaderLaunchable.swift */; }; EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAD8D2C028BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift */; }; EAE785312BA0A438009428EA /* UIImage+Helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAE785302BA0A438009428EA /* UIImage+Helper.swift */; }; - EAEEEC922B1F807300531FC2 /* BadgeChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEEC912B1F807300531FC2 /* BadgeChangeLog.txt */; }; - EAEEEC962B1F893B00531FC2 /* ButtonChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEEC952B1F893B00531FC2 /* ButtonChangeLog.txt */; }; - EAEEEC982B1F8DD100531FC2 /* LineChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEEC972B1F8DD100531FC2 /* LineChangeLog.txt */; }; - EAEEEC9A2B1F8E4400531FC2 /* TextLinkChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEEC992B1F8E4400531FC2 /* TextLinkChangeLog.txt */; }; - EAEEEC9C2B1F8F0700531FC2 /* TextLinkCaretChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEEC9B2B1F8F0700531FC2 /* TextLinkCaretChangeLog.txt */; }; - EAEEEC9E2B1F8F7700531FC2 /* ButtonGroupChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEEC9D2B1F8F7700531FC2 /* ButtonGroupChangeLog.txt */; }; - EAEEECA02B1F908200531FC2 /* BadgeIndicatorChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEEC9F2B1F908200531FC2 /* BadgeIndicatorChangeLog.txt */; }; - EAEEECA22B1F92AD00531FC2 /* LabelChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEECA12B1F92AD00531FC2 /* LabelChangeLog.txt */; }; - EAEEECA42B1F934600531FC2 /* IconChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEECA32B1F934600531FC2 /* IconChangeLog.txt */; }; - EAEEECA72B1F952000531FC2 /* TabsChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEECA62B1F952000531FC2 /* TabsChangeLog.txt */; }; - EAEEECA92B1F969700531FC2 /* TooltipChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEECA82B1F969700531FC2 /* TooltipChangeLog.txt */; }; - EAEEECAB2B1FBF2A00531FC2 /* ToggleChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEECAA2B1FBF2900531FC2 /* ToggleChangeLog.txt */; }; - EAEEECAD2B1FC1A600531FC2 /* TitleLockupChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEECAC2B1FC1A600531FC2 /* TitleLockupChangeLog.txt */; }; - EAEEECAF2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = EAEEECAE2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt */; }; EAF1FE9929D4850E00101452 /* Clickable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9829D4850E00101452 /* Clickable.swift */; }; EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9A29DB1A6000101452 /* Changeable.swift */; }; EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0932899861000B287F5 /* CheckboxItem.swift */; }; @@ -1155,38 +1129,12 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - EAEEECA42B1F934600531FC2 /* IconChangeLog.txt in Resources */, - 7115BD3C2B84C0C200E0A610 /* TileContainerChangeLog.txt in Resources */, EA3362042891E14D0071C351 /* VerizonNHGeTX-Bold.otf in Resources */, - 71C02B382B7BD98F00E93E66 /* NotificationChangeLog.txt in Resources */, - EAEEECA72B1F952000531FC2 /* TabsChangeLog.txt in Resources */, - 186B2A8A2B88DA7F001AB71F /* TextAreaChangeLog.txt in Resources */, - EAEEEC962B1F893B00531FC2 /* ButtonChangeLog.txt in Resources */, - 710607952B91A99500F2863F /* TitleletChangeLog.txt in Resources */, - EA5F86CC2A1D28B500BC83E4 /* ReleaseNotes.txt in Resources */, - EAEEEC982B1F8DD100531FC2 /* LineChangeLog.txt in Resources */, - EAEEECA22B1F92AD00531FC2 /* LabelChangeLog.txt in Resources */, EA3362072891E14D0071C351 /* VerizonNHGeDS-Regular.otf in Resources */, - EAEEEC9A2B1F8E4400531FC2 /* TextLinkChangeLog.txt in Resources */, - 1808BEC02BA456B700129230 /* CarouselScrollbarChangeLog.txt in Resources */, - EAEEECAF2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt in Resources */, - EAEEEC922B1F807300531FC2 /* BadgeChangeLog.txt in Resources */, - EAEEEC9E2B1F8F7700531FC2 /* ButtonGroupChangeLog.txt in Resources */, - 18BDEE822B75316E00452358 /* ButtonIconChangeLog.txt in Resources */, EA3362062891E14D0071C351 /* VerizonNHGeTX-Regular.otf in Resources */, EA3362052891E14D0071C351 /* VerizonNHGeDS-Bold.otf in Resources */, - 18450CF12BA1B19C009FDF2A /* BreadcrumbsChangeLog.txt in Resources */, - EAEEECA02B1F908200531FC2 /* BadgeIndicatorChangeLog.txt in Resources */, EAA5EEB928ECD24B003B3210 /* Icons.xcassets in Resources */, - EAEEECA92B1F969700531FC2 /* TooltipChangeLog.txt in Resources */, - 186D13CF2BBC36EF00986B53 /* DropdownSelectChangeLog.txt in Resources */, - 18FEA1B92BE1301700A56439 /* CalendarChangeLog.txt in Resources */, - EAEEEC9C2B1F8F0700531FC2 /* TextLinkCaretChangeLog.txt in Resources */, EAA5EEE428F5B855003B3210 /* VerizonNHGDS-Light.otf in Resources */, - EAC58C252BF2A7FB00BA39FA /* DatePickerChangeLog.txt in Resources */, - 71B5FCBB2B95A0CA00269BCC /* PaginationChangeLog.txt in Resources */, - EAEEECAD2B1FC1A600531FC2 /* TitleLockupChangeLog.txt in Resources */, - EAEEECAB2B1FBF2A00531FC2 /* ToggleChangeLog.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1217,7 +1165,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\necho \"TARGET_BUILD_DIR: ${TARGET_BUILD_DIR}\"\necho \"UNLOCALIZED_RESOURCES_FOLDER_PATH: ${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\necho \"Build Directory: ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\n\nenv\n\nif [ $VDS_REMOVE_CHANGELOG_FILES -eq 1 ]; then \n echo \"Removing ChangeLog.txt files...\"\n \n # Find all files matching the pattern recursively\n TXT_FILES=$(find \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\" -name \"*ChangeLog.txt\")\n\n # Remove each file \n for FILE in $TXT_FILES; do \n echo \"File: $FILE\"\n rm $FILE\n echo \"Removed $FILE\"\n done\n\n TXT_FILES=$(find \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\" -name \"ReleaseNotes.txt\")\n\n # Remove each file \n for FILE in $TXT_FILES; do \n echo \"File: $FILE\"\n rm $FILE\n echo \"Removed $FILE\"\n done\n\n\nelse \n echo \"Skipping removal of ChangeLog.txt files\"\nfi\n"; + shellScript = "\necho \"TARGET_BUILD_DIR: ${TARGET_BUILD_DIR}\"\necho \"UNLOCALIZED_RESOURCES_FOLDER_PATH: ${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\necho \"Build Directory: ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\n\nif [ $VDS_ADD_CHANGELOG_FILES -eq 1 ]; then \n echo \"Adding ChangeLog.txt files...\"\n \n # Find all files matching the pattern recursively\n TXT_FILES=$(find \"${SRCROOT}\" -name \"*ChangeLog.txt\")\n\n # Add each file \n for FILE in $TXT_FILES; do \n cp \"$FILE\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\n echo \"Added $FILE to ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\n done\n\n TXT_FILES=$(find \"${SRCROOT}\" -name \"ReleaseNotes.txt\")\n\n # Add each file \n for FILE in $TXT_FILES; do \n cp \"$FILE\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\n echo \"Added $FILE to ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}\"\n done\n\nelse \n echo \"Skipping adding of ChangeLog.txt files\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ From 045d64766e021d5aa7e75d7da07af1a0e5e821f6 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 3 Jun 2024 16:47:51 -0500 Subject: [PATCH 20/41] updated name for build phase script to add text files Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 1202daae..69c89077 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -1058,7 +1058,7 @@ EA336168288B19200071C351 /* Sources */, EA336169288B19200071C351 /* Frameworks */, EA33616A288B19200071C351 /* Resources */, - EA78C79E2C0E4FFB00430AD1 /* Remove ChangeLog.txt Files */, + EA78C79E2C0E4FFB00430AD1 /* Add ChangeLog.txt Files */, ); buildRules = ( ); @@ -1148,7 +1148,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - EA78C79E2C0E4FFB00430AD1 /* Remove ChangeLog.txt Files */ = { + EA78C79E2C0E4FFB00430AD1 /* Add ChangeLog.txt Files */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; @@ -1158,7 +1158,7 @@ ); inputPaths = ( ); - name = "Remove ChangeLog.txt Files"; + name = "Add ChangeLog.txt Files"; outputFileListPaths = ( ); outputPaths = ( From a24bbd32a507a4e52712b579b251533b0ca97c56 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 4 Jun 2024 10:24:34 -0500 Subject: [PATCH 21/41] CXTDT-565087 - Input Field - Text - OnDark colors Signed-off-by: Matt Bruce --- VDS/Components/TextFields/EntryFieldBase.swift | 2 +- VDS/Components/TextFields/InputField/InputField.swift | 2 +- VDS/SupportingFiles/ReleaseNotes.txt | 4 ++++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 30df7699..ea023736 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -376,7 +376,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { if isEnabled { let optionColorAttr = ColorLabelAttribute(location: oldText.count + 1, length: 8, - color: VDSColor.elementsSecondaryOnlight) + color: secondaryColorConfiguration.getColor(self)) attributes.append(optionColorAttr) } diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 76cdaa2a..b9b4ab99 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -247,7 +247,7 @@ open class InputField: EntryFieldBase { successLabel.isHidden = false errorLabel.isHidden = true statusIcon.name = .checkmarkAlt - statusIcon.color = VDSColor.paletteBlack + statusIcon.color = iconColorConfiguration.getColor(self) statusIcon.surface = surface statusIcon.isHidden = !isEnabled } else { diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 165ab44b..d0c88cd1 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,3 +1,7 @@ +1.0.66 +---------------- +- CXTDT-565087 - Input Field - Text - OnDark colors + 1.0.65 ---------------- - CXTDT-556996 - RadioboxGroup – Accessibility - Voice over does not render the group position From 772863ff73e5f141aefb85c9727d557401dc3d1d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 4 Jun 2024 12:04:57 -0500 Subject: [PATCH 22/41] CXTDT-565112 - Input Field - Credit Card icons Signed-off-by: Matt Bruce --- .../InputField/FieldTypes/CreditCard.swift | 8 +- .../TextFields/InputField/InputField.swift | 6 +- .../Contents.json | 2 +- .../DCI_Horizontal-2_ondark-2.svg | 62 ++ .../DCI_Horizontal-2_onlight.svg | 819 ------------------ .../generic-inverted.imageset/Contents.json | 2 +- .../generic-inverted.svg | 10 - .../generic_inverted.svg | 20 + .../CreditCard/generic.imageset/generic.svg | 30 +- .../Contents.json | 15 + .../placeholder-Inverted.svg | 19 + .../placeholder.imageset/Contents.json | 15 + .../placeholder.imageset/placeholder.svg | 20 + VDS/SupportingFiles/ReleaseNotes.txt | 1 + 14 files changed, 180 insertions(+), 849 deletions(-) create mode 100644 VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/DCI_Horizontal-2_ondark-2.svg delete mode 100644 VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/DCI_Horizontal-2_onlight.svg delete mode 100644 VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/generic-inverted.svg create mode 100644 VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/generic_inverted.svg create mode 100644 VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-Inverted.imageset/Contents.json create mode 100644 VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-Inverted.imageset/placeholder-Inverted.svg create mode 100644 VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/Contents.json create mode 100644 VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/placeholder.svg diff --git a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift index 43fd6309..bd512b50 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift @@ -19,6 +19,7 @@ extension InputField { case dinersClub case jcb case unionPay + case placeholder public func imageName(surface: Surface) -> String { func getImageName(_ surface: Surface, name: String) -> String { @@ -29,10 +30,11 @@ extension InputField { case .mastercard: return "mastercard" case .amex: return "amex" case .discover: return "discover" - case .dinersClub: return "dinersClub"//getImageName(surface, name: "dinersClub") + case .dinersClub: return getImageName(surface, name: "dinersClub") case .jcb: return "jcb" case .unionPay: return getImageName(surface, name: "unionPay") - default: return getImageName(surface, name: "generic") + case .generic: return getImageName(surface, name: "generic") + default: return getImageName(surface, name: "placeholder") } } @@ -141,7 +143,7 @@ extension InputField { override func textFieldDidBeginEditing(_ inputField: InputField, textField: UITextField) { //reset the textField when you start editing value = nil - inputField.cardType = .generic + inputField.cardType = .placeholder textField.text = "" inputField.validate() updateLeftImage(inputField) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index b9b4ab99..c3daeb9a 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -68,7 +68,11 @@ open class InputField: EntryFieldBase { //-------------------------------------------------- // MARK: - CreditCard/SecurityCode //-------------------------------------------------- - open var cardType: CreditCardType = .generic { didSet { setNeedsUpdate() } } + open var cardType: CreditCardType = .placeholder { + didSet { + setNeedsUpdate() + } + } //-------------------------------------------------- // MARK: - Password //-------------------------------------------------- diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/Contents.json index 6c8a6fee..4f70649b 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "DCI_Horizontal-2_onlight.svg", + "filename" : "DCI_Horizontal-2_ondark-2.svg", "idiom" : "universal" } ], diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/DCI_Horizontal-2_ondark-2.svg b/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/DCI_Horizontal-2_ondark-2.svg new file mode 100644 index 00000000..4d2cd6c6 --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/DCI_Horizontal-2_ondark-2.svg @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/DCI_Horizontal-2_onlight.svg b/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/DCI_Horizontal-2_onlight.svg deleted file mode 100644 index 569a32c0..00000000 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/dinersClub-inverted.imageset/DCI_Horizontal-2_onlight.svg +++ /dev/null @@ -1,819 +0,0 @@ - - - - - - - - - - -]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 -AClkpRPQoVUqp5YqpROIVtpgtzHAAIqX+KBcDSYUVthtoMNtWYU1fSwoyJRZztMGP5QGgkIAPVzT -gPcTYWHFodaALzILR1cY5nJJjAaFyToYy2ehVS5MxMkwOIQHFhQmzBMGWmZSWSw46HWbTz0UKY4N -0Aehb2Bh8rGOz0DbqFVKzKgwYZ4oZowWxiAS0G0YxGnJp5NjVmimRqlyMjSwsGJWGDISAYcixbGV -8aB4joipw3Foal0E4wWAMKopBSsMVigMnJhGAwIdoQYtzEJjDiwgAVeBRcGJoDSqcqAVnqlRxXQb -6sUQUIGgVvjRAaluRJMYMvcsu9fHITe0qqEIVBKOGsni4QsdjOMh4MCS2hujaaiICi4lBrWeIsWx -ZWiDHshCaNnuNPBZb0GCwZATAQqorBKxz54GPhs2mN0rwVLI1IYoZkAjDq2/EDWSJXb+MQBt9Iph -KBuxSxjOFNxnf8kPlOaz3SaRrbrc+CyQu8CMr+9iBiUGeghbVx6CwaCUIEAS9kA4SLwEEblAHohQ -gyMIgrryHg4FD+FhoSIIjSY9RLdVHAoegqDxSRB4hImkskfMNryDbilAKO0KpAAsLbPZQnI3BJdY -pUw5hD7Ew2FXCCwLIwtHg0DJ3WZwFiTE2wA7EZKOJoFqPIbMfWCAuiTDMHIaAb5vfuvHfWPtP8hi -XnvnuH6c7+8f14+3Fa4fc7vbX7W3dtePLf9b2D+vH//9c/24b4w9qO+qtzDfurnsd8bV41873la1 -49uzrZ3n36vOdctyv7Xv/LfmlWPsf924bvwzrhv73v2vGnfc+bfcV41v3xakNf/V/1s31pnrjL/1 -9u6PMa8/Y4591Thjfbu9deP6udz1rhtz/uvPmNeNsfW8brytcre2142tvVn3ujG3u/4btD+2v/4t -3L1ujO/9deNub+WY47Dn67PHtmqcse69YpxvvVhfbnnd1t/aMa8e21stznhb6d0rxlrz+nGuGXNd -P+ex95z7mnHH9XMZY78tKPPdQ+6rxv56fqvGfpW19RXja7Gu3/9fL97X++q3lccW1+59r5vzvlrc -ffb1Yq3zvr9iXDOu+2b/6wbpX3fHXleNN5nFv+qNca4a46yx1rXfj/PGtu66K7/c7r5uvn/l+etc -e+V3Z+5z5Xdb1f4//z3Xfr3+vte+ref9a97vBsq6Y2urv1v4d/6V137x5h33Xvv1e3O8ve+29nsv -/phv/3vl+PbOtef81o73313nyiv//+/KtxWuO/N9q797+1773Va88vtt17xqnnWu/HZ//66b89hW -frfw6mt97vpWfje/ddufceX3+7srv5vs2tx71R/b2u/W2sJvva/6dr8997hqj/P/v+brLdcd8207 -yOe8teYZlG3mvf+a88X6317z1xnbrC+v+ea/bee/d6/xrfpuq6u+2+p6z/XdVd/sd7768g7y94Zc -76qvttriXnXW+tqrdd3X68svr/tyG+vcvc6+7ot9t93fu+u+W1+N719pW/fFfmebf9U339otvvlz -7POu+ub9s/Wd5+6rvjx72c35b47rxqstbG2r/P/91d6Oed3byn6vu628a6517fzzaq+9Fd+s++ad -V3xrX22b7+2V13y5/5xXrjHvmP/LPQhjnLffGO98Nf79Vu6t5hvXe++um8v943qv37hrr7XHFd9f -tf791t33vv1/bX3WdXMbf4xrvluoc90153x/1VzW29qar+f3dssrvpus6mwrvq0NFNqbcbX3a8ux -//X//LPvuv4NBOIY17+F3OtdP2gLM//dV45/x5fv7nn92ypza7W29Vvsq//Y5o9r30D2cp+v7T7r -j/2ufgu597ZXn7mu/lp/cfXb6tfe9e2156sz5rr2DaQ3B9mLe84c5107aAuxzRj/yq3X1t+sscad -145r33/XvoH+xz5vof387tr55xzX3vmuXXPba9fb176F//J8L9ae++xBWmfftzB3nGvPuHrr9eW1 -65t59V5v3X/120r7rr3FPttqr64dhD/uvnLsNa722o6tBvm/tedW417tx1trC7/Vn/fKtxXuuHJ8 -vcW2cvw973VvINDeQmuz5nhfbr3Pvm7QFv7Mf+W349r79v3Xra++uXafdfd1b6Cfuc1bb+Hmu/at -L6+b2/1nzmvXPutf9++1651z5VuYf7/+Z54rz/tu7Wu3H19ducd668q3lf9eV47x9rXf/rfGu3K8 -vb0exLvevfYtxNpzW/f92edd+edaY+995RvIW86zl3G75253t7b2u7W2Ve+ad1s57zj7b7fu+/66 -+97Z28q3VeeX12+/tlpXrj/X2+p3fL+t3/LK7d/Wa135JtP32vyrx5UjQLbWBgo3GYazrW3V98a2 -Yr0/r3dbaWs7z/Xi3jmvOvuqvfbV4u+r/t1vIJ7r3sL8Me/V447xz/V3W6/duf6dQVuI9bW92qs9 -5tf/ardVv9jrjevFu36vd72/3m3V/+6b64o/93nXv3vHVW+r7ce5+m31va2d858r57ju++/fvupN -9vvOevOqPd78+lzxvQiQrdxA4eWZa423xzb3ivX1GPuKs9cVW1xz9jp7X3HWv+LLr635Y41rzndf -bXfHtmZ9O/YYY/9vtjVzu1/e+7U1a7x3/+Hvf/+Kr/0V5w2kcdbc8qy/rTnz/7Xu2W9ta/a+a7s7 -B4Ub/9y79RV/jm2322LLNe7+grzWGH/Pb8a84o5t9ncrtxWu2e/cr645e9x/xzXnbeW/3RVvq96t -rRn7XnG+v+b+L7YWw90apNxA/etv/cX9Z59BHvPb68ebDHN+Na/X1o/DvO/Ft+Zse/5W+9x9xX/f -mvPueeebva05Y/v/9zr7ikE825rzJsvY1lwzv5xv7qvN/vp8f78gzfXdeGtd9xZqv7/Wt/Zq8+5b -c3s/tr7azDH+19abt9X/Xf+O681X285117VzPuvcO9b5buzrzd9+rT23nH9vK9d1c9lm/H+uOOe8 -tbcV501WNeet1nfnXG/2Gf+L8cVV45wr3l133+vl/Na2f8s5xxnXa73dVev8sc9V423Vdf+7Wp59 -37xmfLG//++acd9/74zr/X9rzr3Puev7a+9aX6yz7RZ377nuFWNb8/bX6l8x3mT9Wltz9jn/XTNe -4X151rd/q3/VoM65ztbrurdQ65tx97h+rvvP2O9efe764/q5jfnm/Vu7va/334v7tRVvq40tx9rm -iv/1+ub6Ofb8681r5rn6bWUxt7Vv76+tvGec9ddVX921/7/mTaa7vZb77qvmds/af287x9zristt -1b21v/Kc9eccdHneX1d8t3Bbe33lXN78ft5xvdfajvXNtXMeY3t5rvfefffmH+t9va34esxr5zK3 -mvO9Lc5dV3wx1rn2vbX/lV/9v+cV663cQKCrrb+cV8ztnv/21nu8f+V3A2W+Ocbdaqw9rv1usuut -t5rXy218eeV3Qy1c3spt5b3ePfN9d965e+xzvdvyrTvvFfvN/99Wn1+Nd9YVe935/Xlv/H+9nM/f -3u77zd7vevu9+/9/+cVf//7z7p5rmzHPF9t67wayOOd9sd++3i3U/Pp7ed+dd+7v9tVebuOb9+c9 -X875r5jbmNufrf3V3py73l7/au8m69Zmv/uvmPP+dp453vpuWy9q4bbdatto4e4GABNnNwtnN1S3 -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 -zzBg3UkNHuRBDfK3NKwD - - - diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/Contents.json index 28c8b212..ce49c66f 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/Contents.json +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "generic-inverted.svg", + "filename" : "generic_inverted.svg", "idiom" : "universal" } ], diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/generic-inverted.svg b/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/generic-inverted.svg deleted file mode 100644 index 42904015..00000000 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/generic-inverted.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/generic_inverted.svg b/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/generic_inverted.svg new file mode 100644 index 00000000..d7c2ff67 --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic-inverted.imageset/generic_inverted.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic.imageset/generic.svg b/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic.imageset/generic.svg index 38b925ee..75bb826b 100644 --- a/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic.imageset/generic.svg +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/generic.imageset/generic.svg @@ -1,17 +1,19 @@ - + + + + - - - - - - - - - - - - + + + + + diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-Inverted.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-Inverted.imageset/Contents.json new file mode 100644 index 00000000..1a810559 --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-Inverted.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "placeholder-Inverted.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-Inverted.imageset/placeholder-Inverted.svg b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-Inverted.imageset/placeholder-Inverted.svg new file mode 100644 index 00000000..d90e838d --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder-Inverted.imageset/placeholder-Inverted.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/Contents.json b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/Contents.json new file mode 100644 index 00000000..9b2b69ae --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/Contents.json @@ -0,0 +1,15 @@ +{ + "images" : [ + { + "filename" : "placeholder.svg", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true + } +} diff --git a/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/placeholder.svg b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/placeholder.svg new file mode 100644 index 00000000..7ee2cfc3 --- /dev/null +++ b/VDS/SupportingFiles/Icons.xcassets/CreditCard/placeholder.imageset/placeholder.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index d0c88cd1..99c924d9 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,6 +1,7 @@ 1.0.66 ---------------- - CXTDT-565087 - Input Field - Text - OnDark colors +- CXTDT-565112 - Input Field - Credit Card icons 1.0.65 ---------------- From 0207b70a20d5ecbffa32bc564dd5cb0c83819ca7 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 4 Jun 2024 14:00:32 -0500 Subject: [PATCH 23/41] CXTDT-565117 - Input Field - Overflow not clipped Signed-off-by: Matt Bruce --- .../TextFields/InputField/InputField.swift | 2 +- .../TextFields/InputField/TextField.swift | 53 ++++++++++++++++++- VDS/SupportingFiles/ReleaseNotes.txt | 1 + 3 files changed, 53 insertions(+), 3 deletions(-) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index c3daeb9a..516954cd 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -101,7 +101,7 @@ open class InputField: EntryFieldBase { /// UITextField shown in the InputField. open var textField = TextField().with { $0.translatesAutoresizingMaskIntoConstraints = false - $0.font = TextStyle.bodyLarge.font + $0.textStyle = TextStyle.bodyLarge } /// Color configuration for the textField. diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index 0488f0b2..b054741d 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -8,6 +8,7 @@ import Foundation import UIKit import Combine +import VDSTokens @objc(VDSTextField) open class TextField: UITextField, ViewProtocol, Errorable { @@ -46,6 +47,12 @@ open class TextField: UITextField, ViewProtocol, Errorable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + /// TextStyle used on the titleLabel. + open var textStyle: TextStyle = .defaultStyle { didSet { setNeedsUpdate() } } + + /// Will determine if a scaled font should be used for the titleLabel font. + open var useScaledFont: Bool = false { didSet { setNeedsUpdate() } } + /// Key of whether or not updateView() is called in setNeedsUpdate() open var shouldUpdateView: Bool = true @@ -55,6 +62,22 @@ open class TextField: UITextField, ViewProtocol, Errorable { open var errorText: String? { didSet { setNeedsUpdate() } } + open var lineBreakMode: NSLineBreakMode = .byClipping { didSet { setNeedsUpdate() } } + + open override var isEnabled: Bool { didSet { setNeedsUpdate() } } + + open var textColorConfiguration: AnyColorable = ViewColorConfiguration().with { + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false) + }.eraseToAnyColorable(){ didSet { setNeedsUpdate() }} + + open override var textColor: UIColor? { + get { textColorConfiguration.getColor(self) } + set { } + } + + override public var text: String! { didSet { setNeedsUpdate() } } + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -63,6 +86,8 @@ open class TextField: UITextField, ViewProtocol, Errorable { initialSetupPerformed = true backgroundColor = .clear translatesAutoresizingMaskIntoConstraints = false + setContentCompressionResistancePriority(.defaultLow, for: .horizontal) + clipsToBounds = true setup() setNeedsUpdate() } @@ -84,10 +109,12 @@ open class TextField: UITextField, ViewProtocol, Errorable { @objc func doneButtonAction() { // Resigns the first responder status when 'Done' is tapped - resignFirstResponder() + let _ = resignFirstResponder() } - open func updateView() {} + open func updateView() { + updateLabel() + } open func updateAccessibility() { if let errorText, showError { @@ -139,6 +166,28 @@ open class TextField: UITextField, ViewProtocol, Errorable { } return success } + + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + private func updateLabel() { + + //clear the arrays holding actions + accessibilityCustomActions = [] + if let text, !text.isEmpty { + //create the primary string + let mutableText = NSMutableAttributedString.mutableText(for: text, + textStyle: textStyle, + useScaledFont: useScaledFont, + textColor: textColor!, + alignment: .left, + lineBreakMode: lineBreakMode) + attributedText = mutableText + } else { + attributedText = nil + } + } + } extension UITextField { diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 99c924d9..e39df158 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -2,6 +2,7 @@ ---------------- - CXTDT-565087 - Input Field - Text - OnDark colors - CXTDT-565112 - Input Field - Credit Card icons +- CXTDT-565117 - Input Field - Overflow not clipped 1.0.65 ---------------- From 38310b5ab16cddf6c03f7666ebbd00b364795707 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 5 Jun 2024 08:22:22 -0500 Subject: [PATCH 24/41] made register static Signed-off-by: Matt Bruce --- VDS/Fonts/Font.swift | 2 +- VDS/Fonts/FontProtocol.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VDS/Fonts/Font.swift b/VDS/Fonts/Font.swift index f4ba798d..6ee9300e 100644 --- a/VDS/Fonts/Font.swift +++ b/VDS/Fonts/Font.swift @@ -48,7 +48,7 @@ public enum Font: FontProtocol { /// - size: Size of the font /// - Returns: UIFont for the fontName and Size. public func font(ofSize size: CGFloat) -> UIFont{ - DispatchQueue.once(block: { self.register() }) + DispatchQueue.once(block: { Self.register() }) switch self { case .custom(let font): return font diff --git a/VDS/Fonts/FontProtocol.swift b/VDS/Fonts/FontProtocol.swift index df6ac6a3..786436b4 100644 --- a/VDS/Fonts/FontProtocol.swift +++ b/VDS/Fonts/FontProtocol.swift @@ -18,7 +18,7 @@ public protocol FontProtocol { extension FontProtocol { /// Registers the fonts used in the VDS Framework. - public func register() { + public static func register() { guard let bundle = Bundle(identifier: "com.vzw.vds") else { return } Self.allCases.forEach{ font in if let url = bundle.url(forResource: font.fontName, withExtension: font.fontFileExtension){ @@ -51,7 +51,7 @@ extension FontProtocol { /// - size: Size of the font /// - Returns: UIFont for the fontName and Size. public func font(ofSize size: CGFloat) -> UIFont{ - DispatchQueue.once(block: { self.register() }) + DispatchQueue.once(block: { Self.register() }) guard let found = UIFont(name: self.fontName, size: size) else { return .systemFont(ofSize: size) } return found } From fdd6360f319f3f7593230cf772f6f07c854fc49d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 5 Jun 2024 09:32:32 -0500 Subject: [PATCH 25/41] initial cut Signed-off-by: Matt Bruce --- VDS/Components/Tilelet/Tilelet.swift | 4 +-- .../Tilelet/TileletIconModels.swift | 34 +++++++++++++++---- .../TitleLockup/TitleLockupTextColor.swift | 18 ++++++---- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index 9c4f33da..4d714874 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -540,7 +540,7 @@ open class Tilelet: TileContainerBase { var showIconContainerView = false if let descriptiveIconModel { descriptiveIcon.name = descriptiveIconModel.name - descriptiveIcon.colorConfiguration = descriptiveIconModel.colorConfiguration + descriptiveIcon.color = descriptiveIconModel.iconColor.uiColor ?? VDSColor.paletteBlack descriptiveIcon.size = descriptiveIconModel.size descriptiveIcon.surface = backgroundColorSurface showIconContainerView = true @@ -548,7 +548,7 @@ open class Tilelet: TileContainerBase { if let directionalIconModel { directionalIcon.name = directionalIconModel.iconType.iconName - directionalIcon.colorConfiguration = directionalIconModel.colorConfiguration + directionalIcon.color = directionalIconModel.iconColor.uiColor ?? VDSColor.paletteBlack directionalIcon.size = directionalIconModel.size.value directionalIcon.surface = backgroundColorSurface showIconContainerView = true diff --git a/VDS/Components/Tilelet/TileletIconModels.swift b/VDS/Components/Tilelet/TileletIconModels.swift index 7fc408a3..1ade5c57 100644 --- a/VDS/Components/Tilelet/TileletIconModels.swift +++ b/VDS/Components/Tilelet/TileletIconModels.swift @@ -11,13 +11,33 @@ import VDSTokens extension Tilelet { + public enum IconColor: Equatable { + case token(UIColor.VDSColor) + case custom(UIColor) + + private var reflectedValue: String { String(reflecting: self) } + + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.reflectedValue == rhs.reflectedValue + } + + public var uiColor: UIColor { + switch self { + case .token(let color): + return color + case .custom(let color): + return color + } + } + } + /// Model that represents the options available for the descriptive icon. public struct DescriptiveIcon { /// A representation that will be used to render the icon with corresponding name. public var name: Icon.Name /// Color of the icon. - public var colorConfiguration: SurfaceColorConfiguration + public var iconColor: IconColor? /// Enum for a preset height and width for the icon. public var size: Icon.Size @@ -26,12 +46,12 @@ extension Tilelet { public var accessibleText: String public init(name: Icon.Name = .multipleDocuments, - colorConfiguration: SurfaceColorConfiguration = .init(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark), + iconColor: IconColor? = nil, size: Icon.Size = .medium, accessibleText: String? = nil) { self.name = name - self.colorConfiguration = colorConfiguration + self.iconColor = iconColor self.accessibleText = accessibleText ?? name.rawValue self.size = size } @@ -57,7 +77,7 @@ extension Tilelet { } /// Color of the icon. - public var colorConfiguration: SurfaceColorConfiguration + public var iconColor: IconColor? /// Accessible Text for the Icon public var accessibleText: String @@ -68,13 +88,13 @@ extension Tilelet { /// Enum for a preset height and width for the icon. public var size: IconSize - public init(iconType: IconType = .rightArrow, - colorConfiguration: SurfaceColorConfiguration = .init(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark), + public init(iconType: IconType = .rightArrow, + iconColor: IconColor? = nil, size: IconSize = .medium, accessibleText: String? = nil) { self.iconType = iconType - self.colorConfiguration = colorConfiguration + self.iconColor = iconColor self.accessibleText = accessibleText ?? iconType.iconName.rawValue self.size = size } diff --git a/VDS/Components/TitleLockup/TitleLockupTextColor.swift b/VDS/Components/TitleLockup/TitleLockupTextColor.swift index c0760b6f..56638b0e 100644 --- a/VDS/Components/TitleLockup/TitleLockupTextColor.swift +++ b/VDS/Components/TitleLockup/TitleLockupTextColor.swift @@ -17,7 +17,8 @@ extension TitleLockup { public enum TextColor: Equatable { case primary case secondary - case custom(UIColor, UIColor) + case token(UIColor.VDSColor) + case custom(UIColor) private var reflectedValue: String { String(reflecting: self) } @@ -31,15 +32,18 @@ extension TitleLockup { TitleLockup.textColorPrimaryConfiguration case .secondary: TitleLockup.textColorSecondaryConfiguration - case .custom(let lightColor, let darkColor): - SurfaceColorConfiguration(lightColor, darkColor).eraseToAnyColorable() + case .token(let color): + SurfaceColorConfiguration(color.uiColor, color.uiColor).eraseToAnyColorable() + case .custom(let color): + SurfaceColorConfiguration(color, color).eraseToAnyColorable() } } } public enum TitleTextColor: Equatable { case primary - case custom(UIColor, UIColor) + case token(UIColor.VDSColor) + case custom(UIColor) private var reflectedValue: String { String(reflecting: self) } @@ -51,8 +55,10 @@ extension TitleLockup { switch self { case .primary: TitleLockup.textColorPrimaryConfiguration - case .custom(let lightColor, let darkColor): - SurfaceColorConfiguration(lightColor, darkColor).eraseToAnyColorable() + case .token(let color): + SurfaceColorConfiguration(color.uiColor, color.uiColor).eraseToAnyColorable() + case .custom(let color): + SurfaceColorConfiguration(color, color).eraseToAnyColorable() } } } From 3eda3bd68ec593a2996a4447994fc7577929d4a4 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 5 Jun 2024 13:09:04 -0500 Subject: [PATCH 26/41] fixed titlelet Signed-off-by: Matt Bruce --- VDS/Components/Tilelet/Tilelet.swift | 8 ++++++-- VDS/Components/Tilelet/TileletIconModels.swift | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index 4d714874..c49542b2 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -540,7 +540,9 @@ open class Tilelet: TileContainerBase { var showIconContainerView = false if let descriptiveIconModel { descriptiveIcon.name = descriptiveIconModel.name - descriptiveIcon.color = descriptiveIconModel.iconColor.uiColor ?? VDSColor.paletteBlack + if let color = descriptiveIconModel.iconColor?.uiColor { + descriptiveIcon.color = color + } descriptiveIcon.size = descriptiveIconModel.size descriptiveIcon.surface = backgroundColorSurface showIconContainerView = true @@ -548,7 +550,9 @@ open class Tilelet: TileContainerBase { if let directionalIconModel { directionalIcon.name = directionalIconModel.iconType.iconName - directionalIcon.color = directionalIconModel.iconColor.uiColor ?? VDSColor.paletteBlack + if let color = directionalIconModel.iconColor?.uiColor { + directionalIcon.color = color + } directionalIcon.size = directionalIconModel.size.value directionalIcon.surface = backgroundColorSurface showIconContainerView = true diff --git a/VDS/Components/Tilelet/TileletIconModels.swift b/VDS/Components/Tilelet/TileletIconModels.swift index 1ade5c57..b9b0a181 100644 --- a/VDS/Components/Tilelet/TileletIconModels.swift +++ b/VDS/Components/Tilelet/TileletIconModels.swift @@ -24,7 +24,7 @@ extension Tilelet { public var uiColor: UIColor { switch self { case .token(let color): - return color + return color.uiColor case .custom(let color): return color } From 40a31dfe259d5a282f143cc8ec9158f76391de68 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 5 Jun 2024 15:18:32 -0500 Subject: [PATCH 27/41] =?UTF-8?q?CXTDT-560823=20=E2=80=93=20TextArea=20?= =?UTF-8?q?=E2=80=93=20Accessibility=20Labels/Error/ReadyOnly/Disabled.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 7 +------ .../DropdownSelect/DropdownSelect.swift | 7 +------ VDS/Components/TextFields/EntryFieldBase.swift | 17 +++++++++++++++++ .../TextFields/InputField/InputField.swift | 9 ++------- .../TextFields/TextArea/TextArea.swift | 7 +------ VDS/SupportingFiles/ReleaseNotes.txt | 1 + 6 files changed, 23 insertions(+), 25 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 7a158ff6..7645cce5 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -147,12 +147,7 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov open override func updateAccessibility() { super.updateAccessibility() - let label = "Date Picker, \(isReadOnly ? ", read only" : "")" - if let errorText, showError { - fieldStackView.accessibilityLabel = "\(label) ,error, \(errorText)" - } else { - fieldStackView.accessibilityLabel = label - } + fieldStackView.accessibilityLabel = "Date Picker, \(accessibilityLabelText)" fieldStackView.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open." fieldStackView.accessibilityValue = value } diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index bad62536..e001d130 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -278,12 +278,7 @@ open class DropdownSelect: EntryFieldBase { open override func updateAccessibility() { super.updateAccessibility() - let label = "Dropdown Select, \(isReadOnly ? ", read only" : "")" - if let errorText, showError { - fieldStackView.accessibilityLabel = "\(label) ,error, \(errorText)" - } else { - fieldStackView.accessibilityLabel = label - } + fieldStackView.accessibilityLabel = "Dropdown Select, \(accessibilityLabelText)" fieldStackView.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open." fieldStackView.accessibilityValue = value } diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index ea023736..f56f78a2 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -241,6 +241,23 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open var rules = [AnyRule]() + open var accessibilityLabelText: String { + var accessibilityLabels = [String]() + if let text = titleLabel.text { + accessibilityLabels.append(text) + } + if isReadOnly { + accessibilityLabels.append("read only") + } + if !isEnabled { + accessibilityLabels.append("dimmed") + } + if let errorText, showError { + accessibilityLabels.append("error, \(errorText)") + } + return accessibilityLabels.joined(separator: ", ") + } + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 516954cd..af358dbf 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -225,15 +225,10 @@ open class InputField: EntryFieldBase { textField.isUserInteractionEnabled = isEnabled && !isReadOnly textField.textColor = textFieldTextColorConfiguration.getColor(self) } - + open override func updateAccessibility() { super.updateAccessibility() - let label = "\(isReadOnly ? "read only" : "")" - if let errorText, showError { - textField.accessibilityLabel = "\(label) ,error, \(errorText)" - } else { - textField.accessibilityLabel = label - } + textField.accessibilityLabel = accessibilityLabelText textField.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open." } diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index 870cf6b6..b0d858df 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -195,12 +195,7 @@ open class TextArea: EntryFieldBase { open override func updateAccessibility() { super.updateAccessibility() - let label = "\(isReadOnly ? "read only" : "")" - if let errorText, showError { - textView.accessibilityLabel = "\(label) ,error, \(errorText)" - } else { - textView.accessibilityLabel = label - } + textView.accessibilityLabel = accessibilityLabelText textView.accessibilityHint = isReadOnly || !isEnabled ? "" : "Double tap to open." } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index e39df158..3c1fee48 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -3,6 +3,7 @@ - CXTDT-565087 - Input Field - Text - OnDark colors - CXTDT-565112 - Input Field - Credit Card icons - CXTDT-565117 - Input Field - Overflow not clipped +- CXTDT-560823 – TextArea – Accessibility Labels/Error/ReadyOnly/Disabled 1.0.65 ---------------- From a71085f2b3401579d0802e035584dff2d3f1994f Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 5 Jun 2024 16:16:13 -0500 Subject: [PATCH 28/41] added release notes Signed-off-by: Matt Bruce --- VDS/SupportingFiles/ReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 3c1fee48..2018da35 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -4,6 +4,8 @@ - CXTDT-565112 - Input Field - Credit Card icons - CXTDT-565117 - Input Field - Overflow not clipped - CXTDT-560823 – TextArea – Accessibility Labels/Error/ReadyOnly/Disabled +- CXTDT-553663 - DropdownSelect – Accessibility +- CXTDT-544662 - Breadcrumbs - Text Wrapping 1.0.65 ---------------- From f5380532ff26af9a0c0b24960be986fb243334db Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 5 Jun 2024 17:32:45 -0500 Subject: [PATCH 29/41] fixed bug for Accessibility Elements in Label Signed-off-by: Matt Bruce --- VDS/Components/Label/Label.swift | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 9e4dd18e..7d14a85f 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -314,8 +314,11 @@ open class Label: UILabel, ViewProtocol, UserInfoable { super.text = newValue return } - + + //clear out accessibility + accessibilityElements?.removeAll() accessibilityCustomActions = [] + //create the primary string let mutableText = NSMutableAttributedString.mutableText(for: newValue, textStyle: textStyle, @@ -337,6 +340,10 @@ open class Label: UILabel, ViewProtocol, UserInfoable { return } + //clear out accessibility + accessibilityElements?.removeAll() + accessibilityCustomActions = [] + let mutableText = NSMutableAttributedString(attributedString: newValue) applyAttributes(mutableText) @@ -348,7 +355,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable { private func applyAttributes(_ mutableAttributedString: NSMutableAttributedString) { actions = [] - if let attributes = attributes { + if let attributes { mutableAttributedString.apply(attributes: attributes) } } @@ -359,7 +366,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable { let mutableAttributedString = NSMutableAttributedString(attributedString: attributedText) - if let attributes = attributes { + if let attributes { //loop through the models attributes for attribute in attributes { From 2e46759857e9b93e8f7ec680bd94295675fa70fd Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 6 Jun 2024 12:22:21 -0500 Subject: [PATCH 30/41] fixed credit card issues with formatting for different card types also added an min/max length based on card type. Signed-off-by: Matt Bruce --- .../InputField/FieldTypes/CreditCard.swift | 56 ++++++++++++++----- 1 file changed, 43 insertions(+), 13 deletions(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift index bd512b50..fef4c77a 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift @@ -9,7 +9,20 @@ import Foundation import UIKit extension InputField { - + class CreditCardNumberRule: Rule, Withable { + var cardType: CreditCardType? + var errorMessage: String = "You have exceeded the character limit." + + func isValid(value: String?) -> Bool { + guard let count = value?.count, let min = cardType?.minLength, let max = cardType?.maxLength else { return true } + if min == max { + return count == max + } else { + return count >= min && count <= max + } + } + } + public enum CreditCardType: String, CaseIterable { case generic case visa @@ -38,15 +51,22 @@ extension InputField { } } - var separatorIndices: [Int] { + func separatorIndices(_ length: Int) -> [Int] { + var indices: [Int] = [4, 8, 12] switch self { - case .dinersClub: - return [4, 10] + case .amex, .dinersClub: + indices = [4, 10] + case .unionPay: + if length == 19 { + indices = [5] + } default: - return [4, 8, 12] + break } + + return indices } - + var securityCodeLength: Int { if self == .amex { return 4 @@ -55,9 +75,21 @@ extension InputField { } } + var minLength: Int { + switch self { + case .visa: return 13 + case .amex: return 15 + case .dinersClub: return 14 + default: return 16 + } + } + var maxLength: Int { switch self { + case .visa: return 19 + case .amex: return 15 case .dinersClub: return 14 + case .unionPay: return 19 default: return 16 } } @@ -131,9 +163,8 @@ extension InputField { override func appendRules(_ inputField: InputField) { if let text = inputField.textField.text, text.count > 0 { - let rule = CharacterCountRule().copyWith { - $0.maxLength = inputField.cardType.maxLength - $0.compareType = .equals + let rule = CreditCardNumberRule().copyWith { + $0.cardType = inputField.cardType $0.errorMessage = "Enter a valid credit card." } inputField.rules.append(.init(rule)) @@ -205,8 +236,8 @@ extension InputField { /// Private internal func formatCreditCardNumber(_ cardType: CreditCardType, number: String) -> String { - let formattedInput = number.filter { $0.isNumber } // Remove any existing slashes - return String.format(formattedInput, indices: cardType.separatorIndices, with: " ") + let rawNumber = number.filter { $0.isNumber } // Remove any existing slashes + return String.format(rawNumber, indices: cardType.separatorIndices(rawNumber.count), with: " ") } internal func updateCardTypeIcon(_ inputField: InputField, rawNumber: String) { @@ -224,9 +255,8 @@ extension InputField { guard rawNumber.count == cardType.maxLength else { return formatCreditCardNumber(cardType, number: number) } let lastFourDigits = rawNumber.suffix(4) let maskedSection = String(repeating: "•", count: 12) - let formattedMaskSection = String.format(maskedSection, indices: cardType.separatorIndices, with: " ") + let formattedMaskSection = String.format(maskedSection, indices: cardType.separatorIndices(rawNumber.count), with: " ") return formattedMaskSection + " " + lastFourDigits } } - } From ba0989bb2ba4574d61e19c2b4de45f845229f25c Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 6 Jun 2024 15:54:56 -0500 Subject: [PATCH 31/41] updated credit card rule Signed-off-by: Matt Bruce --- .../TextFields/InputField/FieldTypes/CreditCard.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift index fef4c77a..bc3a289a 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift @@ -9,11 +9,11 @@ import Foundation import UIKit extension InputField { - class CreditCardNumberRule: Rule, Withable { - var cardType: CreditCardType? - var errorMessage: String = "You have exceeded the character limit." + public class CreditCardNumberRule: Rule, Withable { + public var cardType: CreditCardType? + public var errorMessage: String = "You have exceeded the character limit." - func isValid(value: String?) -> Bool { + public func isValid(value: String?) -> Bool { guard let count = value?.count, let min = cardType?.minLength, let max = cardType?.maxLength else { return true } if min == max { return count == max From 8a686314f515312c1c661198be5dcc42f3aa729e Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 6 Jun 2024 16:18:03 -0500 Subject: [PATCH 32/41] added a format label Signed-off-by: Matt Bruce --- .../TextFields/InputField/TextField.swift | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index b054741d..a2980b3b 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -47,6 +47,17 @@ open class TextField: UITextField, ViewProtocol, Errorable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + private var formatLabel = Label().with { + $0.tag = 999 + $0.textColorConfiguration = ViewColorConfiguration().with { + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) + $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: false) + }.eraseToAnyColorable() + } + + /// Format String similar to placeholder + open var formatText: String? + /// TextStyle used on the titleLabel. open var textStyle: TextStyle = .defaultStyle { didSet { setNeedsUpdate() } } @@ -114,6 +125,37 @@ open class TextField: UITextField, ViewProtocol, Errorable { open func updateView() { updateLabel() + updateFormat() + } + + open func updateFormat() { + guard let formatText else { + formatLabel.text = "" + return + } + + if viewWithTag(999) == nil { + addSubview(formatLabel) + formatLabel.pinToSuperView() + } + + var attributes: [any LabelAttributeModel]? + var finalFormatText = formatText + + if let text, !text.isEmpty { + //make the color of the matching text clear + attributes = [ColorLabelAttribute(location: 0, length: text.count, color: .clear)] + + let startIndex = formatText.index(formatText.startIndex, offsetBy: text.count) + if startIndex < formatText.endIndex { + finalFormatText = text + formatText[startIndex...] + } + } + + //set the label + formatLabel.surface = surface + formatLabel.text = finalFormatText + formatLabel.attributes = attributes } open func updateAccessibility() { From f7ded238578468d1b0ea87ae28a5da43bd2fb276 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 6 Jun 2024 16:18:32 -0500 Subject: [PATCH 33/41] CXTDT-565105 - InputField - Date - Typeover text not working. Signed-off-by: Matt Bruce --- .../InputField/FieldTypes/Date.swift | 125 +++++++++++++++++- .../TextFields/InputField/InputField.swift | 11 +- 2 files changed, 125 insertions(+), 11 deletions(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Date.swift b/VDS/Components/TextFields/InputField/FieldTypes/Date.swift index 19656745..17decc96 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Date.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Date.swift @@ -10,6 +10,18 @@ import UIKit extension InputField { + public class DateRule: Rule, Withable { + public var dateFormat: DateFormat? + public var errorMessage: String = "Enter a valid date" + private let dateFormatter = DateFormatter() + + public func isValid(value: String?) -> Bool { + guard let value, let dateFormat, !value.isEmpty else { return true } + dateFormatter.dateFormat = dateFormat.formatString + return dateFormatter.date(from: value) != nil + } + } + public enum DateFormat: String, CaseIterable { case mmyy case mmddyy @@ -46,6 +58,102 @@ extension InputField { case .mmddyyyy: [2,4] } } + + public func isValid(_ date: String) -> Bool { + let allowedCharacters = CharacterSet(charactersIn: "0123456789/") + + // Check if the input contains only allowed characters + if date.rangeOfCharacter(from: allowedCharacters.inverted) != nil || date.isEmpty { + return false + } + + let components = date.split(separator: "/") + + + func isMonth(_ month: String) -> Bool { + switch month.count { + case 1: + guard let month = Int(month), (0...1).contains(month) else { return false } + return true + case 2: + guard let month = Int(month), (1...12).contains(month) else { return false } + return true + default: + return false + } + } + + func isDay(_ day: String) -> Bool { + switch day.count { + case 1: + guard let day = Int(day),(1...3).contains(day) else { return false } + return true + case 2: + guard let day = Int(day), (1...31).contains(day) else { return false } + return true + default: + return false + } + } + + func isYear(_ year: String, max: Int) -> Bool { + guard year.count <= max else { + return false + } + return true + } + + switch self { + case .mmyy: + if components.count > 2 { + return false + } + + // Validate month part + if components.count > 0, let monthPart = components.first { + if !isMonth(String(monthPart)) { + return false + } + } + + // Validate year part + if components.count > 1, let yearPart = components.last { + if !isYear(String(yearPart), max: 2) { + return false + } + } + + case .mmddyy, .mmddyyyy: + if components.count > 3 { + return false + } + + // Validate month part + if components.count > 0, let monthPart = components.first { + if !isMonth(String(monthPart)) { + return false + } + } + + // Validate day part + if components.count > 1 { + let dayPart = components[1] + if !isDay(String(dayPart)) { + return false + } + } + + // Validate year part + if components.count > 2, let yearPart = components.last { + if !isYear(String(yearPart), max: self == .mmddyy ? 2 : 4) { + return false + } + } + } + + return true + } + } class DateHandler: FieldTypeHandler { @@ -58,16 +166,15 @@ extension InputField { override func updateView(_ inputField: InputField) { minWidth = 114.0 - placeholderText = inputField.dateFormat.placeholderText - + //placeholderText = inputField.dateFormat.placeholderText + inputField.textField.formatText = inputField.dateFormat.placeholderText super.updateView(inputField) } override func appendRules(_ inputField: InputField) { if let text = inputField.textField.text, text.count > 0 { - let rule = CharacterCountRule().copyWith { - $0.maxLength = inputField.dateFormat.maxLength - $0.compareType = .equals + let rule = DateRule().copyWith { + $0.dateFormat = inputField.dateFormat $0.errorMessage = "Enter a valid date." } inputField.rules.append(.init(rule)) @@ -86,9 +193,13 @@ extension InputField { if newText.count > inputField.dateFormat.maxLength { return false } - + if newText.count <= inputField.dateFormat.maxLength { - textField.text = String.format(newText, indices: inputField.dateFormat.separatorIndices, with: "/") + let rawNumber = newText.filter { $0.isNumber } + let formatted = String.format(rawNumber, indices: inputField.dateFormat.separatorIndices, with: "/") + if inputField.dateFormat.isValid(formatted) || formatted.isEmpty { + textField.text = formatted + } return false } else { return true diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index af358dbf..a160420a 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -190,9 +190,11 @@ open class InputField: EntryFieldBase { successLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() backgroundColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessBackgroundOnlight, VDSColor.feedbackSuccessBackgroundOndark, forState: .success) - - borderColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessOnlight, VDSColor.feedbackSuccessOndark, forState: .success) + backgroundColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessBackgroundOnlight, VDSColor.feedbackSuccessBackgroundOndark, forState: [.success, .focused]) + borderColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessOnlight, VDSColor.feedbackSuccessOndark, forState: .success) + + textField.textColorConfiguration = textFieldTextColorConfiguration } open override func getFieldContainer() -> UIView { @@ -221,9 +223,9 @@ open class InputField: EntryFieldBase { super.updateView() + textField.surface = surface textField.isEnabled = isEnabled textField.isUserInteractionEnabled = isEnabled && !isReadOnly - textField.textColor = textFieldTextColorConfiguration.getColor(self) } open override func updateAccessibility() { @@ -248,7 +250,7 @@ open class InputField: EntryFieldBase { statusIcon.name = .checkmarkAlt statusIcon.color = iconColorConfiguration.getColor(self) statusIcon.surface = surface - statusIcon.isHidden = !isEnabled + statusIcon.isHidden = !isEnabled || state.contains(.focused) } else { successLabel.isHidden = true } @@ -303,6 +305,7 @@ extension InputField: UITextFieldDelegate { public func textFieldDidBeginEditing(_ textField: UITextField) { fieldType.handler().textFieldDidBeginEditing(self, textField: textField) updateContainerView() + updateErrorLabel() } public func textFieldDidEndEditing(_ textField: UITextField) { From a90cd23f11d96f8e09750b151c51a98ae9187849 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 6 Jun 2024 16:19:55 -0500 Subject: [PATCH 34/41] updated release notes Signed-off-by: Matt Bruce --- VDS/SupportingFiles/ReleaseNotes.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 2018da35..de8c62de 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -6,6 +6,8 @@ - CXTDT-560823 – TextArea – Accessibility Labels/Error/ReadyOnly/Disabled - CXTDT-553663 - DropdownSelect – Accessibility - CXTDT-544662 - Breadcrumbs - Text Wrapping +- CXTDT-565105 - InputField - Date - Typeover text not working +- CXTDT-565115 - InputField - CreditCard - China UnionPay does not allow longer numbers 1.0.65 ---------------- From 2bb1a24d7203a58986868fc9bf4227e24b7373a4 Mon Sep 17 00:00:00 2001 From: vasavk Date: Fri, 7 Jun 2024 14:34:46 +0530 Subject: [PATCH 35/41] Digital ACT-191 CXTDT-568409 defect: Width control missing - CXTDT-568398 - Calendar - Saturday missing - CXTDT-568402 - Calendar - Extra row --- VDS/Components/Calendar/Calendar.swift | 42 ++++++++++++++++++-------- VDS/SupportingFiles/ReleaseNotes.txt | 3 ++ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index b023cf90..3736a52e 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -67,15 +67,20 @@ open class CalendarBase: Control, Changeable { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - internal var containerSize: CGSize { CGSize(width: 328, height: 336) } + internal var containerSize: CGSize { CGSize(width: widthDefault, height: 336) } internal var calendar = Calendar.current private let cellItemSize = CGSize(width: 40, height: 40) private let headerHeight = 88.0 private let footerHeight = 40.0 private let calendarWidth = 304.0 + private let screenThreeSixty = 360.0 + private let widthDefault = 328.0 + private let widthTight = 320.0 - private var heightConstraint: NSLayoutConstraint? + private var collectionViewLeadingConstraint: NSLayoutConstraint? + private var collectionViewHeightConstraint: NSLayoutConstraint? + private var containerWidthConstraint: NSLayoutConstraint? private var containerHeightConstraint: NSLayoutConstraint? private var selectedIndexPath : IndexPath? private var dates: [Date] = [] @@ -133,21 +138,16 @@ open class CalendarBase: Control, Changeable { .pinTop() .pinBottom() .pinLeadingGreaterThanOrEqualTo() - .pinTrailingLessThanOrEqualTo() - .width(containerSize.width) .heightGreaterThanEqualTo(containerSize.height) containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() // Calendar View containerView.addSubview(collectionView) let calendarHeight = containerSize.height - (2 * VDSLayout.space4X) - let spacing = (containerSize.width - calendarWidth) / 2 collectionView .pinTop(VDSLayout.space4X) .pinBottom(VDSLayout.space4X) - .pinLeading(spacing) - .pinTrailing(spacing) .width(calendarWidth) .heightGreaterThanEqualTo(calendarHeight) @@ -191,8 +191,6 @@ open class CalendarBase: Control, Changeable { // MARK: - Private Methods //-------------------------------------------------- func fetchDates(with aDate: Date) { - heightConstraint?.isActive = false - containerHeightConstraint?.isActive = false days.removeAll() dates = aDate.calendarDisplayDays @@ -204,17 +202,35 @@ open class CalendarBase: Control, Changeable { days.append(date.getDay()) } } - + updateViewConstraints() + } + + func updateViewConstraints() { collectionView.reloadData() + // container width && collection view leading + collectionViewLeadingConstraint?.isActive = false + containerWidthConstraint?.isActive = false + var width = containerView.frame.size.width + width = ((width > 0) && (width < screenThreeSixty)) ? ((width > widthTight) && (width < screenThreeSixty)) ? widthTight : containerView.frame.size.width : widthDefault + let spacing = (width - calendarWidth) / 2 + collectionViewLeadingConstraint = collectionView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: spacing) + containerWidthConstraint = containerView.widthAnchor.constraint(equalToConstant: calendarWidth + ( 2 * spacing)) + collectionViewLeadingConstraint?.isActive = true + containerWidthConstraint?.isActive = true + + + // container height && collection view height + collectionViewHeightConstraint?.isActive = false + containerHeightConstraint?.isActive = false var height = collectionView.collectionViewLayout.collectionViewContentSize.height height = height > 0 ? height : containerSize.height - heightConstraint = collectionView.heightAnchor.constraint(equalToConstant: height) containerHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: height + (2 * VDSLayout.space4X)) - heightConstraint?.isActive = true + collectionViewHeightConstraint = collectionView.heightAnchor.constraint(equalToConstant: height) containerHeightConstraint?.isActive = true + collectionViewHeightConstraint?.isActive = true layoutIfNeeded() - } + } } extension CalendarBase: UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout { diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index de8c62de..f45204f1 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -8,6 +8,9 @@ - CXTDT-544662 - Breadcrumbs - Text Wrapping - CXTDT-565105 - InputField - Date - Typeover text not working - CXTDT-565115 - InputField - CreditCard - China UnionPay does not allow longer numbers +- CXTDT-568398 - Calendar - Saturday missing (on smaller screen size devices) +- CXTDT-568402 - Calendar - Extra row (on smaller screen size devices) +- CXTDT-568409 - Calendar - Width control missing 1.0.65 ---------------- From c02ed371210ff9a819ac7f0c6301249c1dc32361 Mon Sep 17 00:00:00 2001 From: vasavk Date: Fri, 7 Jun 2024 17:35:28 +0530 Subject: [PATCH 36/41] Digital ACT-191 CXTDT-568419 defect: Calendar - When hideContainerBorder=true, corner radius disappears --- VDS/Components/Calendar/Calendar.swift | 3 +-- VDS/SupportingFiles/ReleaseNotes.txt | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index 3736a52e..b085de4c 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -165,14 +165,13 @@ open class CalendarBase: Control, Changeable { } containerView.layer.backgroundColor = backgroundColorConfiguration.getColor(self).cgColor + containerView.layer.cornerRadius = VDSFormControls.borderRadius if hideContainerBorder { containerView.layer.borderColor = nil containerView.layer.borderWidth = 0 - containerView.layer.cornerRadius = 0 } else { containerView.layer.borderColor = containerBorderColorConfiguration.getColor(self).cgColor containerView.layer.borderWidth = VDSFormControls.borderWidth - containerView.layer.cornerRadius = VDSFormControls.borderRadius } } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index f45204f1..7ec07834 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -11,6 +11,7 @@ - CXTDT-568398 - Calendar - Saturday missing (on smaller screen size devices) - CXTDT-568402 - Calendar - Extra row (on smaller screen size devices) - CXTDT-568409 - Calendar - Width control missing +- CXTDT-568419 - Calendar - When hideContainerBorder=true, corner radius disappears 1.0.65 ---------------- From a3fe0b72788610a2822990c2a642ce5071865262 Mon Sep 17 00:00:00 2001 From: vasavk Date: Fri, 7 Jun 2024 17:59:29 +0530 Subject: [PATCH 37/41] Digital ACT-191 CXTDT-568413 defect: Calendar - Missing option for Transparent Background --- VDS/Components/Calendar/Calendar.swift | 5 ++--- VDS/SupportingFiles/ReleaseNotes.txt | 1 + 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index b085de4c..04446252 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -120,7 +120,7 @@ open class CalendarBase: Control, Changeable { //-------------------------------------------------- internal var containerBorderColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight , VDSColor.elementsPrimaryOndark) internal var backgroundColorConfiguration = SurfaceColorConfiguration(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark) - + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- @@ -163,8 +163,7 @@ open class CalendarBase: Control, Changeable { displayDate = fallsBetween ? displayDate : minDate fetchDates(with: displayDate) } - - containerView.layer.backgroundColor = backgroundColorConfiguration.getColor(self).cgColor + containerView.backgroundColor = transparentBackground ? .clear : backgroundColorConfiguration.getColor(self) containerView.layer.cornerRadius = VDSFormControls.borderRadius if hideContainerBorder { containerView.layer.borderColor = nil diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 7ec07834..1096dd39 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -12,6 +12,7 @@ - CXTDT-568402 - Calendar - Extra row (on smaller screen size devices) - CXTDT-568409 - Calendar - Width control missing - CXTDT-568419 - Calendar - When hideContainerBorder=true, corner radius disappears +- CXTDT-568413 - Calendar - Missing option for Transparent Background 1.0.65 ---------------- From f11b21a04bf8904f588e6231f248c49d71841750 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 7 Jun 2024 08:05:59 -0500 Subject: [PATCH 38/41] updated release notes Signed-off-by: Matt Bruce --- VDS/SupportingFiles/ReleaseNotes.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index de8c62de..5ccbc332 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,13 +1,13 @@ 1.0.66 ---------------- -- CXTDT-565087 - Input Field - Text - OnDark colors -- CXTDT-565112 - Input Field - Credit Card icons -- CXTDT-565117 - Input Field - Overflow not clipped +- CXTDT-565087 - InputField - Text - OnDark colors +- CXTDT-565112 - InputField - Credit Card icons +- CXTDT-565117 - InputField - Overflow not clipped +- CXTDT-565105 - InputField - Date - Typeover text not working +- CXTDT-565115 - InputField - CreditCard - China UnionPay does not allow longer numbers - CXTDT-560823 – TextArea – Accessibility Labels/Error/ReadyOnly/Disabled - CXTDT-553663 - DropdownSelect – Accessibility - CXTDT-544662 - Breadcrumbs - Text Wrapping -- CXTDT-565105 - InputField - Date - Typeover text not working -- CXTDT-565115 - InputField - CreditCard - China UnionPay does not allow longer numbers 1.0.65 ---------------- From fd4694db8dfd3e1edaaaf32dd0d252dbe1947ae8 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 7 Jun 2024 13:05:33 -0500 Subject: [PATCH 39/41] updated version Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 4 ++-- VDS/SupportingFiles/ReleaseNotes.txt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 0962fe0a..02e8d284 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -1521,7 +1521,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 65; + CURRENT_PROJECT_VERSION = 66; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1559,7 +1559,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 65; + CURRENT_PROJECT_VERSION = 66; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index f0e8e244..615f10df 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,5 +1,6 @@ 1.0.66 ---------------- +- ONEAPP-6325 - Table - Development finished - CXTDT-565087 - InputField - Text - OnDark colors - CXTDT-565112 - InputField - Credit Card icons - CXTDT-565117 - InputField - Overflow not clipped From e42dc244c1d493e607d5def2349457b0d75c80df Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 10 Jun 2024 14:23:55 -0500 Subject: [PATCH 40/41] make the last label a lower priority to anchor the bottom. Signed-off-by: Matt Bruce --- VDS/Components/TitleLockup/TitleLockup.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TitleLockup/TitleLockup.swift b/VDS/Components/TitleLockup/TitleLockup.swift index af52915a..46529b0f 100644 --- a/VDS/Components/TitleLockup/TitleLockup.swift +++ b/VDS/Components/TitleLockup/TitleLockup.swift @@ -385,8 +385,8 @@ open class TitleLockup: View { } //pin the last view to the bottom of this view - previousView?.pinBottom(0) - + previousView?.pinBottom(anchor: bottomAnchor, priority: .defaultLow) + //debugging for borders eyebrowLabel.debugBorder(show: hasDebugBorder, color: .green) titleLabel.debugBorder(show: hasDebugBorder, color: .green) From 9cf0afeda5a341d3e8c23a728e7fc408f209498a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 10 Jun 2024 14:50:21 -0500 Subject: [PATCH 41/41] increased the priority Signed-off-by: Matt Bruce --- VDS/Components/TitleLockup/TitleLockup.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/TitleLockup/TitleLockup.swift b/VDS/Components/TitleLockup/TitleLockup.swift index 46529b0f..a54982c5 100644 --- a/VDS/Components/TitleLockup/TitleLockup.swift +++ b/VDS/Components/TitleLockup/TitleLockup.swift @@ -385,7 +385,7 @@ open class TitleLockup: View { } //pin the last view to the bottom of this view - previousView?.pinBottom(anchor: bottomAnchor, priority: .defaultLow) + previousView?.pinBottom(anchor: bottomAnchor, priority: UILayoutPriority(700)) //debugging for borders eyebrowLabel.debugBorder(show: hasDebugBorder, color: .green)