From 0673a876d1e9f714e0ed4b899f2ff7de986dce86 Mon Sep 17 00:00:00 2001 From: vasavk Date: Wed, 29 May 2024 16:06:51 +0530 Subject: [PATCH 001/117] Digital ACT-191 ONEAPP-7013 story: added new page --- VDS.xcodeproj/project.pbxproj | 16 +++++++++ VDS/Components/Carousel/Carousel.swift | 33 +++++++++++++++++++ VDS/Components/Carousel/CarouselChangeLog.txt | 15 +++++++++ 3 files changed, 64 insertions(+) create mode 100644 VDS/Components/Carousel/Carousel.swift create mode 100644 VDS/Components/Carousel/CarouselChangeLog.txt diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 8f6878a0..f808e999 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -22,6 +22,8 @@ 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 */; }; + 18AE87502C06FDA60075F181 /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18AE874F2C06FDA60075F181 /* Carousel.swift */; }; + 18AE87542C06FE610075F181 /* CarouselChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */; }; 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 */; }; @@ -237,6 +239,8 @@ 18A3F1292BD9298900498E4A /* Calendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Calendar.swift; sourceTree = ""; }; 18A65A012B96E848006602CC /* Breadcrumbs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Breadcrumbs.swift; sourceTree = ""; }; 18A65A032B96F050006602CC /* BreadcrumbItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItem.swift; sourceTree = ""; }; + 18AE874F2C06FDA60075F181 /* Carousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Carousel.swift; sourceTree = ""; }; + 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselChangeLog.txt; sourceTree = ""; }; 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = ""; }; 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = ""; }; 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = ""; }; @@ -493,6 +497,15 @@ path = Breadcrumbs; sourceTree = ""; }; + 18AE874E2C06FD610075F181 /* Carousel */ = { + isa = PBXGroup; + children = ( + 18AE874F2C06FDA60075F181 /* Carousel.swift */, + 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */, + ); + path = Carousel; + sourceTree = ""; + }; 445BA07629C07ABA0036A7C5 /* Notification */ = { isa = PBXGroup; children = ( @@ -649,6 +662,7 @@ 18A65A002B96E7E1006602CC /* Breadcrumbs */, EA0FC2BE2912D18200DF80B4 /* Buttons */, 18A3F1202BD8F5DE00498E4A /* Calendar */, + 18AE874E2C06FD610075F181 /* Carousel */, 1808BEBA2BA41B1D00129230 /* CarouselScrollbar */, EAF7F092289985E200B287F5 /* Checkbox */, EAC58C1F2BF127F000BA39FA /* DatePicker */, @@ -1164,6 +1178,7 @@ EA3362072891E14D0071C351 /* VerizonNHGeDS-Regular.otf in Resources */, EAEEEC9A2B1F8E4400531FC2 /* TextLinkChangeLog.txt in Resources */, 1808BEC02BA456B700129230 /* CarouselScrollbarChangeLog.txt in Resources */, + 18AE87542C06FE610075F181 /* CarouselChangeLog.txt in Resources */, EAEEECAF2B1FC2BA00531FC2 /* ToggleViewChangeLog.txt in Resources */, EAEEEC922B1F807300531FC2 /* BadgeChangeLog.txt in Resources */, EAEEEC9E2B1F8F7700531FC2 /* ButtonGroupChangeLog.txt in Resources */, @@ -1321,6 +1336,7 @@ EA0B18052A9E2D2D00F2D0CD /* SelectorBase.swift in Sources */, EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */, EAF7F0AB289B13FD00B287F5 /* TextStyleLabelAttribute.swift in Sources */, + 18AE87502C06FDA60075F181 /* Carousel.swift in Sources */, EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */, EA81410B2A0E8E3C004F60D2 /* ButtonIcon.swift in Sources */, EA985BE629688F6A00F2FF2E /* TileletBadgeModel.swift in Sources */, diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift new file mode 100644 index 00000000..84bfd340 --- /dev/null +++ b/VDS/Components/Carousel/Carousel.swift @@ -0,0 +1,33 @@ +// +// Carousel.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 29/05/24. +// + +import Foundation +import UIKit +import VDSTokens +import Combine + +/// A carousel is a collection of related content in a row that a customer can navigate through horizontally. +/// Use this component to show content that is supplementary, not essential for task completion. +@objc(VDSCarousel) +open class Carousel: View { + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + required public init() { + super.init(frame: .zero) + } + + public override init(frame: CGRect) { + super.init(frame: .zero) + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + } + +} diff --git a/VDS/Components/Carousel/CarouselChangeLog.txt b/VDS/Components/Carousel/CarouselChangeLog.txt new file mode 100644 index 00000000..a1fd0f4f --- /dev/null +++ b/VDS/Components/Carousel/CarouselChangeLog.txt @@ -0,0 +1,15 @@ +MM/DD/YYYY +---------------- + +06/22/2023 +---------------- +- Initial Beta Release + +10/02/2023 +---------------- +- Removed (Beta) from header. Removed deprecated sections and “New” badge from Kind section. + +11/20/2023 +---------------- +- Updated visuals to reflect new corner radius value - 12px +- Updated focus border corner radius to 14px From 57a0486b3fc81f35c185a15c181df15941122fe7 Mon Sep 17 00:00:00 2001 From: vasavk Date: Tue, 4 Jun 2024 09:30:41 +0530 Subject: [PATCH 002/117] Digital ACT-191 ONEAPP-7016 story: added properties, added Carousel to VDS.md --- VDS.xcodeproj/project.pbxproj | 4 + VDS/Components/Carousel/Carousel.swift | 278 ++++++++++++++++++ .../Carousel/CarouselSlotAlignmentModel.swift | 25 ++ VDS/VDS.docc/VDS.md | 1 + 4 files changed, 308 insertions(+) create mode 100644 VDS/Components/Carousel/CarouselSlotAlignmentModel.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index f808e999..27693abe 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -24,6 +24,7 @@ 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A032B96F050006602CC /* BreadcrumbItem.swift */; }; 18AE87502C06FDA60075F181 /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18AE874F2C06FDA60075F181 /* Carousel.swift */; }; 18AE87542C06FE610075F181 /* CarouselChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */; }; + 18B42AC62C09D197008D6262 /* CarouselSlotAlignmentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.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 */; }; @@ -241,6 +242,7 @@ 18A65A032B96F050006602CC /* BreadcrumbItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbItem.swift; sourceTree = ""; }; 18AE874F2C06FDA60075F181 /* Carousel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Carousel.swift; sourceTree = ""; }; 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselChangeLog.txt; sourceTree = ""; }; + 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselSlotAlignmentModel.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 = ""; }; 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = ""; }; @@ -501,6 +503,7 @@ isa = PBXGroup; children = ( 18AE874F2C06FDA60075F181 /* Carousel.swift */, + 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */, 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */, ); path = Carousel; @@ -1300,6 +1303,7 @@ EA8E40932A82889500934ED3 /* TooltipDialog.swift in Sources */, 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */, EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */, + 18B42AC62C09D197008D6262 /* CarouselSlotAlignmentModel.swift in Sources */, 71B23C2D2B91FA690027F7D9 /* Pagination.swift in Sources */, EA0D1C372A681CCE00E5C127 /* ToggleView.swift in Sources */, EAF7F0B9289C139800B287F5 /* ColorConfiguration.swift in Sources */, diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 84bfd340..a90e4e4b 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -30,4 +30,282 @@ open class Carousel: View { super.init(coder: coder) } + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + /// Aspect-ratio options for tilelet in the carousel. If 'none' is passed, the tilelet will take the height of the tallest item in the carousel. + open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } } + + /// Data used to render tilelets in the carousel. + open var data: Array? = [] { didSet { setNeedsUpdate() } } + + /// Space between each tile. The default value will be 24px in tablet and 12px in mobile. + open var gutter: Gutter? { + get { return _gutter } + set { + if let newValue { + _gutter = newValue + } else { + _gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX + } + setNeedsUpdate() + } + } + + /// The amount of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. + open var layout: Layout? { + get { return _layout } + set { + if let newValue { + _layout = newValue + } else { + _layout = UIDevice.isIPad ? .threeUP : .oneUP + } + setNeedsUpdate() + } + } + + /// A callback when moving the carousel. Returns event object and selectedGroupIndex. + open var onChange: ((Int) -> Void)? { // TO DO: return object and index + get { nil } + set { + onChangeCancellable?.cancel() + if let newValue { + onChangeCancellable = onChangePublisher + .sink { c in + newValue(c) + } + } + } + } + + // TO DO: pagination + /// Config object for pagination. + /// Custom data type for pagination prop in this component. + + /// If provided, will determine the conditions to render the pagination arrows. + open var paginationDisplay: PaginationDisplay? { + get { return _paginationDisplay } + set { + if let newValue { + _paginationDisplay = newValue + } else { + _paginationDisplay = .none + } + setNeedsUpdate() + } + } + + /// If provided, will apply margin to pagination arrows. Can be set to either positive or negative values. + /// The default value will be 12px in tablet and 8px in mobile. + open var paginationInset: CGFloat? { + get { return _paginationInset } + set { + if let newValue { + _paginationInset = newValue + } else { + _paginationInset = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X + } + setNeedsUpdate() + } + } + + /// Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices. + open var peek: Peek? { + get { return _peek } + set { + if let newValue { + _peek = newValue + } else { + _peek = .none + } + setNeedsUpdate() + } + } + + // TO DO: renderItem + /// Render item function. This will pass the data array and expects a react component in return. +// open var renderItem: ([] -> Any)? { // TO DO: return object and index +// get { nil } +// set { +// onScrollCancellable?.cancel() +// if let newValue { +// onScrollCancellable = onScrollPublisher +// .sink { c in +// newValue(c) +// } +// } +// } +// } +// open var renderItem: ([] -> Void)? {} + + /// The initial visible slide's index in the carousel. + open var selectedIndex: Int? { didSet { setNeedsUpdate() } } + + /// If provided, will set the alignment for slot content when the slots has different heights. + open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } } + + /// + open var hidePaginationBorder: Bool = false { didSet { setNeedsUpdate() } } + + /// viewport + /// viewportOverride + /// viewportOverrideObjectType + /// Custom data type for viewportOverride prop in this component. + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + // Sizes are from InVision design specs. + internal var containerSize: CGSize { CGSize(width: 320, height: 44) } + internal var containerView = View().with { + $0.clipsToBounds = true + } + + /// Previous button to show previous slide. + private let previousButton: PaginationButton = .init(type: .previous) + + /// Next button to show next slide. + private let nextButton: PaginationButton = .init(type: .next) + + /// A publisher for when the scrubber position changes. Passes parameters (position). + open var onChangePublisher = PassthroughSubject() + private var onChangeCancellable: AnyCancellable? + + /// A publisher for when the carousel moves. Passes parameters (data). +// open var onScrollPublisher = PassthroughSubject, Never>() +// private var onScrollCancellable: AnyCancellable? + + internal var _layout: Layout = UIDevice.isIPad ? .threeUP : .oneUP + internal var _paginationDisplay: PaginationDisplay = .none + internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X + internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX + internal var _peek: Peek = .none + + private var selectedGroupIndex: Int? { didSet { setNeedsUpdate() } } +// private var minPaginationInset: CGFloat { +// return UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X +// } + + //-------------------------------------------------- + // MARK: - Enums + //-------------------------------------------------- + /// Enum used to describe the aspect ratios used for this component. + public enum AspectRatio: String, CaseIterable { + case ratio1x1 = "1:1" + case ratio3x4 = "3:4" + case ratio4x3 = "4:3" + case ratio2x3 = "2:3" + case ratio3x2 = "3:2" + case ratio9x16 = "9:16" + case ratio16x9 = "16:9" + case ratio1x2 = "1:2" + case ratio2x1 = "2:1" + case none + } + + /// Enum used to describe the number of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. + public enum Layout: String, CaseIterable { + case oneUP = "1UP" + case twoUP = "2UP" + case threeUP = "3UP" + case fourUP = "4UP" + case fiveUP = "5UP" + case sixUP = "6UP" + + var value: Int { + switch self { + case .oneUP: + 1 + case .twoUP: + 2 + case .threeUP: + 3 + case .fourUP: + 4 + case .fiveUP: + 5 + case .sixUP: + 6 + } + } + } + + /// Enum used to describe the number of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. + public enum Gutter: String, CaseIterable { + case twelvePX = "12px" + case twentyFourPX = "24px" + + var value: CGFloat { + switch self { + case .twelvePX: + VDSLayout.space12X + case .twentyFourPX: + VDSLayout.space24X + } + } + } + + /// Enum used to describe the pagination display for this component. + public enum PaginationDisplay: String, CaseIterable { + case onHover, persistent, none + } + + /// Enum used to describe the peek for this component. Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices. + public enum Peek: String, CaseIterable { + case standard, minimum, none + } + + /// Enum used to describe the pagination kind for pagination button icons. + public enum PaginationKind: String, CaseIterable { + case ghost, lowContrast, highContrast + } // TO DO: it should be used as pagination properties, validate desc stmt. and API - passing data is different. + + // TO DO: move to model class + /// Enum used to describe the vertical of slotAlignment. + public enum Vertical: String, CaseIterable { + case top, middle, bottom + } + + /// Enum used to describe the horizontal of slotAlignment. + public enum Horizontal: String, CaseIterable { + case left, center, right + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func initialSetup() { + super.initialSetup() + } + + open override func setup() { + super.setup() + isAccessibilityElement = false + + addSubview(containerView) + containerView + .pinTop() + .pinBottom() + .pinLeadingGreaterThanOrEqualTo() + .pinTrailingLessThanOrEqualTo() + .height(containerSize.height) +// .width(containerSize.width) + + containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + + } + + open override func updateView() { + super.updateView() + } + + open override func reset() { +// for subview in subviews { +// for recognizer in subview.gestureRecognizers ?? [] { +// subview.removeGestureRecognizer(recognizer) +// } +// } + super.reset() + } } diff --git a/VDS/Components/Carousel/CarouselSlotAlignmentModel.swift b/VDS/Components/Carousel/CarouselSlotAlignmentModel.swift new file mode 100644 index 00000000..5a8ec661 --- /dev/null +++ b/VDS/Components/Carousel/CarouselSlotAlignmentModel.swift @@ -0,0 +1,25 @@ +// +// CarouselSlotAlignmentModel.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 31/05/24. +// + +import Foundation + +/// Custom data type for slotAlignment prop for 'Carousel' component. +extension Carousel { + public struct CarouselSlotAlignmentModel { + + /// Text that shown to an indicator for legend + public var vertical: String + + /// Date to an indicator + public var horizontal: String + + public init(vertical: String, horizontal: String) { + self.vertical = vertical + self.horizontal = horizontal + } + } +} diff --git a/VDS/VDS.docc/VDS.md b/VDS/VDS.docc/VDS.md index d145ea0a..6582ce4a 100755 --- a/VDS/VDS.docc/VDS.md +++ b/VDS/VDS.docc/VDS.md @@ -26,6 +26,7 @@ Using the system allows designers and developers to collaborate more easily and - ``ButtonIcon`` - ``ButtonGroup`` - ``CalendarBase`` +- ``Carousel`` - ``CarouselScrollbar`` - ``Checkbox`` - ``CheckboxItem`` From bc35186bf65dcbb4ed9a102a3239528e13b8a987 Mon Sep 17 00:00:00 2001 From: vasavk Date: Sun, 9 Jun 2024 11:02:18 +0530 Subject: [PATCH 003/117] Digital ACT-191 ONEAPP-7013 story: added pagination model --- VDS.xcodeproj/project.pbxproj | 4 + VDS/Components/Carousel/Carousel.swift | 221 +++++++++--------- .../Carousel/CarouselPaginationModel.swift | 40 ++++ 3 files changed, 160 insertions(+), 105 deletions(-) create mode 100644 VDS/Components/Carousel/CarouselPaginationModel.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 27693abe..80abac94 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 18AE87542C06FE610075F181 /* CarouselChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */; }; 18B42AC62C09D197008D6262 /* CarouselSlotAlignmentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */; }; 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; }; + 18B9763F2C11BA4A009271DF /* CarouselPaginationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B9763E2C11BA4A009271DF /* CarouselPaginationModel.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 */; }; @@ -244,6 +245,7 @@ 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselChangeLog.txt; sourceTree = ""; }; 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselSlotAlignmentModel.swift; sourceTree = ""; }; 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownOptionModel.swift; sourceTree = ""; }; + 18B9763E2C11BA4A009271DF /* CarouselPaginationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselPaginationModel.swift; sourceTree = ""; }; 18BDEE812B75316E00452358 /* ButtonIconChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = ButtonIconChangeLog.txt; sourceTree = ""; }; 18FEA1AC2BDD137500A56439 /* CalendarIndicatorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarIndicatorModel.swift; sourceTree = ""; }; 18FEA1B42BE0E63600A56439 /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; @@ -503,6 +505,7 @@ isa = PBXGroup; children = ( 18AE874F2C06FDA60075F181 /* Carousel.swift */, + 18B9763E2C11BA4A009271DF /* CarouselPaginationModel.swift */, 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */, 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */, ); @@ -1293,6 +1296,7 @@ EA5F86D02A1F936100BC83E4 /* TabsContainer.swift in Sources */, EAF7F0B1289B177F00B287F5 /* ColorLabelAttribute.swift in Sources */, EAC9258F2911C9DE00091998 /* EntryFieldBase.swift in Sources */, + 18B9763F2C11BA4A009271DF /* CarouselPaginationModel.swift in Sources */, EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */, EAD068922A560B65002E3A2D /* LoaderViewController.swift in Sources */, 71FC86DA2B96F44C00700965 /* PaginationButton.swift in Sources */, diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index a90e4e4b..b7f045df 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -37,30 +37,22 @@ open class Carousel: View { open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } } /// Data used to render tilelets in the carousel. - open var data: Array? = [] { didSet { setNeedsUpdate() } } + open var data: [Any] = [] { didSet { setNeedsUpdate() } } /// Space between each tile. The default value will be 24px in tablet and 12px in mobile. - open var gutter: Gutter? { + open var gutter: Gutter { get { return _gutter } set { - if let newValue { - _gutter = newValue - } else { - _gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX - } + _gutter = newValue setNeedsUpdate() } } /// The amount of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. - open var layout: Layout? { + open var layout: Layout { get { return _layout } set { - if let newValue { - _layout = newValue - } else { - _layout = UIDevice.isIPad ? .threeUP : .oneUP - } + _layout = newValue setNeedsUpdate() } } @@ -78,114 +70,49 @@ open class Carousel: View { } } } - - // TO DO: pagination + /// Config object for pagination. - /// Custom data type for pagination prop in this component. + open var pagination: CarouselPaginationModel { + get { return _pagination } + set { + _pagination = newValue + setNeedsUpdate() + } + } /// If provided, will determine the conditions to render the pagination arrows. - open var paginationDisplay: PaginationDisplay? { + open var paginationDisplay: PaginationDisplay { get { return _paginationDisplay } set { - if let newValue { - _paginationDisplay = newValue - } else { - _paginationDisplay = .none - } + _paginationDisplay = newValue setNeedsUpdate() } } /// If provided, will apply margin to pagination arrows. Can be set to either positive or negative values. /// The default value will be 12px in tablet and 8px in mobile. - open var paginationInset: CGFloat? { + open var paginationInset: CGFloat { get { return _paginationInset } set { - if let newValue { - _paginationInset = newValue - } else { - _paginationInset = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X - } + _paginationInset = newValue setNeedsUpdate() } } /// Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices. - open var peek: Peek? { + open var peek: Peek { get { return _peek } set { - if let newValue { - _peek = newValue - } else { - _peek = .none - } + _peek = newValue setNeedsUpdate() } } - - // TO DO: renderItem - /// Render item function. This will pass the data array and expects a react component in return. -// open var renderItem: ([] -> Any)? { // TO DO: return object and index -// get { nil } -// set { -// onScrollCancellable?.cancel() -// if let newValue { -// onScrollCancellable = onScrollPublisher -// .sink { c in -// newValue(c) -// } -// } -// } -// } -// open var renderItem: ([] -> Void)? {} /// The initial visible slide's index in the carousel. open var selectedIndex: Int? { didSet { setNeedsUpdate() } } /// If provided, will set the alignment for slot content when the slots has different heights. - open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } } - - /// - open var hidePaginationBorder: Bool = false { didSet { setNeedsUpdate() } } - - /// viewport - /// viewportOverride - /// viewportOverrideObjectType - /// Custom data type for viewportOverride prop in this component. - - //-------------------------------------------------- - // MARK: - Private Properties - //-------------------------------------------------- - // Sizes are from InVision design specs. - internal var containerSize: CGSize { CGSize(width: 320, height: 44) } - internal var containerView = View().with { - $0.clipsToBounds = true - } - - /// Previous button to show previous slide. - private let previousButton: PaginationButton = .init(type: .previous) - - /// Next button to show next slide. - private let nextButton: PaginationButton = .init(type: .next) - - /// A publisher for when the scrubber position changes. Passes parameters (position). - open var onChangePublisher = PassthroughSubject() - private var onChangeCancellable: AnyCancellable? - - /// A publisher for when the carousel moves. Passes parameters (data). -// open var onScrollPublisher = PassthroughSubject, Never>() -// private var onScrollCancellable: AnyCancellable? - - internal var _layout: Layout = UIDevice.isIPad ? .threeUP : .oneUP - internal var _paginationDisplay: PaginationDisplay = .none - internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X - internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX - internal var _peek: Peek = .none - - private var selectedGroupIndex: Int? { didSet { setNeedsUpdate() } } -// private var minPaginationInset: CGFloat { -// return UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X -// } + open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Enums @@ -255,12 +182,7 @@ open class Carousel: View { public enum Peek: String, CaseIterable { case standard, minimum, none } - - /// Enum used to describe the pagination kind for pagination button icons. - public enum PaginationKind: String, CaseIterable { - case ghost, lowContrast, highContrast - } // TO DO: it should be used as pagination properties, validate desc stmt. and API - passing data is different. - + // TO DO: move to model class /// Enum used to describe the vertical of slotAlignment. public enum Vertical: String, CaseIterable { @@ -271,6 +193,61 @@ open class Carousel: View { public enum Horizontal: String, CaseIterable { case left, center, right } + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + // Sizes are from InVision design specs. + internal var containerSize: CGSize { CGSize(width: 320, height: 44) } + + private let contentStackView = UIStackView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + $0.distribution = .fill + $0.spacing = UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X + $0.backgroundColor = .clear + } + + internal var carouselScrollBar = CarouselScrollbar().with { + $0.layout = UIDevice.isIPad ? .threeUP : .oneUP + $0.position = 0 + $0.backgroundColor = .clear + } + + internal var containerView = View().with { + $0.clipsToBounds = true + $0.backgroundColor = .clear + } + + internal var scrollContainerView = View().with { + $0.clipsToBounds = true + $0.backgroundColor = .clear + } + + private var scrollView = UIScrollView().with { + $0.translatesAutoresizingMaskIntoConstraints = false + $0.backgroundColor = .clear + } + + /// A publisher for when the scrubber position changes. Passes parameters (position). + open var onChangePublisher = PassthroughSubject() + private var onChangeCancellable: AnyCancellable? + + internal var _layout: Layout = UIDevice.isIPad ? .threeUP : .oneUP + internal var _pagination: CarouselPaginationModel = .init(kind: .lowContrast, floating: true) + internal var _paginationDisplay: PaginationDisplay = .none + internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X + internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX + internal var _peek: Peek = .none + internal var _numberOfSlides: Int = 1 + + private var selectedGroupIndex: Int? { didSet { setNeedsUpdate() } } + + private var containerStackHeightConstraint: NSLayoutConstraint? + private var containerViewHeightConstraint: NSLayoutConstraint? + + // The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile. + let space = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X //-------------------------------------------------- // MARK: - Lifecycle @@ -282,22 +259,56 @@ open class Carousel: View { open override func setup() { super.setup() isAccessibilityElement = false - + + // add containerView addSubview(containerView) containerView .pinTop() .pinBottom() - .pinLeadingGreaterThanOrEqualTo() - .pinTrailingLessThanOrEqualTo() - .height(containerSize.height) -// .width(containerSize.width) - + .pinLeading() + .pinTrailing() + .heightGreaterThanEqualTo(containerSize.height) containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + + // add content stackview + containerView.addSubview(contentStackView) + + // add scrollview + scrollContainerView.addSubview(scrollView) + + // add pagination button icons + scrollContainerView.addSubview(pagination.previousButton) + pagination.previousButton + .pinLeading(paginationInset) + .pinCenterY() + + scrollContainerView.addSubview(pagination.nextButton) + pagination.nextButton + .pinTrailing(paginationInset) + .pinCenterY() + // add scroll container view & carousel scrollbar + contentStackView.addArrangedSubview(scrollContainerView) + contentStackView.addArrangedSubview(carouselScrollBar) + contentStackView.setCustomSpacing(space, after: scrollContainerView) + contentStackView + .pinTop() + .pinBottom() + .pinLeading() + .pinTrailing() + .heightGreaterThanEqualTo(space+containerSize.height) + contentStackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() } open override func updateView() { + containerViewHeightConstraint?.isActive = false super.updateView() + containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: 200) + containerViewHeightConstraint?.isActive = true + + pagination.previousButton.isHidden = (paginationDisplay == .none) + pagination.nextButton.isHidden = (paginationDisplay == .none) + layoutIfNeeded() } open override func reset() { diff --git a/VDS/Components/Carousel/CarouselPaginationModel.swift b/VDS/Components/Carousel/CarouselPaginationModel.swift new file mode 100644 index 00000000..0abeeded --- /dev/null +++ b/VDS/Components/Carousel/CarouselPaginationModel.swift @@ -0,0 +1,40 @@ +// +// CarouselPaginationModel.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 06/06/24. +// + +import Foundation +import UIKit + +/// Custom data type for pagination prop for 'Carousel' component. +extension Carousel { + public struct CarouselPaginationModel { + + /// Previous button to show previous slide. + public var previousButton = ButtonIcon().with { + $0.kind = .lowContrast + $0.iconName = .leftCaret + $0.iconOffset = .init(x: -2, y: 0) + $0.icon.size = UIDevice.isIPad ? .small : .xsmall + $0.size = UIDevice.isIPad ? .large : .small + } + + /// Next button to show next slide. + public var nextButton = ButtonIcon().with { + $0.kind = .lowContrast + $0.iconName = .rightCaret + $0.iconOffset = .init(x: 2, y: 0) + $0.icon.size = UIDevice.isIPad ? .small : .xsmall + $0.size = UIDevice.isIPad ? .large : .small + } + + public init(kind: ButtonIcon.Kind, floating: Bool) { + previousButton.kind = kind + nextButton.kind = kind + previousButton.floating = floating + nextButton.floating = floating + } + } +} From 0fc8c5c530e8c466b094cce26fe994f9342e36dd Mon Sep 17 00:00:00 2001 From: vasavk Date: Sun, 9 Jun 2024 11:25:39 +0530 Subject: [PATCH 004/117] Digital ACT-191 ONEAPP-7013 story: updating pagination buttons while changing pagination properties --- VDS/Components/Carousel/Carousel.swift | 36 +++++++++++++++---- .../Carousel/CarouselPaginationModel.swift | 28 ++++----------- 2 files changed, 36 insertions(+), 28 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index b7f045df..4007f5fe 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -228,7 +228,25 @@ open class Carousel: View { $0.translatesAutoresizingMaskIntoConstraints = false $0.backgroundColor = .clear } - + + /// Previous button to show previous slide. + private var previousButton = ButtonIcon().with { + $0.kind = .lowContrast + $0.iconName = .leftCaret + $0.iconOffset = .init(x: -2, y: 0) + $0.icon.size = UIDevice.isIPad ? .small : .xsmall + $0.size = UIDevice.isIPad ? .large : .small + } + + /// Next button to show next slide. + private var nextButton = ButtonIcon().with { + $0.kind = .lowContrast + $0.iconName = .rightCaret + $0.iconOffset = .init(x: 2, y: 0) + $0.icon.size = UIDevice.isIPad ? .small : .xsmall + $0.size = UIDevice.isIPad ? .large : .small + } + /// A publisher for when the scrubber position changes. Passes parameters (position). open var onChangePublisher = PassthroughSubject() private var onChangeCancellable: AnyCancellable? @@ -277,13 +295,13 @@ open class Carousel: View { scrollContainerView.addSubview(scrollView) // add pagination button icons - scrollContainerView.addSubview(pagination.previousButton) - pagination.previousButton + scrollContainerView.addSubview(previousButton) + previousButton .pinLeading(paginationInset) .pinCenterY() - scrollContainerView.addSubview(pagination.nextButton) - pagination.nextButton + scrollContainerView.addSubview(nextButton) + nextButton .pinTrailing(paginationInset) .pinCenterY() @@ -306,8 +324,12 @@ open class Carousel: View { containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: 200) containerViewHeightConstraint?.isActive = true - pagination.previousButton.isHidden = (paginationDisplay == .none) - pagination.nextButton.isHidden = (paginationDisplay == .none) + previousButton.isHidden = (paginationDisplay == .none) + nextButton.isHidden = (paginationDisplay == .none) + previousButton.kind = pagination.kind + previousButton.floating = pagination.floating + nextButton.kind = pagination.kind + nextButton.floating = pagination.floating layoutIfNeeded() } diff --git a/VDS/Components/Carousel/CarouselPaginationModel.swift b/VDS/Components/Carousel/CarouselPaginationModel.swift index 0abeeded..69766223 100644 --- a/VDS/Components/Carousel/CarouselPaginationModel.swift +++ b/VDS/Components/Carousel/CarouselPaginationModel.swift @@ -11,30 +11,16 @@ import UIKit /// Custom data type for pagination prop for 'Carousel' component. extension Carousel { public struct CarouselPaginationModel { + + /// Button icon property 'kind' is supported + public var kind: ButtonIcon.Kind - /// Previous button to show previous slide. - public var previousButton = ButtonIcon().with { - $0.kind = .lowContrast - $0.iconName = .leftCaret - $0.iconOffset = .init(x: -2, y: 0) - $0.icon.size = UIDevice.isIPad ? .small : .xsmall - $0.size = UIDevice.isIPad ? .large : .small - } - - /// Next button to show next slide. - public var nextButton = ButtonIcon().with { - $0.kind = .lowContrast - $0.iconName = .rightCaret - $0.iconOffset = .init(x: 2, y: 0) - $0.icon.size = UIDevice.isIPad ? .small : .xsmall - $0.size = UIDevice.isIPad ? .large : .small - } + /// Button icon property 'floating' is supported + public var floating: Bool public init(kind: ButtonIcon.Kind, floating: Bool) { - previousButton.kind = kind - nextButton.kind = kind - previousButton.floating = floating - nextButton.floating = floating + self.kind = kind + self.floating = floating } } } From 35e4cf0301133871a4dad6db21d1a72709ad2046 Mon Sep 17 00:00:00 2001 From: vasavk Date: Tue, 11 Jun 2024 15:16:52 +0530 Subject: [PATCH 005/117] Digital ACT-191 ONEAPP-7013 story: show slides on Scrollview --- VDS/Components/Carousel/Carousel.swift | 276 +++++++++++------- .../Carousel/CarouselPaginationModel.swift | 4 +- .../CarouselScrollbar/CarouselScrollbar.swift | 14 +- 3 files changed, 186 insertions(+), 108 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 4007f5fe..8c234351 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -30,6 +30,92 @@ open class Carousel: View { super.init(coder: coder) } + //-------------------------------------------------- + // MARK: - Enums + //-------------------------------------------------- + /// Enum used to describe the aspect ratios used for this component. + public enum AspectRatio: String, CaseIterable { + case ratio1x1 = "1:1" + case ratio3x4 = "3:4" + case ratio4x3 = "4:3" + case ratio2x3 = "2:3" + case ratio3x2 = "3:2" + case ratio9x16 = "9:16" + case ratio16x9 = "16:9" + case ratio1x2 = "1:2" + case ratio2x1 = "2:1" + case none + } + + /// Enum used to describe the number of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. + public enum Layout: String, CaseIterable { + case oneUP = "1UP" + case twoUP = "2UP" + case threeUP = "3UP" + case fourUP = "4UP" + case fiveUP = "5UP" + case sixUP = "6UP" + + var value: Int { + switch self { + case .oneUP: + 1 + case .twoUP: + 2 + case .threeUP: + 3 + case .fourUP: + 4 + case .fiveUP: + 5 + case .sixUP: + 6 + } + } + } + + /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. + public enum Gutter: String, CaseIterable { + case twelvePX = "12px" + case twentyFourPX = "24px" + + var value: CGFloat { + switch self { + case .twelvePX: + VDSLayout.space3X + case .twentyFourPX: + VDSLayout.space6X + } + } + } + + /// Enum used to describe the pagination display for this component. + public enum PaginationDisplay: String, CaseIterable { + case onHover, persistent, none + } + + /// Enum used to describe the peek for this component. Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices. + public enum Peek: String, CaseIterable { + case standard, minimum, none + } + + // TO DO: move to model class + /// Enum used to describe the vertical of slotAlignment. + public enum Vertical: String, CaseIterable { + case top, middle, bottom + } + + /// Enum used to describe the horizontal of slotAlignment. + public enum Horizontal: String, CaseIterable { + case left, center, right + } + + /// Enum used to describe the width of a fixed value or percentage of parent's width. + public enum Width { + case percentage(CGFloat) + case value(CGFloat) + } + //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- @@ -38,8 +124,30 @@ open class Carousel: View { /// Data used to render tilelets in the carousel. open var data: [Any] = [] { didSet { setNeedsUpdate() } } + + /// If provided, width of slots will be rendered based on this value. If omitted, default widths are rendered. + open var width : Width? { + get { _width } + set { + if let newValue { + switch newValue { + case .percentage(let percentage): + if percentage >= 10 && percentage <= 100.0 { + _width = newValue + } + case .value(let value): + if value > minimumSlotWidth { /*(size.minimumSlotWidth)*/ + _width = newValue + } + } + } else { + _width = nil + } + setNeedsUpdate() + } + } - /// Space between each tile. The default value will be 24px in tablet and 12px in mobile. + /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. open var gutter: Gutter { get { return _gutter } set { @@ -113,87 +221,7 @@ open class Carousel: View { /// If provided, will set the alignment for slot content when the slots has different heights. open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } } - - //-------------------------------------------------- - // MARK: - Enums - //-------------------------------------------------- - /// Enum used to describe the aspect ratios used for this component. - public enum AspectRatio: String, CaseIterable { - case ratio1x1 = "1:1" - case ratio3x4 = "3:4" - case ratio4x3 = "4:3" - case ratio2x3 = "2:3" - case ratio3x2 = "3:2" - case ratio9x16 = "9:16" - case ratio16x9 = "16:9" - case ratio1x2 = "1:2" - case ratio2x1 = "2:1" - case none - } - - /// Enum used to describe the number of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. - public enum Layout: String, CaseIterable { - case oneUP = "1UP" - case twoUP = "2UP" - case threeUP = "3UP" - case fourUP = "4UP" - case fiveUP = "5UP" - case sixUP = "6UP" - - var value: Int { - switch self { - case .oneUP: - 1 - case .twoUP: - 2 - case .threeUP: - 3 - case .fourUP: - 4 - case .fiveUP: - 5 - case .sixUP: - 6 - } - } - } - - /// Enum used to describe the number of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. - public enum Gutter: String, CaseIterable { - case twelvePX = "12px" - case twentyFourPX = "24px" - var value: CGFloat { - switch self { - case .twelvePX: - VDSLayout.space12X - case .twentyFourPX: - VDSLayout.space24X - } - } - } - - /// Enum used to describe the pagination display for this component. - public enum PaginationDisplay: String, CaseIterable { - case onHover, persistent, none - } - - /// Enum used to describe the peek for this component. Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices. - public enum Peek: String, CaseIterable { - case standard, minimum, none - } - - // TO DO: move to model class - /// Enum used to describe the vertical of slotAlignment. - public enum Vertical: String, CaseIterable { - case top, middle, bottom - } - - /// Enum used to describe the horizontal of slotAlignment. - public enum Horizontal: String, CaseIterable { - case left, center, right - } - //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -204,7 +232,7 @@ open class Carousel: View { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical $0.distribution = .fill - $0.spacing = UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X + $0.spacing = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X $0.backgroundColor = .clear } @@ -254,19 +282,21 @@ open class Carousel: View { internal var _layout: Layout = UIDevice.isIPad ? .threeUP : .oneUP internal var _pagination: CarouselPaginationModel = .init(kind: .lowContrast, floating: true) internal var _paginationDisplay: PaginationDisplay = .none - internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space12X : VDSLayout.space8X + internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX internal var _peek: Peek = .none internal var _numberOfSlides: Int = 1 + private var _width: Width? = nil private var selectedGroupIndex: Int? { didSet { setNeedsUpdate() } } - private var containerStackHeightConstraint: NSLayoutConstraint? private var containerViewHeightConstraint: NSLayoutConstraint? // The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile. - let space = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X - + let scrollbarTopSpace = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X + var slotHeight = 100.0 + var peekMinimum = 24.0 + var minimumSlotWidth = 0.0 //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -293,6 +323,7 @@ open class Carousel: View { // add scrollview scrollContainerView.addSubview(scrollView) + scrollView.pinToSuperView() // add pagination button icons scrollContainerView.addSubview(previousButton) @@ -308,37 +339,84 @@ open class Carousel: View { // add scroll container view & carousel scrollbar contentStackView.addArrangedSubview(scrollContainerView) contentStackView.addArrangedSubview(carouselScrollBar) - contentStackView.setCustomSpacing(space, after: scrollContainerView) + contentStackView.setCustomSpacing(scrollbarTopSpace, after: scrollContainerView) contentStackView .pinTop() .pinBottom() .pinLeading() .pinTrailing() - .heightGreaterThanEqualTo(space+containerSize.height) + .heightGreaterThanEqualTo(scrollbarTopSpace + containerSize.height) contentStackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() } open override func updateView() { - containerViewHeightConstraint?.isActive = false super.updateView() - containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: 200) - containerViewHeightConstraint?.isActive = true + containerViewHeightConstraint?.isActive = false + updatePaginationControls() + getSlotWidth() + // perform a loop to iterate each subView + scrollView.subviews.forEach { subView in + // removing subView from its parent view + subView.removeFromSuperview() + } + + // add carousel items + if data.count > 0 { + var xPos = gutter.value + for _ in 0...data.count - 1 { + let carouselSlot = View().with { + $0.clipsToBounds = true + $0.backgroundColor = .lightGray + } + scrollView.addSubview(carouselSlot) + carouselSlot + .pinTop() + .pinBottom() + .pinLeading(xPos) + .width(minimumSlotWidth) + .height(slotHeight) + xPos = xPos + minimumSlotWidth + gutter.value + } + scrollView.heightAnchor.constraint(equalToConstant: slotHeight).isActive = true + scrollView.contentSize = CGSize(width: xPos-minimumSlotWidth, height: slotHeight) + } + let containerHeight = slotHeight + scrollbarTopSpace + containerSize.height + containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: containerHeight) + containerViewHeightConstraint?.isActive = true + layoutIfNeeded() + } + + open override func reset() { + super.reset() + } + + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + func updatePaginationControls() { + containerView.surface = surface previousButton.isHidden = (paginationDisplay == .none) nextButton.isHidden = (paginationDisplay == .none) previousButton.kind = pagination.kind previousButton.floating = pagination.floating nextButton.kind = pagination.kind nextButton.floating = pagination.floating - layoutIfNeeded() + previousButton.surface = surface + nextButton.surface = surface } - open override func reset() { -// for subview in subviews { -// for recognizer in subview.gestureRecognizers ?? [] { -// subview.removeGestureRecognizer(recognizer) -// } -// } - super.reset() + func getSlotWidth() { + let actualWidth = containerView.frame.size.width + minimumSlotWidth = actualWidth - (CGFloat(layout.value) * gutter.value) + switch peek { + case .standard: + minimumSlotWidth = minimumSlotWidth - (3*peekMinimum) + case .minimum: + minimumSlotWidth = minimumSlotWidth - peekMinimum + case .none: + break + } + minimumSlotWidth = minimumSlotWidth / CGFloat(layout.value) } } diff --git a/VDS/Components/Carousel/CarouselPaginationModel.swift b/VDS/Components/Carousel/CarouselPaginationModel.swift index 69766223..ab85d6a4 100644 --- a/VDS/Components/Carousel/CarouselPaginationModel.swift +++ b/VDS/Components/Carousel/CarouselPaginationModel.swift @@ -12,10 +12,10 @@ import UIKit extension Carousel { public struct CarouselPaginationModel { - /// Button icon property 'kind' is supported + /// Pagination supports Button icon property 'kind'. public var kind: ButtonIcon.Kind - /// Button icon property 'floating' is supported + /// Pagination supports Button icon property 'floating'. public var floating: Bool public init(kind: ButtonIcon.Kind, floating: Bool) { diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index 00e810d3..abba5017 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -45,13 +45,13 @@ open class CarouselScrollbar: View { } /// The number of slides that can appear at once in a set in a carousel container. - open var selectedLayout: Layout? { - get { return _selectedLayout } + open var layout: Layout? { + get { return _layout } set { if let newValue { - _selectedLayout = newValue + _layout = newValue } else { - _selectedLayout = .oneUP + _layout = .oneUP } setThumbWidth() scrollThumbToPosition(position) @@ -198,7 +198,7 @@ open class CarouselScrollbar: View { //-------------------------------------------------- // Sizes are from InVision design specs. internal var containerSize: CGSize { CGSize(width: 45, height: 44) } - internal var _selectedLayout: Layout = .oneUP + internal var _layout: Layout = .oneUP internal var _numberOfSlides: Int = 1 internal var totalPositions: Int = 1 internal var _position: Int = 1 @@ -329,7 +329,7 @@ open class CarouselScrollbar: View { // Compute track width and should maintain minimum thumb width if needed private func setThumbWidth() { - let width = (Float(trackViewWidth) / Float(numberOfSlides)) * Float(_selectedLayout.value) + let width = (Float(trackViewWidth) / Float(numberOfSlides)) * Float(_layout.value) computedWidth = (width > Float(trackViewWidth)) ? Float(trackViewWidth) : width thumbWidth = (width <= Float(trackViewWidth) && width > minThumbWidth) ? width : ((width > Float(trackViewWidth)) ? Float(trackViewWidth) : minThumbWidth) thumbView.frame.size.width = CGFloat(thumbWidth) @@ -362,7 +362,7 @@ open class CarouselScrollbar: View { } private func checkPositions() { - totalPositions = Int (ceil (Double(numberOfSlides) / Double(_selectedLayout.value))) + totalPositions = Int (ceil (Double(numberOfSlides) / Double(_layout.value))) } private func scrollThumbToPosition(_ position: Int) { From ba1ffdf39082184ac7ef395c15c943f40d1046e3 Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 13 Jun 2024 13:51:32 +0530 Subject: [PATCH 006/117] Digital ACT-191 ONEAPP-7013 story: added listeners to perform actions --- VDS/Components/Carousel/Carousel.swift | 126 ++++++++++++++++++++++--- 1 file changed, 114 insertions(+), 12 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 8c234351..077fc546 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -91,10 +91,11 @@ open class Carousel: View { /// Enum used to describe the pagination display for this component. public enum PaginationDisplay: String, CaseIterable { - case onHover, persistent, none + case persistent, none } - /// Enum used to describe the peek for this component. Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices. + /// Enum used to describe the peek for this component. + /// This is how much a tile is partially visible. It is measured by the distance between the edge of the tile and the edge of the viewport or carousel container. A peek can appear on the left and/or right edge of the carousel container or viewport, depending on the carousel’s scroll position. public enum Peek: String, CaseIterable { case standard, minimum, none } @@ -157,7 +158,7 @@ open class Carousel: View { } /// The amount of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. - open var layout: Layout { + open var layout: CarouselScrollbar.Layout { get { return _layout } set { _layout = newValue @@ -197,7 +198,7 @@ open class Carousel: View { } } - /// If provided, will apply margin to pagination arrows. Can be set to either positive or negative values. + /// If provided, will apply margin to pagination arrows. Can be set to either positive or negative values. /// The default value will be 12px in tablet and 8px in mobile. open var paginationInset: CGFloat { get { return _paginationInset } @@ -279,7 +280,7 @@ open class Carousel: View { open var onChangePublisher = PassthroughSubject() private var onChangeCancellable: AnyCancellable? - internal var _layout: Layout = UIDevice.isIPad ? .threeUP : .oneUP + internal var _layout: CarouselScrollbar.Layout = UIDevice.isIPad ? .threeUP : .oneUP internal var _pagination: CarouselPaginationModel = .init(kind: .lowContrast, floating: true) internal var _paginationDisplay: PaginationDisplay = .none internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X @@ -294,6 +295,7 @@ open class Carousel: View { // The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile. let scrollbarTopSpace = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X + var slotHeight = 100.0 var peekMinimum = 24.0 var minimumSlotWidth = 0.0 @@ -351,7 +353,19 @@ open class Carousel: View { open override func updateView() { super.updateView() + + carouselScrollBar.numberOfSlides = data.count + carouselScrollBar.position = (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) ? 1 : carouselScrollBar.position + carouselScrollBar.layout = _layout + carouselScrollBar.isHidden = (Int((Float(carouselScrollBar.numberOfSlides)) * Float(carouselScrollBar._layout.value)) <= 1) ? true : false + + // When peek is set to ‘none,’ pagination controls are automatically set to persistent. + if peek == .none { + paginationDisplay = .persistent + } + containerViewHeightConstraint?.isActive = false + containerStackHeightConstraint?.isActive = false updatePaginationControls() getSlotWidth() @@ -363,11 +377,11 @@ open class Carousel: View { // add carousel items if data.count > 0 { - var xPos = gutter.value - for _ in 0...data.count - 1 { + var xPos = 0.0 + for x in 0...data.count - 1 { let carouselSlot = View().with { $0.clipsToBounds = true - $0.backgroundColor = .lightGray + $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) } scrollView.addSubview(carouselSlot) carouselSlot @@ -379,25 +393,74 @@ open class Carousel: View { xPos = xPos + minimumSlotWidth + gutter.value } scrollView.heightAnchor.constraint(equalToConstant: slotHeight).isActive = true - scrollView.contentSize = CGSize(width: xPos-minimumSlotWidth, height: slotHeight) + scrollView.contentSize = CGSize(width: xPos - gutter.value, height: slotHeight) } + let containerHeight = slotHeight + scrollbarTopSpace + containerSize.height - containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: containerHeight) + if carouselScrollBar.isHidden { + containerStackHeightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: slotHeight) + containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: slotHeight) + } else { + containerStackHeightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: containerHeight) + containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: containerHeight) + } containerViewHeightConstraint?.isActive = true + containerStackHeightConstraint?.isActive = true + + addlisteners() layoutIfNeeded() } open override func reset() { + for subview in subviews { + for recognizer in subview.gestureRecognizers ?? [] { + subview.removeGestureRecognizer(recognizer) + } + } super.reset() } //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- + func addlisteners() { + nextButton.onClick = { _ in self.nextButtonClick() } + previousButton.onClick = { _ in self.previousButtonClick() } + + //setup test page to show scrubber id was changed +// carouselScrollBar.onScrubberDrag = { [weak self] scrubberId in +// guard let self else { return } +// updateScrollPosition(position: scrubberId, callbackText:"onScrubberDrag") +// } + + /// will be called when the thumb move forward. + carouselScrollBar.onMoveForward = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onMoveForward") + } + + /// will be called when the thumb move backward. + carouselScrollBar.onMoveBackward = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onMoveBackward") + } + + /// will be called when the thumb touch start. + carouselScrollBar.onThumbTouchStart = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchStart") + } + + /// will be called when the thumb touch end. + carouselScrollBar.onThumbTouchEnd = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchEnd") + } + } + func updatePaginationControls() { containerView.surface = surface - previousButton.isHidden = (paginationDisplay == .none) - nextButton.isHidden = (paginationDisplay == .none) + showPaginationControls() previousButton.kind = pagination.kind previousButton.floating = pagination.floating nextButton.kind = pagination.kind @@ -419,4 +482,43 @@ open class Carousel: View { } minimumSlotWidth = minimumSlotWidth / CGFloat(layout.value) } + + func nextButtonClick() { + carouselScrollBar.position = carouselScrollBar.position+1 + showPaginationControls() + updateScrollPosition(position: carouselScrollBar.position, callbackText:"pageControlClicks") + } + + func previousButtonClick() { + carouselScrollBar.position = carouselScrollBar.position-1 + showPaginationControls() + updateScrollPosition(position: carouselScrollBar.position, callbackText:"pageControlClicks") + } + + func updateScrollPosition(position: Int, callbackText: String) { + if carouselScrollBar.numberOfSlides > 0 { + let contentOffsetWidth = scrollView.contentSize.width + let totalPositions = totalPositions() + let multiplier: Float = (position == 1) ? 0 : Float((position)-1) / Float(totalPositions) + let xPos = (position == totalPositions) ? (contentOffsetWidth - containerView.frame.size.width) : CGFloat(Float(contentOffsetWidth) * multiplier) + carouselScrollBar.scrubberId = position + let yPos = scrollView.contentOffset.y + scrollView.setContentOffset(CGPoint(x: xPos, y: yPos), animated: true) + showPaginationControls() + } + } + + private func totalPositions() -> Int { + return Int (ceil (Double(carouselScrollBar.numberOfSlides) / Double(_layout.value))) + } + + func showPaginationControls() { + if carouselScrollBar.numberOfSlides == _layout.value { + previousButton.isHidden = true + nextButton.isHidden = true + } else { + previousButton.isHidden = (carouselScrollBar.position == 1) || (paginationDisplay == .none) + nextButton.isHidden = (carouselScrollBar.position == totalPositions()) || (paginationDisplay == .none) + } + } } From de8a2ebdeeed4ca1ce8cbb3b4841505cb993e7f5 Mon Sep 17 00:00:00 2001 From: vasavk Date: Thu, 13 Jun 2024 14:13:27 +0530 Subject: [PATCH 007/117] Digital ACT-191 ONEAPP-7013 story: Using custom container size and custom icon sizes for positioning controls --- VDS/Components/Carousel/Carousel.swift | 41 +++++--------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 077fc546..95abb990 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -47,33 +47,6 @@ open class Carousel: View { case none } - /// Enum used to describe the number of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. - public enum Layout: String, CaseIterable { - case oneUP = "1UP" - case twoUP = "2UP" - case threeUP = "3UP" - case fourUP = "4UP" - case fiveUP = "5UP" - case sixUP = "6UP" - - var value: Int { - switch self { - case .oneUP: - 1 - case .twoUP: - 2 - case .threeUP: - 3 - case .fourUP: - 4 - case .fiveUP: - 5 - case .sixUP: - 6 - } - } - } - /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. public enum Gutter: String, CaseIterable { case twelvePX = "12px" @@ -263,8 +236,8 @@ open class Carousel: View { $0.kind = .lowContrast $0.iconName = .leftCaret $0.iconOffset = .init(x: -2, y: 0) - $0.icon.size = UIDevice.isIPad ? .small : .xsmall - $0.size = UIDevice.isIPad ? .large : .small + $0.customContainerSize = UIDevice.isIPad ? 40 : 28 + $0.icon.customSize = UIDevice.isIPad ? 16 : 12 } /// Next button to show next slide. @@ -272,8 +245,8 @@ open class Carousel: View { $0.kind = .lowContrast $0.iconName = .rightCaret $0.iconOffset = .init(x: 2, y: 0) - $0.icon.size = UIDevice.isIPad ? .small : .xsmall - $0.size = UIDevice.isIPad ? .large : .small + $0.customContainerSize = UIDevice.isIPad ? 40 : 28 + $0.icon.customSize = UIDevice.isIPad ? 16 : 12 } /// A publisher for when the scrubber position changes. Passes parameters (position). @@ -355,10 +328,10 @@ open class Carousel: View { super.updateView() carouselScrollBar.numberOfSlides = data.count - carouselScrollBar.position = (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) ? 1 : carouselScrollBar.position carouselScrollBar.layout = _layout - carouselScrollBar.isHidden = (Int((Float(carouselScrollBar.numberOfSlides)) * Float(carouselScrollBar._layout.value)) <= 1) ? true : false - + carouselScrollBar.position = (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) ? 1 : carouselScrollBar.position + carouselScrollBar.isHidden = (totalPositions() <= 1) ? true : false + // When peek is set to ‘none,’ pagination controls are automatically set to persistent. if peek == .none { paginationDisplay = .persistent From a829df86e58ae23c67826aa00ce83be6ebd077b4 Mon Sep 17 00:00:00 2001 From: vasavk Date: Fri, 14 Jun 2024 13:17:29 +0530 Subject: [PATCH 008/117] Digital ACT-191 ONEAPP-7013 story: Fixed Constraints issue and refactored Code --- VDS/Components/Carousel/Carousel.swift | 113 +++++++++++-------------- 1 file changed, 51 insertions(+), 62 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 95abb990..0486af1d 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -33,25 +33,11 @@ open class Carousel: View { //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- - /// Enum used to describe the aspect ratios used for this component. - public enum AspectRatio: String, CaseIterable { - case ratio1x1 = "1:1" - case ratio3x4 = "3:4" - case ratio4x3 = "4:3" - case ratio2x3 = "2:3" - case ratio3x2 = "3:2" - case ratio9x16 = "9:16" - case ratio16x9 = "16:9" - case ratio1x2 = "1:2" - case ratio2x1 = "2:1" - case none - } - /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. public enum Gutter: String, CaseIterable { case twelvePX = "12px" case twentyFourPX = "24px" - + var value: CGFloat { switch self { case .twelvePX: @@ -61,7 +47,7 @@ open class Carousel: View { } } } - + /// Enum used to describe the pagination display for this component. public enum PaginationDisplay: String, CaseIterable { case persistent, none @@ -72,8 +58,7 @@ open class Carousel: View { public enum Peek: String, CaseIterable { case standard, minimum, none } - - // TO DO: move to model class + /// Enum used to describe the vertical of slotAlignment. public enum Vertical: String, CaseIterable { case top, middle, bottom @@ -94,11 +79,11 @@ open class Carousel: View { // MARK: - Public Properties //-------------------------------------------------- /// Aspect-ratio options for tilelet in the carousel. If 'none' is passed, the tilelet will take the height of the tallest item in the carousel. - open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } } - + open var aspectRatio: Tilelet.AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } } + /// Data used to render tilelets in the carousel. open var data: [Any] = [] { didSet { setNeedsUpdate() } } - + /// If provided, width of slots will be rendered based on this value. If omitted, default widths are rendered. open var width : Width? { get { _width } @@ -129,7 +114,7 @@ open class Carousel: View { setNeedsUpdate() } } - + /// The amount of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. open var layout: CarouselScrollbar.Layout { get { return _layout } @@ -138,9 +123,9 @@ open class Carousel: View { setNeedsUpdate() } } - + /// A callback when moving the carousel. Returns event object and selectedGroupIndex. - open var onChange: ((Int) -> Void)? { // TO DO: return object and index + open var onChange: ((Int) -> Void)? { get { nil } set { onChangeCancellable?.cancel() @@ -161,7 +146,7 @@ open class Carousel: View { setNeedsUpdate() } } - + /// If provided, will determine the conditions to render the pagination arrows. open var paginationDisplay: PaginationDisplay { get { return _paginationDisplay } @@ -170,7 +155,7 @@ open class Carousel: View { setNeedsUpdate() } } - + /// If provided, will apply margin to pagination arrows. Can be set to either positive or negative values. /// The default value will be 12px in tablet and 8px in mobile. open var paginationInset: CGFloat { @@ -189,19 +174,19 @@ open class Carousel: View { setNeedsUpdate() } } - + /// The initial visible slide's index in the carousel. open var selectedIndex: Int? { didSet { setNeedsUpdate() } } /// If provided, will set the alignment for slot content when the slots has different heights. open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } } - + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- // Sizes are from InVision design specs. internal var containerSize: CGSize { CGSize(width: 320, height: 44) } - + private let contentStackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical @@ -231,28 +216,32 @@ open class Carousel: View { $0.backgroundColor = .clear } - /// Previous button to show previous slide. - private var previousButton = ButtonIcon().with { - $0.kind = .lowContrast - $0.iconName = .leftCaret - $0.iconOffset = .init(x: -2, y: 0) - $0.customContainerSize = UIDevice.isIPad ? 40 : 28 - $0.icon.customSize = UIDevice.isIPad ? 16 : 12 - } - - /// Next button to show next slide. - private var nextButton = ButtonIcon().with { - $0.kind = .lowContrast - $0.iconName = .rightCaret - $0.iconOffset = .init(x: 2, y: 0) - $0.customContainerSize = UIDevice.isIPad ? 40 : 28 - $0.icon.customSize = UIDevice.isIPad ? 16 : 12 - } + /// Previous button to show previous slide. + private var previousButton = ButtonIcon().with { + $0.kind = .lowContrast + $0.iconName = .leftCaret + $0.iconOffset = .init(x: -2, y: 0) + $0.customContainerSize = UIDevice.isIPad ? 40 : 28 + $0.icon.customSize = UIDevice.isIPad ? 16 : 12 + } + + /// Next button to show next slide. + private var nextButton = ButtonIcon().with { + $0.kind = .lowContrast + $0.iconName = .rightCaret + $0.iconOffset = .init(x: 2, y: 0) + $0.customContainerSize = UIDevice.isIPad ? 40 : 28 + $0.icon.customSize = UIDevice.isIPad ? 16 : 12 + } /// A publisher for when the scrubber position changes. Passes parameters (position). open var onChangePublisher = PassthroughSubject() private var onChangeCancellable: AnyCancellable? - + + /// A publisher for when the carousel moves. Passes parameters (data). + open var onScrollPublisher = PassthroughSubject, Never>() + private var onScrollCancellable: AnyCancellable? + internal var _layout: CarouselScrollbar.Layout = UIDevice.isIPad ? .threeUP : .oneUP internal var _pagination: CarouselPaginationModel = .init(kind: .lowContrast, floating: true) internal var _paginationDisplay: PaginationDisplay = .none @@ -260,12 +249,12 @@ open class Carousel: View { internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX internal var _peek: Peek = .none internal var _numberOfSlides: Int = 1 - + private var _width: Width? = nil private var selectedGroupIndex: Int? { didSet { setNeedsUpdate() } } private var containerStackHeightConstraint: NSLayoutConstraint? private var containerViewHeightConstraint: NSLayoutConstraint? - + // The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile. let scrollbarTopSpace = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X @@ -282,7 +271,7 @@ open class Carousel: View { open override func setup() { super.setup() isAccessibilityElement = false - + // add containerView addSubview(containerView) containerView @@ -305,12 +294,12 @@ open class Carousel: View { previousButton .pinLeading(paginationInset) .pinCenterY() - + scrollContainerView.addSubview(nextButton) nextButton .pinTrailing(paginationInset) .pinCenterY() - + // add scroll container view & carousel scrollbar contentStackView.addArrangedSubview(scrollContainerView) contentStackView.addArrangedSubview(carouselScrollBar) @@ -320,13 +309,15 @@ open class Carousel: View { .pinBottom() .pinLeading() .pinTrailing() - .heightGreaterThanEqualTo(scrollbarTopSpace + containerSize.height) + .heightGreaterThanEqualTo(containerSize.height) contentStackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() + + addlisteners() } open override func updateView() { super.updateView() - + carouselScrollBar.numberOfSlides = data.count carouselScrollBar.layout = _layout carouselScrollBar.position = (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) ? 1 : carouselScrollBar.position @@ -354,7 +345,7 @@ open class Carousel: View { for x in 0...data.count - 1 { let carouselSlot = View().with { $0.clipsToBounds = true - $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) + $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) } scrollView.addSubview(carouselSlot) carouselSlot @@ -365,7 +356,6 @@ open class Carousel: View { .height(slotHeight) xPos = xPos + minimumSlotWidth + gutter.value } - scrollView.heightAnchor.constraint(equalToConstant: slotHeight).isActive = true scrollView.contentSize = CGSize(width: xPos - gutter.value, height: slotHeight) } @@ -380,7 +370,6 @@ open class Carousel: View { containerViewHeightConstraint?.isActive = true containerStackHeightConstraint?.isActive = true - addlisteners() layoutIfNeeded() } @@ -401,10 +390,10 @@ open class Carousel: View { previousButton.onClick = { _ in self.previousButtonClick() } //setup test page to show scrubber id was changed -// carouselScrollBar.onScrubberDrag = { [weak self] scrubberId in -// guard let self else { return } -// updateScrollPosition(position: scrubberId, callbackText:"onScrubberDrag") -// } + // carouselScrollBar.onScrubberDrag = { [weak self] scrubberId in + // guard let self else { return } + // updateScrollPosition(position: scrubberId, callbackText:"onScrubberDrag") + // } /// will be called when the thumb move forward. carouselScrollBar.onMoveForward = { [weak self] scrubberId in @@ -467,7 +456,7 @@ open class Carousel: View { showPaginationControls() updateScrollPosition(position: carouselScrollBar.position, callbackText:"pageControlClicks") } - + func updateScrollPosition(position: Int, callbackText: String) { if carouselScrollBar.numberOfSlides > 0 { let contentOffsetWidth = scrollView.contentSize.width From 0a27c27189927b3731c52a85434889e983c67016 Mon Sep 17 00:00:00 2001 From: vasavk Date: Fri, 14 Jun 2024 18:02:57 +0530 Subject: [PATCH 009/117] Digital ACT-191 ONEAPP-7013 story: add corner radius to slot, supporting to dynamic pagination inset --- VDS/Components/Carousel/Carousel.swift | 38 +++++++++++++++----------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 0486af1d..cf52506b 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -161,8 +161,9 @@ open class Carousel: View { open var paginationInset: CGFloat { get { return _paginationInset } set { - _paginationInset = newValue - setNeedsUpdate() + let minValue = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X + _paginationInset = (newValue < minValue) ? minValue : newValue + updatePaginationInset() } } @@ -180,7 +181,7 @@ open class Carousel: View { /// If provided, will set the alignment for slot content when the slots has different heights. open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } } - + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -254,7 +255,9 @@ open class Carousel: View { private var selectedGroupIndex: Int? { didSet { setNeedsUpdate() } } private var containerStackHeightConstraint: NSLayoutConstraint? private var containerViewHeightConstraint: NSLayoutConstraint? - + private var prevButtonLeadingConstraint: NSLayoutConstraint? + private var nextButtonTrailingConstraint: NSLayoutConstraint? + // The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile. let scrollbarTopSpace = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X @@ -292,12 +295,12 @@ open class Carousel: View { // add pagination button icons scrollContainerView.addSubview(previousButton) previousButton - .pinLeading(paginationInset) + .pinLeadingGreaterThanOrEqualTo() .pinCenterY() scrollContainerView.addSubview(nextButton) nextButton - .pinTrailing(paginationInset) + .pinTrailingLessThanOrEqualTo() .pinCenterY() // add scroll container view & carousel scrollbar @@ -314,10 +317,9 @@ open class Carousel: View { addlisteners() } - + open override func updateView() { super.updateView() - carouselScrollBar.numberOfSlides = data.count carouselScrollBar.layout = _layout carouselScrollBar.position = (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) ? 1 : carouselScrollBar.position @@ -327,7 +329,7 @@ open class Carousel: View { if peek == .none { paginationDisplay = .persistent } - + containerViewHeightConstraint?.isActive = false containerStackHeightConstraint?.isActive = false updatePaginationControls() @@ -354,6 +356,7 @@ open class Carousel: View { .pinLeading(xPos) .width(minimumSlotWidth) .height(slotHeight) + carouselSlot.layer.cornerRadius = 12.0 xPos = xPos + minimumSlotWidth + gutter.value } scrollView.contentSize = CGSize(width: xPos - gutter.value, height: slotHeight) @@ -388,13 +391,7 @@ open class Carousel: View { func addlisteners() { nextButton.onClick = { _ in self.nextButtonClick() } previousButton.onClick = { _ in self.previousButtonClick() } - - //setup test page to show scrubber id was changed - // carouselScrollBar.onScrubberDrag = { [weak self] scrubberId in - // guard let self else { return } - // updateScrollPosition(position: scrubberId, callbackText:"onScrubberDrag") - // } - + /// will be called when the thumb move forward. carouselScrollBar.onMoveForward = { [weak self] scrubberId in guard let self else { return } @@ -431,6 +428,15 @@ open class Carousel: View { nextButton.surface = surface } + func updatePaginationInset() { + prevButtonLeadingConstraint?.isActive = false + nextButtonTrailingConstraint?.isActive = false + prevButtonLeadingConstraint = previousButton.leadingAnchor.constraint(equalTo: scrollContainerView.leadingAnchor, constant: paginationInset) + nextButtonTrailingConstraint = nextButton.trailingAnchor.constraint(equalTo: scrollContainerView.trailingAnchor, constant: -paginationInset) + prevButtonLeadingConstraint?.isActive = true + nextButtonTrailingConstraint?.isActive = true + } + func getSlotWidth() { let actualWidth = containerView.frame.size.width minimumSlotWidth = actualWidth - (CGFloat(layout.value) * gutter.value) From 993afcc367ff8da31643eac5c2b6215295101c4e Mon Sep 17 00:00:00 2001 From: vasavk Date: Fri, 14 Jun 2024 20:32:22 +0530 Subject: [PATCH 010/117] updated to VDSCoreTokens --- VDS/Components/Carousel/Carousel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index cf52506b..4ad8ef8c 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -7,7 +7,7 @@ import Foundation import UIKit -import VDSTokens +import VDSCoreTokens import Combine /// A carousel is a collection of related content in a row that a customer can navigate through horizontally. From b302077f994fe98539a389dd5471ca82783ded4e Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 20 Jun 2024 18:23:54 -0500 Subject: [PATCH 011/117] added popover Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 8 +- VDS/Classes/ClearPopoverViewController.swift | 151 +++++++++++++++++++ 2 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 VDS/Classes/ClearPopoverViewController.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index ff8a6735..7c9565f7 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -152,7 +152,7 @@ EAC58C182BED0E2300BA39FA /* SecurityCode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C172BED0E2300BA39FA /* SecurityCode.swift */; }; EAC58C232BF2824200BA39FA /* DatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C222BF2824200BA39FA /* DatePicker.swift */; }; EAC58C272BF4116200BA39FA /* DatePickerCalendarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */; }; - EAC58C292BF4118C00BA39FA /* DatePickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */; }; + EAC58C292BF4118C00BA39FA /* ClearPopoverViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC58C282BF4118C00BA39FA /* ClearPopoverViewController.swift */; }; EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */; }; EAC71A1F2A2E173D00E47A9F /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */; }; EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */; }; @@ -368,7 +368,7 @@ EAC58C222BF2824200BA39FA /* DatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePicker.swift; sourceTree = ""; }; EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = DatePickerChangeLog.txt; sourceTree = ""; }; EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerCalendarModel.swift; sourceTree = ""; }; - EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatePickerViewController.swift; sourceTree = ""; }; + EAC58C282BF4118C00BA39FA /* ClearPopoverViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClearPopoverViewController.swift; sourceTree = ""; }; EAC71A1C2A2E155A00E47A9F /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; EAC71A1E2A2E173D00E47A9F /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupCollectionViewCell.swift; sourceTree = ""; }; @@ -746,6 +746,7 @@ isa = PBXGroup; children = ( EA985C1C296CD13600F2FF2E /* BundleManager.swift */, + EAC58C282BF4118C00BA39FA /* ClearPopoverViewController.swift */, EAF7F0B8289C139800B287F5 /* ColorConfiguration.swift */, EAB5FEF02927F4AA00998C17 /* SelfSizingCollectionView.swift */, EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */, @@ -966,7 +967,6 @@ children = ( EAC58C222BF2824200BA39FA /* DatePicker.swift */, EAC58C262BF4116200BA39FA /* DatePickerCalendarModel.swift */, - EAC58C282BF4118C00BA39FA /* DatePickerViewController.swift */, EAC58C242BF2A7FB00BA39FA /* DatePickerChangeLog.txt */, ); path = DatePicker; @@ -1304,7 +1304,7 @@ EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */, EAC58C0A2BED004E00BA39FA /* FieldType.swift in Sources */, EA471F3A2A95587500CE9E58 /* LayoutConstraintable.swift in Sources */, - EAC58C292BF4118C00BA39FA /* DatePickerViewController.swift in Sources */, + EAC58C292BF4118C00BA39FA /* ClearPopoverViewController.swift in Sources */, EAF193432C134F3800C68D18 /* TableCellItem.swift in Sources */, EAB1D2CF28ABEF2B00DAE764 /* Typography+Base.swift in Sources */, EA0D1C3B2A6AD51B00E5C127 /* Typogprahy+Styles.swift in Sources */, diff --git a/VDS/Classes/ClearPopoverViewController.swift b/VDS/Classes/ClearPopoverViewController.swift new file mode 100644 index 00000000..33531a75 --- /dev/null +++ b/VDS/Classes/ClearPopoverViewController.swift @@ -0,0 +1,151 @@ +// +// DatePickerPopoverViewController.swift +// VDS +// +// Created by Matt Bruce on 5/14/24. +// + +import Foundation +import UIKit + +open class ClearPopoverViewController: UIViewController, UIPopoverPresentationControllerDelegate { + + /// The view to be inserted inside the popover + private var contentView: UIView! + + /// An object representing the arrow of the popover. + private var arrow: UIPopoverArrowDirection + + /// Popover presentation controller of the popover + private var popOver: UIPopoverPresentationController! + + open var spacing: CGFloat = 0 + /** + A controller that manages the popover. + - Parameter contentView: The view to be inserted inside the popover. + - Parameter design: An object used for defining visual attributes of the popover. + - Parameter arrow: An object representing the arrow in popover. + - Parameter sourceView: The view containing the anchor rectangle for the popover. + - Parameter sourceRect: The rectangle in the specified view in which to anchor the popover. + - Parameter barButtonItem: The bar button item on which to anchor the popover. + + Assign a value to `barButton` to anchor the popover to the specified bar button item. When presented, the popover’s arrow points to the specified item. Alternatively, you may specify the anchor location for the popover using the `sourceView` and `sourceRect` properties. + */ + public init(contentView: UIView, arrow: UIPopoverArrowDirection, sourceView: UIView? = nil, sourceRect: CGRect? = nil, spacing: CGFloat = 0, barButtonItem: UIBarButtonItem? = nil) { + self.contentView = contentView + self.spacing = spacing + self.arrow = arrow + super.init(nibName: nil, bundle: nil) + setupPopover(sourceView, sourceRect, barButtonItem) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func viewIsAppearing(_ animated: Bool) { + super.viewIsAppearing(animated) + view.superview?.accessibilityIdentifier = "HadCornerRadius" + view.accessibilityIdentifier = "PopoverViewController.View" + contentView.accessibilityIdentifier = "PopoverViewController.ContentView" + view.superview?.layer.cornerRadius = 0 + } + + open override func viewDidLayoutSubviews() { + contentView.frame.origin = CGPoint(x: 0, y: 0) + } + + ///Sets up the Popover and starts the timer for its closing. + private func setupPopover(_ sourceView: UIView?, _ sourceRect: CGRect?, _ barButtonItem: UIBarButtonItem?) { + modalPresentationStyle = .popover + view.addSubview(contentView) + + popOver = self.popoverPresentationController! + popOver.popoverLayoutMargins = .zero + popOver.popoverBackgroundViewClass = ClearPopoverBackgroundView.self + popOver.sourceView = sourceView + popOver.popoverLayoutMargins = .zero + + if let sourceRect = sourceRect { + popOver.sourceRect = sourceRect + } + + popOver.barButtonItem = barButtonItem + popOver.delegate = self + popOver.permittedArrowDirections = arrow + popOver.backgroundColor = .clear + + } + + open func popoverPresentationController(_ popoverPresentationController: UIPopoverPresentationController, willRepositionPopoverTo rect: UnsafeMutablePointer, in view: AutoreleasingUnsafeMutablePointer) { + if let presentedView = popoverPresentationController.presentedViewController.view.superview { + presentedView.layer.cornerRadius = 0 + } + } + + private func updatePopoverPosition() { + guard let popoverPresentationController = popoverPresentationController else { return } + if let sourceView = popoverPresentationController.sourceView { + popoverPresentationController.sourceRect = .init(x: sourceView.bounds.origin.x, + y: sourceView.bounds.origin.y, + width: sourceView.bounds.width, + height: sourceView.bounds.height + spacing) + } + } + + // Ensure to handle rotations + open override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + coordinator.animate(alongsideTransition: { [weak self] _ in + self?.updatePopoverPosition() + }) + } + + open func adaptivePresentationStyle(for controller: UIPresentationController, traitCollection: UITraitCollection) -> UIModalPresentationStyle { + return .none + } + + // Returns presentation controller of the popover + open func getPopoverPresentationController() -> UIPopoverPresentationController { + return popOver + } +} + +open class ClearPopoverBackgroundView: UIPopoverBackgroundView { + open override var arrowOffset: CGFloat { + get { 0 } + set { } + } + + open override var arrowDirection: UIPopoverArrowDirection { + get { .any } + set { } + } + + open override class var wantsDefaultContentAppearance: Bool { + false + } + + open override class func contentViewInsets() -> UIEdgeInsets{ + .zero + } + + open override class func arrowHeight() -> CGFloat { + 0 + } + + open override class func arrowBase() -> CGFloat{ + 0 + } + + open override func layoutSubviews() { + super.layoutSubviews() + layer.shadowOpacity = 0 + layer.shadowRadius = 0 + layer.cornerRadius = 0 + } + + open override func draw(_ rect: CGRect) { + + } +} From 0a2e6c88b6a5421ab883fe45c3c8b0a835963b8b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 20 Jun 2024 18:24:09 -0500 Subject: [PATCH 012/117] deleted file no longer needed Signed-off-by: Matt Bruce --- .../DatePicker/DatePickerViewController.swift | 71 ------------------- 1 file changed, 71 deletions(-) delete mode 100644 VDS/Components/DatePicker/DatePickerViewController.swift diff --git a/VDS/Components/DatePicker/DatePickerViewController.swift b/VDS/Components/DatePicker/DatePickerViewController.swift deleted file mode 100644 index f8a6e2f0..00000000 --- a/VDS/Components/DatePicker/DatePickerViewController.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// DatePickerPopoverViewController.swift -// VDS -// -// Created by Matt Bruce on 5/14/24. -// - -import Foundation -import UIKit - -protocol DatePickerViewControllerDelegate: NSObject { - func didSelectDate(_ controller: DatePicker.DatePickerViewController, date: Date) -} - -extension DatePicker { - class DatePickerViewController: UIViewController { - private var padding: CGFloat = 15 - private var topPadding: CGFloat { 10 + padding } - private var calendarModel: CalendarModel - private let picker = CalendarBase() - weak var delegate: DatePickerViewControllerDelegate? - - init(_ calendarModel: CalendarModel, delegate: DatePickerViewControllerDelegate?) { - self.delegate = delegate - self.calendarModel = calendarModel - super.init(nibName: nil, bundle: nil) - self.picker.onChange = { [weak self] control in - guard let self else { return } - self.delegate?.didSelectDate(self, date: control.selectedDate) - } - } - - var selectedDate: Date = Date() { - didSet { - picker.selectedDate = selectedDate - } - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - view.addSubview(picker) - picker.surface = calendarModel.surface - picker.hideContainerBorder = calendarModel.hideContainerBorder - picker.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator - picker.indicators = calendarModel.indicators - picker.activeDates = calendarModel.activeDates - picker.inactiveDates = calendarModel.inactiveDates - picker.selectedDate = selectedDate - picker.minDate = calendarModel.minDate - picker.maxDate = calendarModel.maxDate - picker.pinToSuperView(.init(top: topPadding, left: padding, bottom: padding, right: padding)) - view.backgroundColor = picker.backgroundColor - } - - override var preferredContentSize: CGSize { - get { - var size = picker.frame.size - size.height += 40 - size.width += 30 - return size - } - set { - super.preferredContentSize = newValue - } - } - } -} From ce6aad5540788e3b637139488093d223e5d600f3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 20 Jun 2024 18:24:21 -0500 Subject: [PATCH 013/117] refactored to use new popover Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 69 +++++++++++++++------- 1 file changed, 47 insertions(+), 22 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index fdc6e05f..0e985176 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -5,7 +5,7 @@ import Combine /// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection. @objc(VDSDatePicker) -open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopoverPresentationControllerDelegate { +open class DatePicker: EntryFieldBase { //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -31,7 +31,7 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov // MARK: - Private Properties //-------------------------------------------------- internal var minWidthDefault = 186.0 - + internal var bottomStackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false @@ -114,6 +114,16 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov } } .store(in: &subscribers) + + NotificationCenter.default + .publisher(for: UIDevice.orientationDidChangeNotification).sink { [weak self] _ in + guard let self, let popoverController else { return } + popoverController.dismiss(animated: true){ [weak self] in + guard let self else { return } + } + } + .store(in: &subscribers) + } open override func getFieldContainer() -> UIView { @@ -153,31 +163,46 @@ open class DatePicker: EntryFieldBase, DatePickerViewControllerDelegate, UIPopov selectedDateLabel.text = formatter.string(from: date) } - internal func togglePicker() { - let calendarVC = DatePickerViewController(calendarModel, delegate: self) - calendarVC.modalPresentationStyle = .popover - calendarVC.selectedDate = selectedDate ?? Date() - if let popoverController = calendarVC.popoverPresentationController { - popoverController.delegate = self - popoverController.sourceView = containerView - popoverController.sourceRect = containerView.bounds - popoverController.permittedArrowDirections = .up - } - if let viewController = UIApplication.topViewController() { - viewController.present(calendarVC, animated: true, completion: nil) - } - } + internal var popoverController: UIViewController? - internal func didSelectDate(_ controller: DatePickerViewController, date: Date) { + func didSelect(_ date: Date) { selectedDate = date - controller.dismiss(animated: true) { [weak self] in + sendActions(for: .valueChanged) + UIAccessibility.post(notification: .layoutChanged, argument: self.containerView) + popoverController?.dismiss(animated: true){ [weak self] in guard let self else { return } - self.sendActions(for: .valueChanged) - UIAccessibility.post(notification: .layoutChanged, argument: self.containerView) + popoverController = nil } } - public func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle { - return .none + internal func togglePicker() { + let calendar = CalendarBase() + calendar.activeDates = calendarModel.activeDates + calendar.hideContainerBorder = calendarModel.hideContainerBorder + calendar.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator + calendar.inactiveDates = calendarModel.inactiveDates + calendar.indicators = calendarModel.indicators + calendar.maxDate = calendarModel.maxDate + calendar.minDate = calendarModel.minDate + calendar.surface = calendarModel.surface + calendar.setNeedsLayout() + calendar.layoutIfNeeded() + calendar.onChange = { [weak self] control in + guard let self else { return } + didSelect(control.selectedDate) + } + + popoverController = ClearPopoverViewController(contentView: calendar, + arrow: .up, + sourceView: containerView, + sourceRect: containerView.bounds, + spacing: VDSLayout.space1X) + + if let viewController = UIApplication.topViewController(), let popoverController { + viewController.present(popoverController, + animated: true, + completion: nil) + } } } + From 31b9704163e64fd444759d51630254df0c1271ab Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 21 Jun 2024 12:20:39 -0500 Subject: [PATCH 014/117] first attempt to get link clicks working again Signed-off-by: Matt Bruce --- VDS/Components/Label/Label.swift | 85 ++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 10 deletions(-) diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 15ed4b45..c622b694 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -411,20 +411,85 @@ open class Label: UILabel, ViewProtocol, UserInfoable { private func didTapActionInLabel(_ location: CGPoint, inRange targetRange: NSRange) -> Bool { - guard let attributedText else { return false } +// guard let attributedText else { return false } +// let layoutManager = NSLayoutManager() +// let textContainer = NSTextContainer(size: bounds.size) +// let textStorage = NSTextStorage(attributedString: attributedText) + // layoutManager.addTextContainer(textContainer) + // textStorage.addLayoutManager(layoutManager) + // + // let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) + // + // guard let _ = attributedText.attribute(NSAttributedString.Key.action, at: characterIndex, effectiveRange: nil) as? String, + // characterIndex < attributedText.length else { + // return false + // } + // return true + + // There would only ever be one clause to act on. + guard let abstractContainer = abstractTextContainer() else { return false } + let textContainer = abstractContainer.0 + let layoutManager = abstractContainer.1 + + let tapLocation = location + let indexOfGlyph = layoutManager.glyphIndex(for: tapLocation, in: textContainer) + let intrinsicWidth = intrinsicContentSize.width + + // Assert that tapped occured within acceptable bounds based on alignment. + switch textAlignment { + case .right: + if tapLocation.x < bounds.width - intrinsicWidth { + return false + } + case .center: + let halfBounds = bounds.width / 2 + let halfIntrinsicWidth = intrinsicWidth / 2 + + if tapLocation.x > halfBounds + halfIntrinsicWidth { + return false + } else if tapLocation.x < halfBounds - halfIntrinsicWidth { + return false + } + default: // Left align + if tapLocation.x > intrinsicWidth { + return false + } + } + + // Affirms that the tap occured in the desired rect of provided by the target range. + return layoutManager.boundingRect(forGlyphRange: targetRange, in: textContainer).contains(tapLocation) && NSLocationInRange(indexOfGlyph, targetRange) + } + + /** + Provides a text container and layout manager of how the text would appear on screen. + They are used in tandem to derive low-level TextKit results of the label. + */ + public func abstractTextContainer() -> (NSTextContainer, NSLayoutManager, NSTextStorage)? { + + // Must configure the attributed string to translate what would appear on screen to accurately analyze. + guard let attributedText = attributedText else { return nil } + + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = textAlignment + + let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText) + stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count)) + + let textStorage = NSTextStorage(attributedString: stagedAttributedString) let layoutManager = NSLayoutManager() - let textContainer = NSTextContainer(size: bounds.size) - let textStorage = NSTextStorage(attributedString: attributedText) + let textContainer = NSTextContainer(size: .zero) + layoutManager.addTextContainer(textContainer) textStorage.addLayoutManager(layoutManager) - - let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) - - guard let _ = attributedText.attribute(NSAttributedString.Key.action, at: characterIndex, effectiveRange: nil) as? String, characterIndex < attributedText.length else { return false } - return true + + textContainer.lineFragmentPadding = 0.0 + textContainer.lineBreakMode = lineBreakMode + textContainer.maximumNumberOfLines = numberOfLines + textContainer.size = bounds.size + + return (textContainer, layoutManager, textStorage) } - - + private func customAccessibilityElement(text: String?, range: NSRange, accessibleText: String? = nil) -> AccessibilityActionElement? { guard let text = text, let attributedText else { return nil } From 8b37986b400622da5d6c7b03c7ece80543d91d0c Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 21 Jun 2024 13:19:15 -0500 Subject: [PATCH 015/117] reverted back to original MVA code for getting the location of links and such. refactored Signed-off-by: Matt Bruce --- VDS/Components/Label/Label.swift | 84 +++++++-------------- VDS/Extensions/UITapGestureRecognizer.swift | 20 +---- 2 files changed, 32 insertions(+), 72 deletions(-) diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index c622b694..78d34574 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -389,82 +389,59 @@ open class Label: UILabel, ViewProtocol, UserInfoable { } } + //-------------------------------------------------- + // MARK: - Touch Events + //-------------------------------------------------- @objc private func textLinkTapped(_ gesture: UITapGestureRecognizer) { - for actionable in actions { - // This determines if we tapped on the desired range of text. - let location = gesture.location(in: self) - if didTapActionInLabel(location, inRange: actionable.range) { - actionable.performAction() - return - } + let location = gesture.location(in: self) + if let action = actions.first(where: { isAction(for: location, inRange: $0.range) }) { + action.performAction() } } public func isAction(for location: CGPoint) -> Bool { - for actionable in actions { - if didTapActionInLabel(location, inRange: actionable.range) { - return true - } - } - return false + actions.contains(where: {isAction(for: location, inRange: $0.range)}) } - private func didTapActionInLabel(_ location: CGPoint, inRange targetRange: NSRange) -> Bool { + public func isAction(for location: CGPoint, inRange targetRange: NSRange) -> Bool { + guard let attributedText = attributedText, let abstractContainer = abstractTextContainer() else { return false } + let textContainer = abstractContainer.textContainer + let layoutManager = abstractContainer.layoutManager -// guard let attributedText else { return false } -// let layoutManager = NSLayoutManager() -// let textContainer = NSTextContainer(size: bounds.size) -// let textStorage = NSTextStorage(attributedString: attributedText) - // layoutManager.addTextContainer(textContainer) - // textStorage.addLayoutManager(layoutManager) - // - // let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) - // - // guard let _ = attributedText.attribute(NSAttributedString.Key.action, at: characterIndex, effectiveRange: nil) as? String, - // characterIndex < attributedText.length else { - // return false - // } - // return true - - // There would only ever be one clause to act on. - guard let abstractContainer = abstractTextContainer() else { return false } - let textContainer = abstractContainer.0 - let layoutManager = abstractContainer.1 - - let tapLocation = location - let indexOfGlyph = layoutManager.glyphIndex(for: tapLocation, in: textContainer) + let indexOfGlyph = layoutManager.glyphIndex(for: location, in: textContainer) let intrinsicWidth = intrinsicContentSize.width // Assert that tapped occured within acceptable bounds based on alignment. switch textAlignment { case .right: - if tapLocation.x < bounds.width - intrinsicWidth { + if location.x < bounds.width - intrinsicWidth { return false } case .center: let halfBounds = bounds.width / 2 let halfIntrinsicWidth = intrinsicWidth / 2 - if tapLocation.x > halfBounds + halfIntrinsicWidth { + if location.x > halfBounds + halfIntrinsicWidth { return false - } else if tapLocation.x < halfBounds - halfIntrinsicWidth { + } else if location.x < halfBounds - halfIntrinsicWidth { return false } default: // Left align - if tapLocation.x > intrinsicWidth { + if location.x > intrinsicWidth { return false } } // Affirms that the tap occured in the desired rect of provided by the target range. - return layoutManager.boundingRect(forGlyphRange: targetRange, in: textContainer).contains(tapLocation) && NSLocationInRange(indexOfGlyph, targetRange) + return layoutManager.boundingRect(forGlyphRange: targetRange, in: textContainer).contains(location) + && NSLocationInRange(indexOfGlyph, targetRange) } /** Provides a text container and layout manager of how the text would appear on screen. They are used in tandem to derive low-level TextKit results of the label. */ - public func abstractTextContainer() -> (NSTextContainer, NSLayoutManager, NSTextStorage)? { + public func abstractTextContainer() -> (textContainer: NSTextContainer, layoutManager: NSLayoutManager, textStorage: NSTextStorage)? { // Must configure the attributed string to translate what would appear on screen to accurately analyze. guard let attributedText = attributedText else { return nil } @@ -489,25 +466,23 @@ open class Label: UILabel, ViewProtocol, UserInfoable { return (textContainer, layoutManager, textStorage) } - + + //-------------------------------------------------- + // MARK: - Accessibility + //-------------------------------------------------- private func customAccessibilityElement(text: String?, range: NSRange, accessibleText: String? = nil) -> AccessibilityActionElement? { - guard let text = text, let attributedText else { return nil } + guard let text = text, let attributedText, let abstractContainer = abstractTextContainer() else { return nil } + let textContainer = abstractContainer.textContainer + let layoutManager = abstractContainer.layoutManager + let actionText = accessibleText ?? (text.isValid(range: range) ? NSString(string:text).substring(with: range) : text) - // Calculate the frame of the substring - let layoutManager = NSLayoutManager() - let textContainer = NSTextContainer(size: bounds.size) - let textStorage = NSTextStorage(attributedString: attributedText) - layoutManager.addTextContainer(textContainer) - textStorage.addLayoutManager(layoutManager) - var glyphRange = NSRange() // Convert the range for the substring into a range of glyphs layoutManager.characterRange(forGlyphRange: range, actualGlyphRange: &glyphRange) - let substringBounds = layoutManager.boundingRect(forGlyphRange: glyphRange, in: textContainer) // Create custom accessibility element @@ -520,11 +495,8 @@ open class Label: UILabel, ViewProtocol, UserInfoable { accessibilityElements?.append(element) return element } + - - //-------------------------------------------------- - // MARK: - Accessibility - //-------------------------------------------------- open var accessibilityAction: ((Label) -> Void)? private var _isAccessibilityElement: Bool = false diff --git a/VDS/Extensions/UITapGestureRecognizer.swift b/VDS/Extensions/UITapGestureRecognizer.swift index 612a11ca..4461ae06 100644 --- a/VDS/Extensions/UITapGestureRecognizer.swift +++ b/VDS/Extensions/UITapGestureRecognizer.swift @@ -12,23 +12,11 @@ extension UITapGestureRecognizer { /// Determines if the touch event has a action attribute within the range given /// - Parameters: - /// - label: UILabel in question + /// - label: Label in question /// - targetRange: Range to look within /// - Returns: Wether the range in the label has an action - public func didTapActionInLabel(_ label: UILabel, inRange targetRange: NSRange) -> Bool { - - guard let attributedText = label.attributedText else { return false } - - let layoutManager = NSLayoutManager() - let textContainer = NSTextContainer(size: label.bounds.size) - let textStorage = NSTextStorage(attributedString: attributedText) - layoutManager.addTextContainer(textContainer) - textStorage.addLayoutManager(layoutManager) - - let location = location(in: label) - let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) - - guard let _ = attributedText.attribute(NSAttributedString.Key.action, at: characterIndex, effectiveRange: nil) as? String, characterIndex < attributedText.length else { return false } - return true + public func didTapActionInLabel(_ label: Label, inRange targetRange: NSRange) -> Bool { + let tapLocation = location(in: label) + return label.isAction(for: tapLocation, inRange: targetRange) } } From e7f5d4ee94699689c8c5b594161df7ccfc20be99 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 21 Jun 2024 13:21:36 -0500 Subject: [PATCH 016/117] removed code not needed. Signed-off-by: Matt Bruce --- VDS/Components/Label/Label.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 78d34574..c8427f2e 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -404,7 +404,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable { } public func isAction(for location: CGPoint, inRange targetRange: NSRange) -> Bool { - guard let attributedText = attributedText, let abstractContainer = abstractTextContainer() else { return false } + guard let abstractContainer = abstractTextContainer() else { return false } let textContainer = abstractContainer.textContainer let layoutManager = abstractContainer.layoutManager @@ -472,7 +472,7 @@ open class Label: UILabel, ViewProtocol, UserInfoable { //-------------------------------------------------- private func customAccessibilityElement(text: String?, range: NSRange, accessibleText: String? = nil) -> AccessibilityActionElement? { - guard let text = text, let attributedText, let abstractContainer = abstractTextContainer() else { return nil } + guard let text = text, let abstractContainer = abstractTextContainer() else { return nil } let textContainer = abstractContainer.textContainer let layoutManager = abstractContainer.layoutManager From aad70d2e409aadc36bc0ea84b68541902dc6d26f Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 21 Jun 2024 13:30:19 -0500 Subject: [PATCH 017/117] testing not using popover viewcontroller Signed-off-by: Matt Bruce --- VDS/Classes/ClearPopoverViewController.swift | 13 +- VDS/Components/DatePicker/DatePicker.swift | 204 ++++++++++++++++--- 2 files changed, 182 insertions(+), 35 deletions(-) diff --git a/VDS/Classes/ClearPopoverViewController.swift b/VDS/Classes/ClearPopoverViewController.swift index 33531a75..6f9bcb67 100644 --- a/VDS/Classes/ClearPopoverViewController.swift +++ b/VDS/Classes/ClearPopoverViewController.swift @@ -19,6 +19,10 @@ open class ClearPopoverViewController: UIViewController, UIPopoverPresentationCo /// Popover presentation controller of the popover private var popOver: UIPopoverPresentationController! + open var maxWidth: CGFloat? + + open var sourceRect: CGRect? + open var spacing: CGFloat = 0 /** A controller that manages the popover. @@ -35,6 +39,7 @@ open class ClearPopoverViewController: UIViewController, UIPopoverPresentationCo self.contentView = contentView self.spacing = spacing self.arrow = arrow + self.sourceRect = sourceRect super.init(nibName: nil, bundle: nil) setupPopover(sourceView, sourceRect, barButtonItem) } @@ -65,7 +70,6 @@ open class ClearPopoverViewController: UIViewController, UIPopoverPresentationCo popOver.popoverBackgroundViewClass = ClearPopoverBackgroundView.self popOver.sourceView = sourceView popOver.popoverLayoutMargins = .zero - if let sourceRect = sourceRect { popOver.sourceRect = sourceRect } @@ -85,11 +89,8 @@ open class ClearPopoverViewController: UIViewController, UIPopoverPresentationCo private func updatePopoverPosition() { guard let popoverPresentationController = popoverPresentationController else { return } - if let sourceView = popoverPresentationController.sourceView { - popoverPresentationController.sourceRect = .init(x: sourceView.bounds.origin.x, - y: sourceView.bounds.origin.y, - width: sourceView.bounds.width, - height: sourceView.bounds.height + spacing) + if let sourceView = popoverPresentationController.sourceView, let sourceRect { + popoverPresentationController.sourceRect = sourceRect } } diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 0e985176..3ab83be1 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -117,10 +117,11 @@ open class DatePicker: EntryFieldBase { NotificationCenter.default .publisher(for: UIDevice.orientationDidChangeNotification).sink { [weak self] _ in - guard let self, let popoverController else { return } - popoverController.dismiss(animated: true){ [weak self] in + guard let self else { return } + popoverController?.dismiss(animated: true){ [weak self] in guard let self else { return } } + hidePopoverView() } .store(in: &subscribers) @@ -163,7 +164,7 @@ open class DatePicker: EntryFieldBase { selectedDateLabel.text = formatter.string(from: date) } - internal var popoverController: UIViewController? + internal var popoverController: ClearPopoverViewController? func didSelect(_ date: Date) { selectedDate = date @@ -175,34 +176,179 @@ open class DatePicker: EntryFieldBase { } } - internal func togglePicker() { - let calendar = CalendarBase() - calendar.activeDates = calendarModel.activeDates - calendar.hideContainerBorder = calendarModel.hideContainerBorder - calendar.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator - calendar.inactiveDates = calendarModel.inactiveDates - calendar.indicators = calendarModel.indicators - calendar.maxDate = calendarModel.maxDate - calendar.minDate = calendarModel.minDate - calendar.surface = calendarModel.surface - calendar.setNeedsLayout() - calendar.layoutIfNeeded() - calendar.onChange = { [weak self] control in - guard let self else { return } - didSelect(control.selectedDate) - } + private var overlayView = UIView().with { + $0.backgroundColor = .clear; + $0.isHidden = true + } + private var popoverView: UIView! + private var popoverVisible = false + private var outsideTapGesture: UITapGestureRecognizer? + private var outsidePanGesture: UIPanGestureRecognizer? + +// internal func togglePicker() { +// calendar.activeDates = calendarModel.activeDates +// calendar.hideContainerBorder = calendarModel.hideContainerBorder +// calendar.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator +// calendar.inactiveDates = calendarModel.inactiveDates +// calendar.indicators = calendarModel.indicators +// calendar.maxDate = calendarModel.maxDate +// calendar.minDate = calendarModel.minDate +// calendar.surface = calendarModel.surface +// calendar.setNeedsLayout() +// calendar.layoutIfNeeded() +// calendar.onChange = { [weak self] control in +// guard let self else { return } +// didSelect(control.selectedDate) +// } +// +// popoverController = ClearPopoverViewController(contentView: calendar, +// arrow: .any, +// sourceView: containerView, +// sourceRect: .init(x: 0, y: 0, width: 320, height: 45), +// spacing: VDSLayout.space1X) +// popoverController?.maxWidth = 320 +// if let viewController = UIApplication.topViewController(), let popoverController { +// viewController.present(popoverController, +// animated: true, +// completion: nil) +// } +// } +} + +extension DatePicker { + + private func togglePicker() { + guard let viewController = UIApplication.topViewController(), let parentView = viewController.view else { return } + + if popoverVisible { + hidePopoverView() + } else { + let calendar = CalendarBase() + calendar.activeDates = calendarModel.activeDates + calendar.hideContainerBorder = calendarModel.hideContainerBorder + calendar.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator + calendar.inactiveDates = calendarModel.inactiveDates + calendar.indicators = calendarModel.indicators + calendar.maxDate = calendarModel.maxDate + calendar.minDate = calendarModel.minDate + calendar.surface = calendarModel.surface + calendar.setNeedsLayout() + calendar.layoutIfNeeded() + calendar.onChange = { [weak self] control in + guard let self else { return } + selectedDate = control.selectedDate + sendActions(for: .valueChanged) + UIAccessibility.post(notification: .layoutChanged, argument: containerView) + hidePopoverView() + } + + outsideTapGesture = UITapGestureRecognizer() + outsidePanGesture = UIPanGestureRecognizer() + + overlayView.publisher(for: outsideTapGesture!).sink { [weak self] _ in + guard let self else { return } + hidePopoverView() + }.store(in: &subscribers) + parentView.publisher(for: outsidePanGesture!).sink { [weak self] _ in + guard let self else { return } + hidePopoverView() + }.store(in: &subscribers) + + overlayView.frame = parentView.bounds + overlayView.isHidden = false + + parentView.addSubview(overlayView) + + popoverView = UIView() + popoverView.backgroundColor = .white + popoverView.layer.cornerRadius = 10 + popoverView.layer.shadowColor = UIColor.black.cgColor + popoverView.layer.shadowOpacity = 0.2 + popoverView.layer.shadowOffset = CGSize(width: 0, height: 5) + popoverView.layer.shadowRadius = 10 + popoverView.isHidden = true + popoverView.addSubview(calendar) + calendar.pinToSuperView() + popoverView.translatesAutoresizingMaskIntoConstraints = false + parentView.addSubview(popoverView) + + popoverView.width(calendar.frame.width) + popoverView.height(calendar.frame.height) + + let spacing: CGFloat = 4 + let (popoverX, popoverY) = calculatePopoverPosition(relativeTo: containerView, in: parentView, size: calendar.frame.size, with: spacing) + popoverView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: popoverX).isActive = true + popoverView.topAnchor.constraint(equalTo: parentView.topAnchor, constant: popoverY).isActive = true + + parentView.layoutIfNeeded() + popoverView.alpha = 0 + popoverView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) + popoverView.isHidden = false + popoverVisible = true - popoverController = ClearPopoverViewController(contentView: calendar, - arrow: .up, - sourceView: containerView, - sourceRect: containerView.bounds, - spacing: VDSLayout.space1X) - - if let viewController = UIApplication.topViewController(), let popoverController { - viewController.present(popoverController, - animated: true, - completion: nil) + UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.2, options: .curveEaseOut, animations: { [weak self] in + guard let self else { return } + popoverView.alpha = 1 + popoverView.transform = CGAffineTransform.identity + parentView.layoutIfNeeded() + }) } } + + private func hidePopoverView() { + overlayView.isHidden = true + overlayView.removeFromSuperview() + + outsideTapGesture = nil + outsidePanGesture = nil + UIView.animate(withDuration: 0.2, animations: { + self.popoverView.alpha = 0 + self.popoverView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) + }) { _ in + self.popoverView.isHidden = true + self.popoverView.removeFromSuperview() + self.popoverVisible = false + } + } + + private func calculatePopoverPosition(relativeTo sourceView: UIView, in parentView: UIView, size: CGSize, with spacing: CGFloat) -> (CGFloat, CGFloat) { + let sourceFrameInParent = sourceView.convert(sourceView.bounds, to: parentView) + let parentBounds = parentView.bounds + let popoverWidth: CGFloat = size.width + let popoverHeight: CGFloat = size.height + + var popoverX: CGFloat = 0 + var popoverY: CGFloat = 0 + + // Calculate horizontal position + if sourceFrameInParent.width < popoverWidth { + if sourceFrameInParent.midX - popoverWidth / 2 < 0 { + // Align to left + popoverX = sourceFrameInParent.minX + } else if sourceFrameInParent.midX + popoverWidth / 2 > parentBounds.width { + // Align to right + popoverX = sourceFrameInParent.maxX - popoverWidth + } else { + // Center on source view + popoverX = sourceFrameInParent.midX - popoverWidth / 2 + } + } else { + popoverX = sourceFrameInParent.midX - popoverWidth / 2 + } + + // Calculate vertical position + if sourceFrameInParent.origin.y > parentBounds.height / 2 { + // Show above + popoverY = sourceFrameInParent.minY - popoverHeight - spacing + } else { + // Show below + popoverY = sourceFrameInParent.maxY + spacing + } + + // Ensure the popover is within the parent's bounds + popoverX = max(0, min(popoverX, parentBounds.width - popoverWidth)) + + return (popoverX, popoverY) + } } From eaf6d68ab7f426baf4bd376ce5fb939e22f696d2 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 21 Jun 2024 14:10:16 -0500 Subject: [PATCH 018/117] reverted to using kevin's old code for actions Signed-off-by: Matt Bruce --- VDS/Components/Label/Label.swift | 30 +++++++++++++ VDS/Extensions/UITapGestureRecognizer.swift | 47 +++++++++++++++------ 2 files changed, 63 insertions(+), 14 deletions(-) diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 4f56c272..eafd440a 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -402,6 +402,36 @@ open class Label: UILabel, ViewProtocol, UserInfoable { } } + /** + Provides a text container and layout manager of how the text would appear on screen. + They are used in tandem to derive low-level TextKit results of the label. + */ + public func abstractTextContainer() -> (textContainer: NSTextContainer, layoutManager: NSLayoutManager, textStorage: NSTextStorage)? { + + // Must configure the attributed string to translate what would appear on screen to accurately analyze. + guard let attributedText = attributedText else { return nil } + + let paragraph = NSMutableParagraphStyle() + paragraph.alignment = textAlignment + + let stagedAttributedString = NSMutableAttributedString(attributedString: attributedText) + stagedAttributedString.addAttributes([NSAttributedString.Key.paragraphStyle: paragraph], range: NSRange(location: 0, length: attributedText.string.count)) + + let textStorage = NSTextStorage(attributedString: stagedAttributedString) + let layoutManager = NSLayoutManager() + let textContainer = NSTextContainer(size: .zero) + + layoutManager.addTextContainer(textContainer) + textStorage.addLayoutManager(layoutManager) + + textContainer.lineFragmentPadding = 0.0 + textContainer.lineBreakMode = lineBreakMode + textContainer.maximumNumberOfLines = numberOfLines + textContainer.size = bounds.size + + return (textContainer, layoutManager, textStorage) + } + private func customAccessibilityAction(text: String?, range: NSRange, accessibleText: String? = nil) -> UIAccessibilityCustomAction? { guard let text = text, let attributedText else { return nil } diff --git a/VDS/Extensions/UITapGestureRecognizer.swift b/VDS/Extensions/UITapGestureRecognizer.swift index 612a11ca..93fd0d8f 100644 --- a/VDS/Extensions/UITapGestureRecognizer.swift +++ b/VDS/Extensions/UITapGestureRecognizer.swift @@ -15,20 +15,39 @@ extension UITapGestureRecognizer { /// - label: UILabel in question /// - targetRange: Range to look within /// - Returns: Wether the range in the label has an action - public func didTapActionInLabel(_ label: UILabel, inRange targetRange: NSRange) -> Bool { - - guard let attributedText = label.attributedText else { return false } - - let layoutManager = NSLayoutManager() - let textContainer = NSTextContainer(size: label.bounds.size) - let textStorage = NSTextStorage(attributedString: attributedText) - layoutManager.addTextContainer(textContainer) - textStorage.addLayoutManager(layoutManager) - + public func didTapActionInLabel(_ label: Label, inRange targetRange: NSRange) -> Bool { + guard let abstractContainer = label.abstractTextContainer() else { return false } let location = location(in: label) - let characterIndex = layoutManager.characterIndex(for: location, in: textContainer, fractionOfDistanceBetweenInsertionPoints: nil) - - guard let _ = attributedText.attribute(NSAttributedString.Key.action, at: characterIndex, effectiveRange: nil) as? String, characterIndex < attributedText.length else { return false } - return true + + let textContainer = abstractContainer.textContainer + let layoutManager = abstractContainer.layoutManager + + let indexOfGlyph = layoutManager.glyphIndex(for: location, in: textContainer) + let intrinsicWidth = label.intrinsicContentSize.width + + // Assert that tapped occured within acceptable bounds based on alignment. + switch label.textAlignment { + case .right: + if location.x < label.bounds.width - intrinsicWidth { + return false + } + case .center: + let halfBounds = label.bounds.width / 2 + let halfIntrinsicWidth = intrinsicWidth / 2 + + if location.x > halfBounds + halfIntrinsicWidth { + return false + } else if location.x < halfBounds - halfIntrinsicWidth { + return false + } + default: // Left align + if location.x > intrinsicWidth { + return false + } + } + + // Affirms that the tap occured in the desired rect of provided by the target range. + return layoutManager.boundingRect(forGlyphRange: targetRange, in: textContainer).contains(location) + && NSLocationInRange(indexOfGlyph, targetRange) } } From beaa2b3a82ba33afb79cc6614edc48c4f865eb6a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 21 Jun 2024 14:43:55 -0500 Subject: [PATCH 019/117] refactored out old code and reorganized Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 79 ++++++++-------------- 1 file changed, 27 insertions(+), 52 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 3ab83be1..e44c2a76 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -31,6 +31,14 @@ open class DatePicker: EntryFieldBase { // MARK: - Private Properties //-------------------------------------------------- internal var minWidthDefault = 186.0 + internal var popoverView: UIView! + internal var popoverVisible = false + internal var outsideTapGesture: UITapGestureRecognizer? + internal var outsidePanGesture: UIPanGestureRecognizer? + internal var overlayView = UIView().with { + $0.backgroundColor = .clear; + $0.isHidden = true + } internal var bottomStackView: UIStackView = { return UIStackView().with { @@ -109,8 +117,8 @@ open class DatePicker: EntryFieldBase { .publisher(for: UITapGestureRecognizer()) .sink { [weak self] _ in guard let self else { return } - if self.isEnabled && !self.isReadOnly { - self.togglePicker() + if isEnabled && !isReadOnly { + showPopover() } } .store(in: &subscribers) @@ -118,9 +126,6 @@ open class DatePicker: EntryFieldBase { NotificationCenter.default .publisher(for: UIDevice.orientationDidChangeNotification).sink { [weak self] _ in guard let self else { return } - popoverController?.dismiss(animated: true){ [weak self] in - guard let self else { return } - } hidePopoverView() } .store(in: &subscribers) @@ -175,49 +180,11 @@ open class DatePicker: EntryFieldBase { popoverController = nil } } - - private var overlayView = UIView().with { - $0.backgroundColor = .clear; - $0.isHidden = true - } - private var popoverView: UIView! - private var popoverVisible = false - private var outsideTapGesture: UITapGestureRecognizer? - private var outsidePanGesture: UIPanGestureRecognizer? - -// internal func togglePicker() { -// calendar.activeDates = calendarModel.activeDates -// calendar.hideContainerBorder = calendarModel.hideContainerBorder -// calendar.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator -// calendar.inactiveDates = calendarModel.inactiveDates -// calendar.indicators = calendarModel.indicators -// calendar.maxDate = calendarModel.maxDate -// calendar.minDate = calendarModel.minDate -// calendar.surface = calendarModel.surface -// calendar.setNeedsLayout() -// calendar.layoutIfNeeded() -// calendar.onChange = { [weak self] control in -// guard let self else { return } -// didSelect(control.selectedDate) -// } -// -// popoverController = ClearPopoverViewController(contentView: calendar, -// arrow: .any, -// sourceView: containerView, -// sourceRect: .init(x: 0, y: 0, width: 320, height: 45), -// spacing: VDSLayout.space1X) -// popoverController?.maxWidth = 320 -// if let viewController = UIApplication.topViewController(), let popoverController { -// viewController.present(popoverController, -// animated: true, -// completion: nil) -// } -// } } extension DatePicker { - private func togglePicker() { + private func showPopover() { guard let viewController = UIApplication.topViewController(), let parentView = viewController.view else { return } if popoverVisible { @@ -286,7 +253,12 @@ extension DatePicker { popoverView.isHidden = false popoverVisible = true - UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.2, options: .curveEaseOut, animations: { [weak self] in + UIView.animate(withDuration: 0.3, + delay: 0, + usingSpringWithDamping: 0.8, + initialSpringVelocity: 0.2, + options: .curveEaseOut, + animations: { [weak self] in guard let self else { return } popoverView.alpha = 1 popoverView.transform = CGAffineTransform.identity @@ -301,13 +273,16 @@ extension DatePicker { outsideTapGesture = nil outsidePanGesture = nil - UIView.animate(withDuration: 0.2, animations: { - self.popoverView.alpha = 0 - self.popoverView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) - }) { _ in - self.popoverView.isHidden = true - self.popoverView.removeFromSuperview() - self.popoverVisible = false + UIView.animate(withDuration: 0.2, + animations: {[weak self] in + guard let self else { return } + popoverView.alpha = 0 + popoverView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) + }) { [weak self] _ in + guard let self else { return } + popoverView.isHidden = true + popoverView.removeFromSuperview() + popoverVisible = false } } From c00ddc68fd2d37c92641294241879a9df7746faf Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Mon, 24 Jun 2024 16:31:00 +0530 Subject: [PATCH 020/117] Digital ACT-191 ONEAPP-7013 story: changes to pagination inset --- VDS/Components/Carousel/Carousel.swift | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 4ad8ef8c..6c091ce2 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -157,12 +157,11 @@ open class Carousel: View { } /// If provided, will apply margin to pagination arrows. Can be set to either positive or negative values. - /// The default value will be 12px in tablet and 8px in mobile. + /// The default value will be 12px in tablet and 8px in mobile. These values are the default in order to avoid overlapping content within the carousel. open var paginationInset: CGFloat { get { return _paginationInset } set { - let minValue = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X - _paginationInset = (newValue < minValue) ? minValue : newValue + _paginationInset = newValue updatePaginationInset() } } From 0b47d70352912b61b433ae44fe93841f63a8c210 Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Mon, 24 Jun 2024 17:04:36 +0530 Subject: [PATCH 021/117] Digital ACT-191 ONEAPP-7013 story: changes to the default view of the carousel with one peek visible --- VDS/Components/Carousel/Carousel.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 6c091ce2..378289c7 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -441,7 +441,13 @@ open class Carousel: View { minimumSlotWidth = actualWidth - (CGFloat(layout.value) * gutter.value) switch peek { case .standard: - minimumSlotWidth = minimumSlotWidth - (3*peekMinimum) + // Supported for all Tablet viewports and layouts. + // Supported only for 1up layouts on Mobile viewports. + if UIDevice.isIPad { + minimumSlotWidth = minimumSlotWidth - (minimumSlotWidth/(CGFloat(layout.value) + 2)) + } else if layout == .oneUP { + minimumSlotWidth = minimumSlotWidth - (minimumSlotWidth/4) + } case .minimum: minimumSlotWidth = minimumSlotWidth - peekMinimum case .none: From 0840ed5ed31819f27012c79fdd0ef9be7c210aba Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Mon, 24 Jun 2024 18:42:07 +0530 Subject: [PATCH 022/117] Digital ACT-191 ONEAPP-7013 story: Adjust slot width if scrollbar suppressed and/or based on peek value --- VDS/Components/Carousel/Carousel.swift | 42 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 378289c7..80c55b16 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -247,7 +247,7 @@ open class Carousel: View { internal var _paginationDisplay: PaginationDisplay = .none internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX - internal var _peek: Peek = .none + internal var _peek: Peek = .standard internal var _numberOfSlides: Int = 1 private var _width: Width? = nil @@ -324,11 +324,17 @@ open class Carousel: View { carouselScrollBar.position = (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) ? 1 : carouselScrollBar.position carouselScrollBar.isHidden = (totalPositions() <= 1) ? true : false - // When peek is set to ‘none,’ pagination controls are automatically set to persistent. + // Mobile/Tablet layouts without peek - must show pagination controls. + // If peek is ‘none’, pagination controls should show. So set to persistent. if peek == .none { paginationDisplay = .persistent } + // Minimum (Mobile only) Supported only on Mobile viewports. If a user passes Minimum for tablet carousel, the peek reverts to Standard. + if UIDevice.isIPad && peek == .minimum { + peek = .standard + } + containerViewHeightConstraint?.isActive = false containerStackHeightConstraint?.isActive = false updatePaginationControls() @@ -438,20 +444,26 @@ open class Carousel: View { func getSlotWidth() { let actualWidth = containerView.frame.size.width - minimumSlotWidth = actualWidth - (CGFloat(layout.value) * gutter.value) - switch peek { - case .standard: - // Supported for all Tablet viewports and layouts. - // Supported only for 1up layouts on Mobile viewports. - if UIDevice.isIPad { - minimumSlotWidth = minimumSlotWidth - (minimumSlotWidth/(CGFloat(layout.value) + 2)) - } else if layout == .oneUP { - minimumSlotWidth = minimumSlotWidth - (minimumSlotWidth/4) + let isScrollbarSuppressed = data.count > 0 && layout.value == data.count + let isPeekMinimumOnTablet = UIDevice.isIPad && peek == .minimum + let isPeekNone: Bool = peek == .none + minimumSlotWidth = isScrollbarSuppressed || isPeekMinimumOnTablet || isPeekNone ? actualWidth - ((CGFloat(layout.value)-1) * gutter.value): actualWidth - (CGFloat(layout.value) * gutter.value) + if !isScrollbarSuppressed { + switch peek { + case .standard: + // Standard(Default) Peek - Supported for all Tablet viewports and layouts. Supported only for 1up layouts on Mobile viewports. + if UIDevice.isIPad { + minimumSlotWidth = minimumSlotWidth - (minimumSlotWidth/(CGFloat(layout.value) + 3)) + } else if layout == .oneUP { + minimumSlotWidth = minimumSlotWidth - (minimumSlotWidth/4) + } + case .minimum: + // Peek Mimumum Width: 24px from edge of container (at the default view of the carousel with one peek visible) + // Minimum (Mobile only) Supported only on Mobile viewports. If a user passes Minimum for tablet carousel, the peek reverts to Standard. + minimumSlotWidth = isPeekMinimumOnTablet ? minimumSlotWidth : minimumSlotWidth - peekMinimum + case .none: + break } - case .minimum: - minimumSlotWidth = minimumSlotWidth - peekMinimum - case .none: - break } minimumSlotWidth = minimumSlotWidth / CGFloat(layout.value) } From 891a816f557dbc0e16932853c5cb7fe190f3d4eb Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 24 Jun 2024 15:33:47 -0500 Subject: [PATCH 023/117] refactored base for responder Signed-off-by: Matt Bruce --- VDS/Components/DropdownSelect/DropdownSelect.swift | 14 +------------- VDS/Components/TextFields/EntryFieldBase.swift | 3 +++ .../TextFields/InputField/InputField.swift | 6 +----- VDS/Components/TextFields/TextArea/TextArea.swift | 13 +------------ 4 files changed, 6 insertions(+), 30 deletions(-) diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index 7fc52808..832349d2 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -30,19 +30,7 @@ open class DropdownSelect: EntryFieldBase { //-------------------------------------------------- // MARK: - Public Properties - //-------------------------------------------------- - /// Override UIControl state to add the .error state if showSuccess is true and if showError is true. - open override var state: UIControl.State { - get { - var state = super.state - if dropdownField.isFirstResponder { - state.insert(.focused) - } - - return state - } - } - + //-------------------------------------------------- /// If true, the label will be displayed inside the dropdown containerView. Otherwise, the label will be above the dropdown containerView like a normal text input. open var showInlineLabel: Bool = false { didSet { setNeedsUpdate() }} diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 2ef89480..e4aeddc2 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -207,6 +207,9 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { if isReadOnly { state.insert(.readonly) } + if let responder, responder.isFirstResponder { + state.insert(.focused) + } } return state } diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index c2f779d4..2ca4c207 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -166,11 +166,7 @@ open class InputField: EntryFieldBase { if showSuccess { state.insert(.success) } - - if textField.isFirstResponder { - state.insert(.focused) - } - + return state } } diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index a487dc53..d0396d7a 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -56,18 +56,7 @@ open class TextArea: EntryFieldBase { //-------------------------------------------------- // MARK: - Public Properties - //-------------------------------------------------- - /// Override UIControl state to add the .error state if showSuccess is true and if showError is true. - open override var state: UIControl.State { - get { - var state = super.state - if textView.isFirstResponder { - state.insert(.focused) - } - return state - } - } - + //-------------------------------------------------- override var containerSize: CGSize { CGSize(width: 182, height: Height.twoX.value) } /// Enum used to describe the the height of TextArea. From 32a44a51b5dbd967f80d0584a1e88b56aa4874f0 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 24 Jun 2024 15:34:02 -0500 Subject: [PATCH 024/117] updated date picker Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 106 ++++++++++++--------- 1 file changed, 59 insertions(+), 47 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index e44c2a76..991167e1 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -30,8 +30,10 @@ open class DatePicker: EntryFieldBase { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- + internal override var responder: UIResponder? { hiddenView } + internal var hiddenView = UITextView().with { $0.width(0) } internal var minWidthDefault = 186.0 - internal var popoverView: UIView! + internal var popoverView: UIScrollView! internal var popoverVisible = false internal var outsideTapGesture: UITapGestureRecognizer? internal var outsidePanGesture: UIPanGestureRecognizer? @@ -141,6 +143,7 @@ open class DatePicker: EntryFieldBase { } controlStackView.addArrangedSubview(calendarIcon) controlStackView.addArrangedSubview(selectedDateLabel) + controlStackView.addArrangedSubview(hiddenView) return controlStackView } @@ -168,25 +171,11 @@ open class DatePicker: EntryFieldBase { formatter.dateFormat = dateFormat.format selectedDateLabel.text = formatter.string(from: date) } - - internal var popoverController: ClearPopoverViewController? - - func didSelect(_ date: Date) { - selectedDate = date - sendActions(for: .valueChanged) - UIAccessibility.post(notification: .layoutChanged, argument: self.containerView) - popoverController?.dismiss(animated: true){ [weak self] in - guard let self else { return } - popoverController = nil - } - } } extension DatePicker { - private func showPopover() { guard let viewController = UIApplication.topViewController(), let parentView = viewController.view else { return } - if popoverVisible { hidePopoverView() } else { @@ -226,34 +215,33 @@ extension DatePicker { parentView.addSubview(overlayView) - popoverView = UIView() - popoverView.backgroundColor = .white - popoverView.layer.cornerRadius = 10 - popoverView.layer.shadowColor = UIColor.black.cgColor - popoverView.layer.shadowOpacity = 0.2 - popoverView.layer.shadowOffset = CGSize(width: 0, height: 5) - popoverView.layer.shadowRadius = 10 + popoverView = UIScrollView() + popoverView.backgroundColor = .green + popoverView.clipsToBounds = true + popoverView.backgroundColor = .clear popoverView.isHidden = true popoverView.addSubview(calendar) calendar.pinToSuperView() - popoverView.translatesAutoresizingMaskIntoConstraints = false parentView.addSubview(popoverView) - popoverView.width(calendar.frame.width) - popoverView.height(calendar.frame.height) let spacing: CGFloat = 4 - let (popoverX, popoverY) = calculatePopoverPosition(relativeTo: containerView, in: parentView, size: calendar.frame.size, with: spacing) - popoverView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor, constant: popoverX).isActive = true - popoverView.topAnchor.constraint(equalTo: parentView.topAnchor, constant: popoverY).isActive = true + let popoverSize: CGSize = .init(width: calendar.frame.width, height: calendar.frame.height) + popoverView.contentSize = CGSize(width: popoverSize.width, height: popoverSize.height) + + let (popoverX, popoverY, adjustedHeight) = calculatePopoverPosition(relativeTo: containerView, in: parentView, size: popoverSize, with: spacing) + //let adjustedX = adjustedHeight != popoverSize.height ? popoverX - 10 : popoverX + let adjustedWidth = adjustedHeight != popoverSize.height ? popoverSize.width + 10 : popoverSize.width + popoverView.frame = CGRect(x: popoverX, y: popoverY, width: adjustedWidth, height: adjustedHeight) parentView.layoutIfNeeded() popoverView.alpha = 0 popoverView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) popoverView.isHidden = false popoverVisible = true - - UIView.animate(withDuration: 0.3, + _ = responder?.becomeFirstResponder() + updateContainerView() + UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.2, @@ -262,6 +250,10 @@ extension DatePicker { guard let self else { return } popoverView.alpha = 1 popoverView.transform = CGAffineTransform.identity + if popoverSize.height > adjustedHeight { + popoverView.flashScrollIndicators() + } + UIAccessibility.post(notification: .layoutChanged, argument: calendar) parentView.layoutIfNeeded() }) } @@ -270,10 +262,9 @@ extension DatePicker { private func hidePopoverView() { overlayView.isHidden = true overlayView.removeFromSuperview() - outsideTapGesture = nil outsidePanGesture = nil - UIView.animate(withDuration: 0.2, + UIView.animate(withDuration: 0.2, animations: {[weak self] in guard let self else { return } popoverView.alpha = 0 @@ -283,17 +274,23 @@ extension DatePicker { popoverView.isHidden = true popoverView.removeFromSuperview() popoverVisible = false + responder?.resignFirstResponder() + setNeedsUpdate() + UIAccessibility.post(notification: .layoutChanged, argument: containerView) } + } - - private func calculatePopoverPosition(relativeTo sourceView: UIView, in parentView: UIView, size: CGSize, with spacing: CGFloat) -> (CGFloat, CGFloat) { + + private func calculatePopoverPosition(relativeTo sourceView: UIView, in parentView: UIView, size: CGSize, with spacing: CGFloat) -> (CGFloat, CGFloat, CGFloat) { let sourceFrameInParent = sourceView.convert(sourceView.bounds, to: parentView) let parentBounds = parentView.bounds - let popoverWidth: CGFloat = size.width - let popoverHeight: CGFloat = size.height + let safeAreaInsets = parentView.safeAreaInsets + let popoverWidth = size.width + let popoverHeight = size.height var popoverX: CGFloat = 0 var popoverY: CGFloat = 0 + var adjustedHeight = popoverHeight // Calculate horizontal position if sourceFrameInParent.width < popoverWidth { @@ -311,19 +308,34 @@ extension DatePicker { popoverX = sourceFrameInParent.midX - popoverWidth / 2 } - // Calculate vertical position - if sourceFrameInParent.origin.y > parentBounds.height / 2 { - // Show above - popoverY = sourceFrameInParent.minY - popoverHeight - spacing - } else { - // Show below - popoverY = sourceFrameInParent.maxY + spacing - } - - // Ensure the popover is within the parent's bounds + // Ensure the popover is within the parent's bounds horizontally popoverX = max(0, min(popoverX, parentBounds.width - popoverWidth)) - return (popoverX, popoverY) + // Calculate vertical position and height + let availableSpaceAbove = sourceFrameInParent.minY - safeAreaInsets.top - spacing + let availableSpaceBelow = parentBounds.height - sourceFrameInParent.maxY - safeAreaInsets.bottom - spacing + let totalAvailableHeight = parentBounds.height - safeAreaInsets.top - safeAreaInsets.bottom + + if availableSpaceAbove >= popoverHeight { + // Show above without adjusting height + popoverY = sourceFrameInParent.minY - popoverHeight - spacing + } else if availableSpaceBelow >= popoverHeight { + // Show below without adjusting height + popoverY = sourceFrameInParent.maxY + spacing + + } else if totalAvailableHeight >= popoverHeight { + // check if the total + if availableSpaceAbove > availableSpaceBelow { + popoverY = safeAreaInsets.top + } else { + popoverY = parentBounds.height - safeAreaInsets.bottom - popoverHeight + } + } else { + popoverY = safeAreaInsets.top + adjustedHeight = totalAvailableHeight + } + + return (popoverX, popoverY, adjustedHeight) } } From 7cd9a7d1a93c0ed9b10d14e97a3006b08b9cec43 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 11:08:28 -0500 Subject: [PATCH 025/117] added alertViewController Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 4 + VDS/Classes/AlertViewController.swift | 101 ++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) create mode 100644 VDS/Classes/AlertViewController.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 7c9565f7..bf8fbc94 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -174,6 +174,7 @@ EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9A29DB1A6000101452 /* Changeable.swift */; }; EAF2F4762C231EAA007BFEDC /* AccessibilityActionElement.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */; }; EAF2F4782C249D72007BFEDC /* AccessibilityUpdatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF2F4772C249D72007BFEDC /* AccessibilityUpdatable.swift */; }; + EAF2F4892C2A1075007BFEDC /* AlertViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF2F4882C2A1075007BFEDC /* AlertViewController.swift */; }; EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0932899861000B287F5 /* CheckboxItem.swift */; }; EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0992899B17200B287F5 /* CATransaction.swift */; }; EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F09F289AB7EC00B287F5 /* View.swift */; }; @@ -402,6 +403,7 @@ EAF1FE9A29DB1A6000101452 /* Changeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Changeable.swift; sourceTree = ""; }; EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityActionElement.swift; sourceTree = ""; }; EAF2F4772C249D72007BFEDC /* AccessibilityUpdatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityUpdatable.swift; sourceTree = ""; }; + EAF2F4882C2A1075007BFEDC /* AlertViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlertViewController.swift; sourceTree = ""; }; EAF7F0932899861000B287F5 /* CheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CheckboxItem.swift; sourceTree = ""; }; EAF7F0992899B17200B287F5 /* CATransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATransaction.swift; sourceTree = ""; }; EAF7F09F289AB7EC00B287F5 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; @@ -750,6 +752,7 @@ EAF7F0B8289C139800B287F5 /* ColorConfiguration.swift */, EAB5FEF02927F4AA00998C17 /* SelfSizingCollectionView.swift */, EAF2F4752C231EAA007BFEDC /* AccessibilityActionElement.swift */, + EAF2F4882C2A1075007BFEDC /* AlertViewController.swift */, ); path = Classes; sourceTree = ""; @@ -1310,6 +1313,7 @@ EA0D1C3B2A6AD51B00E5C127 /* Typogprahy+Styles.swift in Sources */, EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */, EAC58C162BED0E0300BA39FA /* InlineAction.swift in Sources */, + EAF2F4892C2A1075007BFEDC /* AlertViewController.swift in Sources */, EA0D1C3D2A6AD57600E5C127 /* Typography+Enums.swift in Sources */, EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */, EAC58C0C2BED01D500BA39FA /* Telephone.swift in Sources */, diff --git a/VDS/Classes/AlertViewController.swift b/VDS/Classes/AlertViewController.swift new file mode 100644 index 00000000..71a1c6d9 --- /dev/null +++ b/VDS/Classes/AlertViewController.swift @@ -0,0 +1,101 @@ +// +// AlertViewController.swift +// VDS +// +// Created by Matt Bruce on 6/24/24. +// + +import Foundation +import UIKit +import Combine +import VDSCoreTokens + +open class AlertViewController: UIViewController, Surfaceable { + + /// Set of Subscribers for any Publishers for this Control. + open var subscribers = Set() + + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + private var onClickSubscriber: AnyCancellable? { + willSet { + if let onClickSubscriber { + onClickSubscriber.cancel() + } + } + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + /// Current Surface and this is used to pass down to child objects that implement Surfacable + open var surface: Surface = .light { didSet { updateView() }} + open var presenter: UIView? { didSet { updateView() }} + open var dialog: UIView! + + //-------------------------------------------------- + // MARK: - Configuration + //-------------------------------------------------- + private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryDark, VDSColor.backgroundPrimaryLight) + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func viewDidLoad() { + super.viewDidLoad() + isModalInPresentation = true + setup() + } + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + UIAccessibility.post(notification: .screenChanged, argument: dialog) + } + + private func dismiss() { + dismiss(animated: true) { [weak self] in + guard let self, let presenter else { return } + UIAccessibility.post(notification: .layoutChanged, argument: presenter) + } + } + + open func setup() { + guard let dialog else { return } + view.accessibilityElements = [dialog] + + //left-right swipe + view.publisher(for: UISwipeGestureRecognizer().with{ $0.direction = .right }) + .sink { [weak self] swipe in + guard let self, !UIAccessibility.isVoiceOverRunning else { return } + self.dismiss() + }.store(in: &subscribers) + + //tapping in background + view.publisher(for: UITapGestureRecognizer().with{ $0.numberOfTapsRequired = 1 }) + .sink { [weak self] swipe in + guard let self, !UIAccessibility.isVoiceOverRunning else { return } + self.dismiss() + }.store(in: &subscribers) + + view.addSubview(dialog) + + // Activate constraints + NSLayoutConstraint.activate([ + // Constraints for the floating modal view + dialog.centerXAnchor.constraint(equalTo: view.centerXAnchor), + dialog.centerYAnchor.constraint(equalTo: view.centerYAnchor), + dialog.leadingAnchor.constraint(greaterThanOrEqualTo: view.leadingAnchor, constant: 10), + dialog.trailingAnchor.constraint(lessThanOrEqualTo: view.trailingAnchor, constant: -10), + dialog.topAnchor.constraint(greaterThanOrEqualTo: view.topAnchor, constant: 10), + dialog.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: -10) + ]) + } + + /// Used to make changes to the View based off a change events or from local properties. + open func updateView() { + view.backgroundColor = backgroundColorConfiguration.getColor(self).withAlphaComponent(0.3) + if var dialog = dialog as? Surfaceable { + dialog.surface = surface + } + } +} From 8757fe6147a40979bab21603d2f285e80249cab7 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 11:28:40 -0500 Subject: [PATCH 026/117] refactored popover controlling algo Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 304 +++++++++++++-------- 1 file changed, 183 insertions(+), 121 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 991167e1..e8d13b27 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -26,22 +26,13 @@ open class DatePicker: EntryFieldBase { //-------------------------------------------------- /// A callback when the selected option changes. Passes parameters (option). open var onDateSelected: ((Date, DatePicker) -> Void)? - + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- internal override var responder: UIResponder? { hiddenView } internal var hiddenView = UITextView().with { $0.width(0) } internal var minWidthDefault = 186.0 - internal var popoverView: UIScrollView! - internal var popoverVisible = false - internal var outsideTapGesture: UITapGestureRecognizer? - internal var outsidePanGesture: UIPanGestureRecognizer? - internal var overlayView = UIView().with { - $0.backgroundColor = .clear; - $0.isHidden = true - } - internal var bottomStackView: UIStackView = { return UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false @@ -51,6 +42,24 @@ open class DatePicker: EntryFieldBase { $0.spacing = VDSLayout.space2X } }() + + //-------------------------------------------------- + // MARK: - Private Popover/Alert Properties + //-------------------------------------------------- + /// View shown inline + internal var popoverView: UIView! + /// Size used for the popover + internal var popoverViewSize: CGSize = .zero + /// Spacing between the popover and the ContainerView when not a AlertViewController + internal var popoverSpacing: CGFloat = VDSLayout.space1X + /// Whether or not the popover is visible + internal var popoverVisible = false + /// If the ContainerView exists somewhere in the superview hierarch in a ScrollView. + internal var scrollView: UIScrollView? + /// Original Found ScrollView ContentSize, this will get reset back to this size when the Popover is removed. + internal var scrollViewContentSize: CGSize? + /// Presenting ViewController with showing the AlertViewController Version. + internal var topViewController: UIViewController? //-------------------------------------------------- // MARK: - Public Properties @@ -97,12 +106,12 @@ open class DatePicker: EntryFieldBase { } open var dateFormat: DateFormat = .shortNumeric { didSet{ setNeedsUpdate() } } - + //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- internal override var containerSize: CGSize { CGSize(width: minWidthDefault, height: 44) } - + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- @@ -110,10 +119,10 @@ open class DatePicker: EntryFieldBase { /// 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() - + // setting color config selectedDateLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() - + // tap gesture containerView .publisher(for: UITapGestureRecognizer()) @@ -131,7 +140,7 @@ open class DatePicker: EntryFieldBase { hidePopoverView() } .store(in: &subscribers) - + } open override func getFieldContainer() -> UIView { @@ -146,7 +155,7 @@ open class DatePicker: EntryFieldBase { controlStackView.addArrangedSubview(hiddenView) return controlStackView } - + /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() @@ -159,7 +168,7 @@ open class DatePicker: EntryFieldBase { selectedDateLabel.isEnabled = isEnabled calendarIcon.color = iconColorConfiguration.getColor(self) } - + /// Resets to default settings. open override func reset() { super.reset() @@ -172,24 +181,45 @@ open class DatePicker: EntryFieldBase { selectedDateLabel.text = formatter.string(from: date) } } - + extension DatePicker { + private func showPopover() { - guard let viewController = UIApplication.topViewController(), let parentView = viewController.view else { return } - if popoverVisible { + guard let viewController = UIApplication.topViewController(), var parentView = viewController.view, !popoverVisible else { hidePopoverView() - } else { - let calendar = CalendarBase() - calendar.activeDates = calendarModel.activeDates - calendar.hideContainerBorder = calendarModel.hideContainerBorder - calendar.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator - calendar.inactiveDates = calendarModel.inactiveDates - calendar.indicators = calendarModel.indicators - calendar.maxDate = calendarModel.maxDate - calendar.minDate = calendarModel.minDate - calendar.surface = calendarModel.surface - calendar.setNeedsLayout() - calendar.layoutIfNeeded() + return + } + + let calendar = CalendarBase() + calendar.activeDates = calendarModel.activeDates + calendar.hideContainerBorder = calendarModel.hideContainerBorder + calendar.hideCurrentDateIndicator = calendarModel.hideCurrentDateIndicator + calendar.inactiveDates = calendarModel.inactiveDates + calendar.indicators = calendarModel.indicators + calendar.maxDate = calendarModel.maxDate + calendar.minDate = calendarModel.minDate + calendar.surface = calendarModel.surface + calendar.setNeedsLayout() + calendar.layoutIfNeeded() + + //size the popover + popoverViewSize = .init(width: calendar.frame.width, height: calendar.frame.height) + + //find scrollView + if scrollView == nil { + scrollView = findScrollView(from: containerView) + scrollViewContentSize = scrollView?.contentSize + } + + if let scrollView { + parentView = scrollView + } + + // see if you should use the popover or show an alert + if let popoverOrigin = try? calculatePopoverPosition(relativeTo: containerView, + in: parentView, + size: popoverViewSize, + with: popoverSpacing) { calendar.onChange = { [weak self] control in guard let self else { return } selectedDate = control.selectedDate @@ -197,51 +227,28 @@ extension DatePicker { UIAccessibility.post(notification: .layoutChanged, argument: containerView) hidePopoverView() } - - outsideTapGesture = UITapGestureRecognizer() - outsidePanGesture = UIPanGestureRecognizer() - - overlayView.publisher(for: outsideTapGesture!).sink { [weak self] _ in - guard let self else { return } - hidePopoverView() - }.store(in: &subscribers) - parentView.publisher(for: outsidePanGesture!).sink { [weak self] _ in - guard let self else { return } - hidePopoverView() - }.store(in: &subscribers) - - overlayView.frame = parentView.bounds - overlayView.isHidden = false - - parentView.addSubview(overlayView) - - popoverView = UIScrollView() - popoverView.backgroundColor = .green - popoverView.clipsToBounds = true + + // popoverView container + popoverView = UIView() popoverView.backgroundColor = .clear - popoverView.isHidden = true - popoverView.addSubview(calendar) - calendar.pinToSuperView() - parentView.addSubview(popoverView) - - - let spacing: CGFloat = 4 - let popoverSize: CGSize = .init(width: calendar.frame.width, height: calendar.frame.height) - popoverView.contentSize = CGSize(width: popoverSize.width, height: popoverSize.height) - - let (popoverX, popoverY, adjustedHeight) = calculatePopoverPosition(relativeTo: containerView, in: parentView, size: popoverSize, with: spacing) - //let adjustedX = adjustedHeight != popoverSize.height ? popoverX - 10 : popoverX - let adjustedWidth = adjustedHeight != popoverSize.height ? popoverSize.width + 10 : popoverSize.width - popoverView.frame = CGRect(x: popoverX, y: popoverY, width: adjustedWidth, height: adjustedHeight) - - parentView.layoutIfNeeded() + popoverView.frame = CGRect(x: popoverOrigin.x, y: popoverOrigin.y, width: calendar.frame.width, height: calendar.frame.height) popoverView.alpha = 0 popoverView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) - popoverView.isHidden = false popoverVisible = true + popoverView.addSubview(calendar) + + calendar.pinToSuperView() + + // add views + parentView.addSubview(popoverView) + parentView.layoutIfNeeded() + + // update containerview _ = responder?.becomeFirstResponder() updateContainerView() - UIView.animate(withDuration: 0.3, + + // animate the calendar to show + UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.2, @@ -250,48 +257,87 @@ extension DatePicker { guard let self else { return } popoverView.alpha = 1 popoverView.transform = CGAffineTransform.identity - if popoverSize.height > adjustedHeight { - popoverView.flashScrollIndicators() - } UIAccessibility.post(notification: .layoutChanged, argument: calendar) parentView.layoutIfNeeded() }) - } - } - - private func hidePopoverView() { - overlayView.isHidden = true - overlayView.removeFromSuperview() - outsideTapGesture = nil - outsidePanGesture = nil - UIView.animate(withDuration: 0.2, - animations: {[weak self] in - guard let self else { return } - popoverView.alpha = 0 - popoverView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) - }) { [weak self] _ in - guard let self else { return } - popoverView.isHidden = true - popoverView.removeFromSuperview() - popoverVisible = false - responder?.resignFirstResponder() - setNeedsUpdate() - UIAccessibility.post(notification: .layoutChanged, argument: containerView) + + } else { + let dialog = UIScrollView() + dialog.translatesAutoresizingMaskIntoConstraints = false + dialog.addSubview(calendar) + dialog.backgroundColor = .clear + dialog.contentSize = .init(width: calendar.frame.width + 20, height: calendar.frame.width + 20) + dialog.width(calendar.frame.width + 20) + dialog.height(calendar.frame.height + 20) + calendar.pinToSuperView(.uniform(10)) + calendar.onChange = { [weak self] control in + guard let self else { return } + selectedDate = control.selectedDate + sendActions(for: .valueChanged) + UIAccessibility.post(notification: .layoutChanged, argument: containerView) + viewController.dismiss(animated: true) + } + + let alert = AlertViewController().with { + $0.dialog = dialog + $0.modalPresentationStyle = .overCurrentContext + $0.modalTransitionStyle = .crossDissolve + } + topViewController = viewController + viewController.present(alert, animated: true){ + dialog.flashScrollIndicators() + } } } - - private func calculatePopoverPosition(relativeTo sourceView: UIView, in parentView: UIView, size: CGSize, with spacing: CGFloat) -> (CGFloat, CGFloat, CGFloat) { + + private func hidePopoverView() { + if topViewController != nil { + topViewController?.dismiss(animated: true) + topViewController = nil + } else { + UIView.animate(withDuration: 0.2, + animations: {[weak self] in + guard let self, let popoverView else { return } + popoverView.alpha = 0 + popoverView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) + if let scrollView, let scrollViewContentSize { + scrollView.contentSize = scrollViewContentSize + } + + }) { [weak self] _ in + guard let self, let popoverView else { return } + popoverView.isHidden = true + popoverView.removeFromSuperview() + popoverVisible = false + responder?.resignFirstResponder() + setNeedsUpdate() + UIAccessibility.post(notification: .layoutChanged, argument: containerView) + } + } + } + + private func findScrollView(from view: UIView) -> UIScrollView? { + var currentView = view + while let superview = currentView.superview { + if let scrollView = superview as? UIScrollView { + return scrollView + } + currentView = superview + } + return nil + } + + private func calculatePopoverPosition(relativeTo sourceView: UIView, in parentView: UIView, size: CGSize, with spacing: CGFloat) throws -> CGPoint? { let sourceFrameInParent = sourceView.convert(sourceView.bounds, to: parentView) let parentBounds = parentView.bounds let safeAreaInsets = parentView.safeAreaInsets let popoverWidth = size.width let popoverHeight = size.height - + var popoverX: CGFloat = 0 var popoverY: CGFloat = 0 - var adjustedHeight = popoverHeight - + // Calculate horizontal position if sourceFrameInParent.width < popoverWidth { if sourceFrameInParent.midX - popoverWidth / 2 < 0 { @@ -307,35 +353,51 @@ extension DatePicker { } else { popoverX = sourceFrameInParent.midX - popoverWidth / 2 } - + // Ensure the popover is within the parent's bounds horizontally popoverX = max(0, min(popoverX, parentBounds.width - popoverWidth)) - - // Calculate vertical position and height - let availableSpaceAbove = sourceFrameInParent.minY - safeAreaInsets.top - spacing - let availableSpaceBelow = parentBounds.height - sourceFrameInParent.maxY - safeAreaInsets.bottom - spacing - let totalAvailableHeight = parentBounds.height - safeAreaInsets.top - safeAreaInsets.bottom - if availableSpaceAbove >= popoverHeight { - // Show above without adjusting height - popoverY = sourceFrameInParent.minY - popoverHeight - spacing - } else if availableSpaceBelow >= popoverHeight { - // Show below without adjusting height - popoverY = sourceFrameInParent.maxY + spacing + var availableSpaceAbove: CGFloat = 0.0 + var availableSpaceBelow: CGFloat = 0.0 + + /// if the scrollView is set we want to change how we calculate the containerView's position + if var scrollView = parentView as? UIScrollView { + // Calculate vertical position and height + availableSpaceAbove = sourceFrameInParent.minY - scrollView.bounds.minY - spacing + availableSpaceBelow = scrollView.bounds.maxY - sourceFrameInParent.maxY - spacing - } else if totalAvailableHeight >= popoverHeight { - // check if the total if availableSpaceAbove > availableSpaceBelow { - popoverY = safeAreaInsets.top + // Show above + popoverY = sourceFrameInParent.minY - popoverHeight - spacing } else { - popoverY = parentBounds.height - safeAreaInsets.bottom - popoverHeight + // Show below + popoverY = sourceFrameInParent.maxY + spacing + + // See if we need to expand the contentSize of the ScrollView + let diff = scrollView.contentSize.height - sourceFrameInParent.maxY + if diff < popoverHeight { + scrollView.contentSize.height += popoverHeight - diff + VDSLayout.space4X + } } + } else { - popoverY = safeAreaInsets.top - adjustedHeight = totalAvailableHeight + // Calculate vertical position and height + availableSpaceAbove = sourceFrameInParent.minY - safeAreaInsets.top - spacing + availableSpaceBelow = parentBounds.height - sourceFrameInParent.maxY - safeAreaInsets.bottom - spacing + + if availableSpaceAbove >= popoverHeight { + // Show above + popoverY = sourceFrameInParent.minY - popoverHeight - spacing + + } else if availableSpaceBelow >= popoverHeight { + // Show below + popoverY = sourceFrameInParent.maxY + spacing + + } else { + return nil + } } - - return (popoverX, popoverY, adjustedHeight) + + return .init(x: popoverX, y: popoverY) } } - From 67e055878d1be6d7dd2d0915b9b50fc71d49e2d3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 13:36:56 -0500 Subject: [PATCH 027/117] fixed bug in accessibilityLabel Signed-off-by: Matt Bruce --- VDS/Components/Buttons/ButtonBase.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/VDS/Components/Buttons/ButtonBase.swift b/VDS/Components/Buttons/ButtonBase.swift index d7b80c8a..720fca36 100644 --- a/VDS/Components/Buttons/ButtonBase.swift +++ b/VDS/Components/Buttons/ButtonBase.swift @@ -114,6 +114,11 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { titleLabel?.adjustsFontSizeToFitWidth = false titleLabel?.lineBreakMode = .byTruncatingTail titleLabel?.numberOfLines = 1 + + bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return nil } + return text + } } open func updateView() { From 3589e1230a7ab990e8a1badb58a0c0839aa02096 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 13:37:45 -0500 Subject: [PATCH 028/117] removed throws Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index e8d13b27..0ae5f47c 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -328,7 +328,7 @@ extension DatePicker { return nil } - private func calculatePopoverPosition(relativeTo sourceView: UIView, in parentView: UIView, size: CGSize, with spacing: CGFloat) throws -> CGPoint? { + private func calculatePopoverPosition(relativeTo sourceView: UIView, in parentView: UIView, size: CGSize, with spacing: CGFloat) -> CGPoint? { let sourceFrameInParent = sourceView.convert(sourceView.bounds, to: parentView) let parentBounds = parentView.bounds let safeAreaInsets = parentView.safeAreaInsets @@ -394,6 +394,8 @@ extension DatePicker { popoverY = sourceFrameInParent.maxY + spacing } else { + + //return nil since there is no way we can show the popover without a scrollview return nil } } From 1f0ba0cee6958851d9214760b012bc30f2ad8660 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 13:37:59 -0500 Subject: [PATCH 029/117] made icon accessible Signed-off-by: Matt Bruce --- VDS/Components/TextFields/EntryFieldBase.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index e4aeddc2..bd9155d1 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -183,7 +183,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open var statusIcon: Icon = Icon().with { $0.size = .medium - $0.isAccessibilityElement = false + $0.isAccessibilityElement = true } open var labelText: String? { didSet { setNeedsUpdate() } } From 67a5663fc34f867d758821a82fef1afce9573580 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 13:38:16 -0500 Subject: [PATCH 030/117] made credit card image accessible Signed-off-by: Matt Bruce --- .../TextFields/InputField/FieldTypes/CreditCard.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift index ed457446..429b68be 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift @@ -125,7 +125,7 @@ extension InputField { class CreditCardHandler: FieldTypeHandler { static let shared = CreditCardHandler() - + private override init() { super.init() self.validateOnChange = false @@ -135,6 +135,7 @@ extension InputField { fileprivate func updateLeftImage(_ inputField: InputField) { let imageName = inputField.cardType.imageName(surface: inputField.surface) creditCardImageView.image = BundleManager.shared.image(for: imageName) + creditCardImageView.accessibilityLabel = inputField.cardType.rawValue } override func updateView(_ inputField: InputField) { @@ -148,14 +149,14 @@ extension InputField { inputField.textField.leftView = iconContainerView inputField.textField.leftViewMode = .always - + updateLeftImage(inputField) } - + internal var creditCardImageView = UIImageView().with { $0.height(20) $0.width(32) - $0.isAccessibilityElement = false + $0.isAccessibilityElement = true $0.translatesAutoresizingMaskIntoConstraints = false $0.contentMode = .scaleAspectFill $0.clipsToBounds = true From 0ec165701bb776add76d89d873f4d6f1b86eec5e Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 13:39:18 -0500 Subject: [PATCH 031/117] CXTDT-577463 - InputField - Accessible Elements for actionLink/credit Card image, status image - fixed issue with success text Signed-off-by: Matt Bruce --- .../TextFields/InputField/InputField.swift | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 2ca4c207..575e843f 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -196,6 +196,33 @@ open class InputField: EntryFieldBase { borderColorConfiguration.setSurfaceColors(VDSColor.feedbackSuccessOnlight, VDSColor.feedbackSuccessOndark, forState: .success) textField.textColorConfiguration = textFieldTextColorConfiguration + + containerView.bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + var accessibilityLabels = [String]() + + if let text = titleLabel.text?.trimmingCharacters(in: .whitespaces) { + accessibilityLabels.append(text) + } + if isReadOnly { + accessibilityLabels.append("read only") + } + if !isEnabled { + accessibilityLabels.append("dimmed") + } + if let errorText, showError { + accessibilityLabels.append("error, \(errorText)") + } + + if let successText, showSuccess { + accessibilityLabels.append("success, \(successText)") + } + + accessibilityLabels.append("\(Self.self)") + + return accessibilityLabels.joined(separator: ", ") + } + } open override func getFieldContainer() -> UIView { @@ -260,11 +287,20 @@ open class InputField: EntryFieldBase { get { var elements = [Any]() elements.append(contentsOf: [titleLabel, containerView]) - if showError { + if let leftView = textField.leftView { + elements.append(leftView) + } + + if !statusIcon.isHidden{ elements.append(statusIcon) - if let errorText, !errorText.isEmpty { - elements.append(errorLabel) - } + } + + if !actionTextLink.isHidden { + elements.append(actionTextLink) + } + + if let errorText, !errorText.isEmpty, showError || hasInternalError { + elements.append(errorLabel) } else if showSuccess, let successText, !successText.isEmpty { elements.append(successLabel) } From 0981bf77297b6bf9451b5d8decfe59eb3f8ea70a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 13:46:00 -0500 Subject: [PATCH 032/117] updated logic for statusIcon label Signed-off-by: Matt Bruce --- VDS/Components/TextFields/EntryFieldBase.swift | 5 +++++ VDS/Components/TextFields/InputField/InputField.swift | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index bd9155d1..5bd50351 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -344,6 +344,11 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { guard let self else { return "" } return value } + + statusIcon.bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + return showError || hasInternalError ? "error" : nil + } } /// Updates the UI diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 575e843f..9adf34f2 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -223,6 +223,16 @@ open class InputField: EntryFieldBase { return accessibilityLabels.joined(separator: ", ") } + statusIcon.bridge_accessibilityLabelBlock = { [weak self] in + guard let self else { return "" } + if showError { + return "error" + } else if showSuccess { + return "success" + } else { + return nil + } + } } open override func getFieldContainer() -> UIView { From 649acb77af9417cfe6a9401b155cd2c6a4a2a92a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 13:56:03 -0500 Subject: [PATCH 033/117] CXTDT-577463 - InputField - Accessibility - Format Text Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 9adf34f2..f7f54896 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -204,12 +204,23 @@ open class InputField: EntryFieldBase { if let text = titleLabel.text?.trimmingCharacters(in: .whitespaces) { accessibilityLabels.append(text) } + + if let formatText = textField.formatText, !formatText.isEmpty { + accessibilityLabels.append("format, \(formatText)") + } + + if let placeholderText = textField.placeholder, !placeholderText.isEmpty { + accessibilityLabels.append("placeholder, \(placeholderText)") + } + if isReadOnly { accessibilityLabels.append("read only") } + if !isEnabled { accessibilityLabels.append("dimmed") } + if let errorText, showError { accessibilityLabels.append("error, \(errorText)") } From 6408d97841bfae778027a54ee02e54e0c51593f3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 13:57:41 -0500 Subject: [PATCH 034/117] updted release notes Signed-off-by: Matt Bruce --- VDS/SupportingFiles/ReleaseNotes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 8f13a9b0..484621ea 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,6 +1,7 @@ 1.0.68 ---------------- - CXTDT-553663 - DropdownSelect - Accessibility - has popup +- CXTDT-577463 - InputField - Accessibility 1.0.67 ---------------- From 4c4cac92c750cee19f2c5ac726a755359b24e2dd Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 13:58:07 -0500 Subject: [PATCH 035/117] more notes Signed-off-by: Matt Bruce --- VDS/SupportingFiles/ReleaseNotes.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 484621ea..cd86edbc 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,5 +1,6 @@ 1.0.68 ---------------- +- DatePicker - Refactored how this is shown - CXTDT-553663 - DropdownSelect - Accessibility - has popup - CXTDT-577463 - InputField - Accessibility From 83a0ff07b8b9b6f947b1451065c2d72a25a4c7ee Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 14:01:40 -0500 Subject: [PATCH 036/117] updated notes version Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 4 ++-- VDS/SupportingFiles/ReleaseNotes.txt | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index bf8fbc94..a5403c49 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -1535,7 +1535,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 67; + CURRENT_PROJECT_VERSION = 68; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1573,7 +1573,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 67; + CURRENT_PROJECT_VERSION = 68; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index cd86edbc..063d9422 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,6 +1,9 @@ 1.0.68 ---------------- - DatePicker - Refactored how this is shown +- Checkbox Item/Group - Accessibility Refactor +- Radiobox Item/Group - Accessibility Refactor +- Radiobutton Item/Group - Accessibility Refactor - CXTDT-553663 - DropdownSelect - Accessibility - has popup - CXTDT-577463 - InputField - Accessibility From 3ec982d45c546b254d4794b0d094a4c7c1d02465 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 14:29:55 -0500 Subject: [PATCH 037/117] fixed responder issue Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 0ae5f47c..b5231154 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -30,8 +30,14 @@ open class DatePicker: EntryFieldBase { //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- + class Responder: UIView { + open override var canBecomeFirstResponder: Bool { + true + } + } + internal override var responder: UIResponder? { hiddenView } - internal var hiddenView = UITextView().with { $0.width(0) } + internal var hiddenView = Responder().with { $0.width(0) } internal var minWidthDefault = 186.0 internal var bottomStackView: UIStackView = { return UIStackView().with { From 77fbec8bedb4783ffa45921d9715dc2cbb354a37 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 15:18:19 -0500 Subject: [PATCH 038/117] fixeds for the datePicker Signed-off-by: Matt Bruce --- VDS/Classes/AlertViewController.swift | 25 +++++------- VDS/Components/DatePicker/DatePicker.swift | 47 ++++++++++++++++++---- 2 files changed, 50 insertions(+), 22 deletions(-) diff --git a/VDS/Classes/AlertViewController.swift b/VDS/Classes/AlertViewController.swift index 71a1c6d9..30a5d5c6 100644 --- a/VDS/Classes/AlertViewController.swift +++ b/VDS/Classes/AlertViewController.swift @@ -62,21 +62,6 @@ open class AlertViewController: UIViewController, Surfaceable { open func setup() { guard let dialog else { return } view.accessibilityElements = [dialog] - - //left-right swipe - view.publisher(for: UISwipeGestureRecognizer().with{ $0.direction = .right }) - .sink { [weak self] swipe in - guard let self, !UIAccessibility.isVoiceOverRunning else { return } - self.dismiss() - }.store(in: &subscribers) - - //tapping in background - view.publisher(for: UITapGestureRecognizer().with{ $0.numberOfTapsRequired = 1 }) - .sink { [weak self] swipe in - guard let self, !UIAccessibility.isVoiceOverRunning else { return } - self.dismiss() - }.store(in: &subscribers) - view.addSubview(dialog) // Activate constraints @@ -90,6 +75,16 @@ open class AlertViewController: UIViewController, Surfaceable { dialog.bottomAnchor.constraint(lessThanOrEqualTo: view.bottomAnchor, constant: -10) ]) } + + open override func touchesBegan(_ touches: Set, with event: UIEvent?) { + guard let touch = touches.first else { return } + let location = touch.location(in: view) + if dialog.frame.contains(location) { + super.touchesBegan(touches, with: event) + } else { + dismiss() + } + } /// Used to make changes to the View based off a change events or from local properties. open func updateView() { diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index b5231154..cb2b5b88 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -52,6 +52,15 @@ open class DatePicker: EntryFieldBase { //-------------------------------------------------- // MARK: - Private Popover/Alert Properties //-------------------------------------------------- + /// View shown inline + internal var popoverOverlayView = UIView().with { + $0.backgroundColor = .clear + $0.translatesAutoresizingMaskIntoConstraints = false + } + + /// use this to track touch events outside of the popover in the overlay + internal var popupOverlayTapGesture: AnyCancellable? + /// View shown inline internal var popoverView: UIView! /// Size used for the popover @@ -147,6 +156,7 @@ open class DatePicker: EntryFieldBase { } .store(in: &subscribers) + popoverOverlayView.isHidden = true } open override func getFieldContainer() -> UIView { @@ -189,7 +199,7 @@ open class DatePicker: EntryFieldBase { } extension DatePicker { - + private func showPopover() { guard let viewController = UIApplication.topViewController(), var parentView = viewController.view, !popoverVisible else { hidePopoverView() @@ -220,12 +230,12 @@ extension DatePicker { if let scrollView { parentView = scrollView } - + // see if you should use the popover or show an alert - if let popoverOrigin = try? calculatePopoverPosition(relativeTo: containerView, - in: parentView, - size: popoverViewSize, - with: popoverSpacing) { + if let popoverOrigin = calculatePopoverPosition(relativeTo: containerView, + in: parentView, + size: popoverViewSize, + with: popoverSpacing) { calendar.onChange = { [weak self] control in guard let self else { return } selectedDate = control.selectedDate @@ -246,6 +256,16 @@ extension DatePicker { calendar.pinToSuperView() // add views + popoverOverlayView.isHidden = false + popupOverlayTapGesture = popoverOverlayView + .publisher(for: UITapGestureRecognizer()) + .sink(receiveValue: { [weak self] gesture in + guard let self else { return } + gestureEventOccured(gesture, parentView: parentView) + }) + + parentView.addSubview(popoverOverlayView) + popoverOverlayView.pinToSuperView() parentView.addSubview(popoverView) parentView.layoutIfNeeded() @@ -302,6 +322,11 @@ extension DatePicker { topViewController?.dismiss(animated: true) topViewController = nil } else { + popoverOverlayView.isHidden = true + popoverOverlayView.removeFromSuperview() + popupOverlayTapGesture?.cancel() + popupOverlayTapGesture = nil + UIView.animate(withDuration: 0.2, animations: {[weak self] in guard let self, let popoverView else { return } @@ -367,7 +392,7 @@ extension DatePicker { var availableSpaceBelow: CGFloat = 0.0 /// if the scrollView is set we want to change how we calculate the containerView's position - if var scrollView = parentView as? UIScrollView { + if let scrollView = parentView as? UIScrollView { // Calculate vertical position and height availableSpaceAbove = sourceFrameInParent.minY - scrollView.bounds.minY - spacing availableSpaceBelow = scrollView.bounds.maxY - sourceFrameInParent.maxY - spacing @@ -408,4 +433,12 @@ extension DatePicker { return .init(x: popoverX, y: popoverY) } + + private func gestureEventOccured(_ gesture: UIGestureRecognizer, parentView: UIView) { + guard let popoverView, popoverVisible else { return } + let location = gesture.location(in: parentView) + if !popoverView.frame.contains(location) { + hidePopoverView() + } + } } From 738dee681fc40e07d8010dee3cc5d8daaf3afde6 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 25 Jun 2024 15:20:44 -0500 Subject: [PATCH 039/117] updated release notes Signed-off-by: Matt Bruce --- VDS/SupportingFiles/ReleaseNotes.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 063d9422..54be0815 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,12 +1,16 @@ -1.0.68 +1.0.69 ---------------- - DatePicker - Refactored how this is shown -- Checkbox Item/Group - Accessibility Refactor +- Checkbox Item/Group - Accessibility Refactor - Radiobox Item/Group - Accessibility Refactor - Radiobutton Item/Group - Accessibility Refactor - CXTDT-553663 - DropdownSelect - Accessibility - has popup - CXTDT-577463 - InputField - Accessibility +1.0.69 +---------------- +- Expired Build because of a issue + 1.0.67 ---------------- - CXTDT-568463 - Calendar - On long press, hover randomizes From 9c32ea4870ed483c41e36f8f89f95d384455f595 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 26 Jun 2024 09:50:06 -0500 Subject: [PATCH 040/117] redo constraints Signed-off-by: Matt Bruce --- .../TileContainer/TileContainer.swift | 146 +++++++++++------- 1 file changed, 89 insertions(+), 57 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 546d8d05..b6014e8f 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -179,11 +179,16 @@ open class TileContainerBase: Control where Padding //-------------------------------------------------- internal var widthConstraint: NSLayoutConstraint? internal var heightConstraint: NSLayoutConstraint? - internal var heightGreaterThanConstraint: NSLayoutConstraint? - internal var containerTopConstraint: NSLayoutConstraint? - internal var containerBottomConstraint: NSLayoutConstraint? - internal var containerLeadingConstraint: NSLayoutConstraint? - internal var containerTrailingConstraint: NSLayoutConstraint? + + internal var containerViewBottomConstraint: NSLayoutConstraint? + internal var containerViewTrailingConstraint: NSLayoutConstraint? + internal var containerViewBottomLessThanOrEqualConstraint: NSLayoutConstraint? + internal var containerViewTrailingLessThanOrEqualConstraint: NSLayoutConstraint? + + internal var contentViewTopConstraint: NSLayoutConstraint? + internal var contentViewBottomConstraint: NSLayoutConstraint? + internal var contentViewLeadingConstraint: NSLayoutConstraint? + internal var contentViewTrailingConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Configuration @@ -221,29 +226,25 @@ open class TileContainerBase: Control where Padding open override func setup() { super.setup() isAccessibilityElement = false - - let layoutGuide = UILayoutGuide() - addLayoutGuide(layoutGuide) - layoutGuide + addSubview(containerView) + containerView.addSubview(backgroundImageView) + containerView.addSubview(contentView) + containerView.addSubview(highlightView) + + containerView .pinTop() .pinLeading() - .pinTrailing(0, .defaultHigh) - .pinBottom(0, .defaultHigh) - addSubview(backgroundImageView) - addSubview(containerView) - containerView.addSubview(contentView) - addSubview(highlightView) - - containerView.pinToSuperView() - widthConstraint = layoutGuide.widthAnchor.constraint(equalToConstant: 0) - - heightGreaterThanConstraint = layoutGuide.heightAnchor.constraint(greaterThanOrEqualToConstant: 44.0) - heightGreaterThanConstraint?.isActive = false - - heightConstraint = layoutGuide.heightAnchor.constraint(equalToConstant: 0) - + containerViewTrailingConstraint = containerView.pinTrailing(anchor: trailingAnchor)?.deactivate() + containerViewBottomConstraint = containerView.pinBottom(anchor: bottomAnchor)?.deactivate() + + containerViewTrailingLessThanOrEqualConstraint = containerView.pinTrailingLessThanOrEqualTo(anchor: trailingAnchor)?.deactivate() + containerViewBottomLessThanOrEqualConstraint = containerView.pinBottomLessThanOrEqualTo(anchor: bottomAnchor)?.deactivate() + backgroundImageView.pinToSuperView() + + widthConstraint = containerView.widthAnchor.constraint(equalToConstant: 0).deactivate() + heightConstraint = containerView.heightAnchor.constraint(equalToConstant: 0).deactivate() backgroundImageView.setContentHuggingPriority(.defaultLow, for: .horizontal) backgroundImageView.setContentHuggingPriority(.defaultLow, for: .vertical) @@ -252,20 +253,20 @@ open class TileContainerBase: Control where Padding backgroundImageView.isUserInteractionEnabled = false backgroundImageView.isHidden = true - containerTopConstraint = contentView.pinTop(anchor: layoutGuide.topAnchor, constant: padding.value) - containerBottomConstraint = layoutGuide.pinBottom(anchor: contentView.bottomAnchor, constant: padding.value) - containerLeadingConstraint = contentView.pinLeading(anchor: layoutGuide.leadingAnchor, constant: padding.value) - containerTrailingConstraint = layoutGuide.pinTrailing(anchor: contentView.trailingAnchor, constant: padding.value) + contentViewTopConstraint = contentView.pinTop(anchor: containerView.topAnchor, constant: padding.value) + contentViewBottomConstraint = containerView.pinBottom(anchor: contentView.bottomAnchor, constant: padding.value) + contentViewLeadingConstraint = contentView.pinLeading(anchor: containerView.leadingAnchor, constant: padding.value) + contentViewTrailingConstraint = containerView.pinTrailing(anchor: contentView.trailingAnchor, constant: padding.value) - highlightView.pin(layoutGuide) + highlightView.pin(containerView) highlightView.isHidden = true highlightView.backgroundColor = .clear //corner radius - layer.cornerRadius = cornerRadius + containerView.layer.cornerRadius = cornerRadius backgroundImageView.layer.cornerRadius = cornerRadius highlightView.layer.cornerRadius = cornerRadius - clipsToBounds = true + containerView.clipsToBounds = true containerView.bridge_isAccessibilityElementBlock = { [weak self] in self?.onClickSubscriber != nil } containerView.accessibilityHint = "Double tap to open." @@ -303,43 +304,74 @@ open class TileContainerBase: Control where Padding highlightView.backgroundColor = hightLightViewColorConfiguration.getColor(self) highlightView.isHidden = !isHighlighted - layer.borderColor = borderColorConfiguration.getColor(self).cgColor - layer.borderWidth = showBorder ? VDSFormControls.borderWidth : 0 + containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor + containerView.layer.borderWidth = showBorder ? VDSFormControls.borderWidth : 0 - containerTopConstraint?.constant = padding.value - containerLeadingConstraint?.constant = padding.value - containerBottomConstraint?.constant = padding.value - containerTrailingConstraint?.constant = padding.value + contentViewTopConstraint?.constant = padding.value + contentViewLeadingConstraint?.constant = padding.value + contentViewBottomConstraint?.constant = padding.value + contentViewTrailingConstraint?.constant = padding.value + //deactivate everything + widthConstraint?.deactivate() + heightConstraint?.deactivate() + containerViewTrailingLessThanOrEqualConstraint?.deactivate() + containerViewTrailingLessThanOrEqualConstraint?.deactivate() + containerViewBottomConstraint?.deactivate() + containerViewTrailingConstraint?.deactivate() + + //run logic to determine which to activate if let width, aspectRatio == .none && height == nil{ widthConstraint?.constant = width - widthConstraint?.isActive = true - heightConstraint?.isActive = false - heightGreaterThanConstraint?.isActive = true + widthConstraint?.activate() + containerViewTrailingConstraint?.activate() + containerViewBottomConstraint?.activate() + + } else if let height, aspectRatio == .none && width == nil{ + heightConstraint?.constant = height + heightConstraint?.activate() + containerViewTrailingConstraint?.activate() + containerViewBottomConstraint?.activate() + } else if let height, let width { widthConstraint?.constant = width heightConstraint?.constant = height - heightConstraint?.isActive = true - widthConstraint?.isActive = true - heightGreaterThanConstraint?.isActive = false + heightConstraint?.activate() + widthConstraint?.activate() + containerViewTrailingLessThanOrEqualConstraint?.activate() + containerViewBottomLessThanOrEqualConstraint?.activate() + } else if let width { let size = ratioSize(for: width) widthConstraint?.constant = size.width heightConstraint?.constant = size.height - widthConstraint?.isActive = true - heightConstraint?.isActive = true - heightGreaterThanConstraint?.isActive = false + widthConstraint?.activate() + heightConstraint?.activate() + containerViewTrailingLessThanOrEqualConstraint?.activate() + containerViewBottomLessThanOrEqualConstraint?.activate() + + } else if let height { + let size = ratioSize(for: height) + //enforce exact height/width + widthConstraint?.constant = size.width + heightConstraint?.constant = size.height + widthConstraint?.activate() + heightConstraint?.activate() + containerViewTrailingLessThanOrEqualConstraint?.activate() + containerViewBottomLessThanOrEqualConstraint?.activate() + } else { - widthConstraint?.isActive = false - heightConstraint?.isActive = false + //set to the parent view + containerViewBottomConstraint?.activate() + containerViewTrailingConstraint?.activate() } applyBackgroundEffects() if showDropShadow, surface == .light { - addDropShadow(dropShadowConfiguration) + containerView.addDropShadow(dropShadowConfiguration) } else { - removeDropShadows() + containerView.removeDropShadows() } } @@ -372,8 +404,8 @@ open class TileContainerBase: Control where Padding /// Used to update frames for the added CAlayers to our view open override func layoutSubviews() { super.layoutSubviews() - dropShadowLayers?.forEach { $0.frame = bounds } - gradientLayers?.forEach { $0.frame = bounds } + containerView.dropShadowLayers?.forEach { $0.frame = bounds } + containerView.gradientLayers?.forEach { $0.frame = bounds } } //-------------------------------------------------- @@ -400,25 +432,25 @@ open class TileContainerBase: Control where Padding switch backgroundEffect { case .transparency: alphaConfiguration = 0.8 - removeGradientLayer() + containerView.removeGradientLayer() case .gradient(let firstColor, let secondColor): alphaConfiguration = 1.0 - addGradientLayer(with: firstColor, secondColor: secondColor) + containerView.addGradientLayer(with: firstColor, secondColor: secondColor) backgroundImageView.isHidden = true backgroundImageView.alpha = 1.0 case .none: alphaConfiguration = 1.0 - removeGradientLayer() + containerView.removeGradientLayer() } if let backgroundImage { backgroundImageView.image = backgroundImage backgroundImageView.isHidden = false backgroundImageView.alpha = alphaConfiguration - backgroundColor = imageFallbackColor.withAlphaComponent(alphaConfiguration) + containerView.backgroundColor = imageFallbackColor.withAlphaComponent(alphaConfiguration) } else { backgroundImageView.isHidden = true backgroundImageView.alpha = 1.0 - backgroundColor = color.withAlphaComponent(alphaConfiguration) + containerView.backgroundColor = color.withAlphaComponent(alphaConfiguration) } } From 9c6009e4ac4b3f3d8188c922100f1a8d80a89b7b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 26 Jun 2024 10:14:48 -0500 Subject: [PATCH 041/117] fixed bug in not using super for overrides Signed-off-by: Matt Bruce --- VDS/BaseClasses/Control.swift | 22 +++++++--------- VDS/BaseClasses/View.swift | 22 +++++++--------- VDS/Components/Buttons/ButtonBase.swift | 25 ++++++------------- VDS/Components/Label/Label.swift | 24 ++++++------------ .../TextFields/InputField/TextField.swift | 20 ++++++--------- .../TextFields/TextArea/TextView.swift | 20 ++++++--------- 6 files changed, 50 insertions(+), 83 deletions(-) diff --git a/VDS/BaseClasses/Control.swift b/VDS/BaseClasses/Control.swift index 2a9fe769..7aa08717 100644 --- a/VDS/BaseClasses/Control.swift +++ b/VDS/BaseClasses/Control.swift @@ -129,7 +129,6 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable { //-------------------------------------------------- open var accessibilityAction: ((Control) -> Void)? - private var _isAccessibilityElement: Bool = false open override var isAccessibilityElement: Bool { get { var block: AXBoolReturnBlock? @@ -137,7 +136,7 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable { // if #available(iOS 17, *) { // block = isAccessibilityElementBlock // } - + if block == nil { block = bridge_isAccessibilityElementBlock } @@ -145,15 +144,14 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable { if let block { return block() } else { - return _isAccessibilityElement + return super.isAccessibilityElement } } set { - _isAccessibilityElement = newValue + super.isAccessibilityElement = newValue } } - private var _accessibilityLabel: String? open override var accessibilityLabel: String? { get { var block: AXStringReturnBlock? @@ -168,15 +166,14 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable { if let block { return block() } else { - return _accessibilityLabel + return super.accessibilityLabel } } set { - _accessibilityLabel = newValue + super.accessibilityLabel = newValue } } - private var _accessibilityHint: String? open override var accessibilityHint: String? { get { var block: AXStringReturnBlock? @@ -191,15 +188,14 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable { if let block { return block() } else { - return _accessibilityHint + return super.accessibilityHint } } set { - _accessibilityHint = newValue + super.accessibilityHint = newValue } } - private var _accessibilityValue: String? open override var accessibilityValue: String? { get { var block: AXStringReturnBlock? @@ -215,11 +211,11 @@ open class Control: UIControl, ViewProtocol, UserInfoable, Clickable { if let block{ return block() } else { - return _accessibilityValue + return super.accessibilityValue } } set { - _accessibilityValue = newValue + super.accessibilityValue = newValue } } diff --git a/VDS/BaseClasses/View.swift b/VDS/BaseClasses/View.swift index c7df1765..7e88df8e 100644 --- a/VDS/BaseClasses/View.swift +++ b/VDS/BaseClasses/View.swift @@ -96,7 +96,6 @@ open class View: UIView, ViewProtocol, UserInfoable { //-------------------------------------------------- open var accessibilityAction: ((View) -> Void)? - private var _isAccessibilityElement: Bool = false open override var isAccessibilityElement: Bool { get { var block: AXBoolReturnBlock? @@ -112,22 +111,21 @@ open class View: UIView, ViewProtocol, UserInfoable { if let block { return block() } else { - return _isAccessibilityElement + return super.isAccessibilityElement } } set { - _isAccessibilityElement = newValue + super.isAccessibilityElement = newValue } } - private var _accessibilityLabel: String? open override var accessibilityLabel: String? { get { var block: AXStringReturnBlock? // if #available(iOS 17, *) { // block = accessibilityLabelBlock // } -// + if block == nil { block = bridge_accessibilityLabelBlock } @@ -135,15 +133,14 @@ open class View: UIView, ViewProtocol, UserInfoable { if let block { return block() } else { - return _accessibilityLabel + return super.accessibilityLabel } } set { - _accessibilityLabel = newValue + super.accessibilityLabel = newValue } } - private var _accessibilityHint: String? open override var accessibilityHint: String? { get { var block: AXStringReturnBlock? @@ -158,15 +155,14 @@ open class View: UIView, ViewProtocol, UserInfoable { if let block { return block() } else { - return _accessibilityHint + return super.accessibilityHint } } set { - _accessibilityHint = newValue + super.accessibilityHint = newValue } } - private var _accessibilityValue: String? open override var accessibilityValue: String? { get { var block: AXStringReturnBlock? @@ -182,11 +178,11 @@ open class View: UIView, ViewProtocol, UserInfoable { if let block{ return block() } else { - return _accessibilityValue + return super.accessibilityValue } } set { - _accessibilityValue = newValue + super.accessibilityValue = newValue } } diff --git a/VDS/Components/Buttons/ButtonBase.swift b/VDS/Components/Buttons/ButtonBase.swift index 720fca36..eebf3a68 100644 --- a/VDS/Components/Buttons/ButtonBase.swift +++ b/VDS/Components/Buttons/ButtonBase.swift @@ -114,11 +114,6 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { titleLabel?.adjustsFontSizeToFitWidth = false titleLabel?.lineBreakMode = .byTruncatingTail titleLabel?.numberOfLines = 1 - - bridge_accessibilityLabelBlock = { [weak self] in - guard let self else { return nil } - return text - } } open func updateView() { @@ -182,7 +177,6 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { //-------------------------------------------------- open var accessibilityAction: ((ButtonBase) -> Void)? - private var _isAccessibilityElement: Bool = false open override var isAccessibilityElement: Bool { get { var block: AXBoolReturnBlock? @@ -198,15 +192,14 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { if let block { return block() } else { - return _isAccessibilityElement + return super.isAccessibilityElement } } set { - _isAccessibilityElement = newValue + super.isAccessibilityElement = newValue } } - private var _accessibilityLabel: String? open override var accessibilityLabel: String? { get { var block: AXStringReturnBlock? @@ -221,15 +214,14 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { if let block { return block() } else { - return _accessibilityLabel + return super.accessibilityLabel } } set { - _accessibilityLabel = newValue + super.accessibilityLabel = newValue } } - private var _accessibilityHint: String? open override var accessibilityHint: String? { get { var block: AXStringReturnBlock? @@ -244,15 +236,14 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { if let block { return block() } else { - return _accessibilityHint + return super.accessibilityHint } } set { - _accessibilityHint = newValue + super.accessibilityHint = newValue } } - private var _accessibilityValue: String? open override var accessibilityValue: String? { get { var block: AXStringReturnBlock? @@ -268,11 +259,11 @@ open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { if let block{ return block() } else { - return _accessibilityValue + return super.accessibilityValue } } set { - _accessibilityValue = newValue + super.accessibilityValue = newValue } } diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index dccc0353..e408953b 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -214,10 +214,6 @@ open class Label: UILabel, ViewProtocol, UserInfoable { } open func setup() { - bridge_accessibilityLabelBlock = { [weak self] in - guard let self else { return "" } - return text - } } open func reset() { @@ -498,7 +494,6 @@ open class Label: UILabel, ViewProtocol, UserInfoable { open var accessibilityAction: ((Label) -> Void)? - private var _isAccessibilityElement: Bool = false open override var isAccessibilityElement: Bool { get { var block: AXBoolReturnBlock? @@ -514,15 +509,14 @@ open class Label: UILabel, ViewProtocol, UserInfoable { if let block { return block() } else { - return _isAccessibilityElement + return super.isAccessibilityElement } } set { - _isAccessibilityElement = newValue + super.isAccessibilityElement = newValue } } - private var _accessibilityLabel: String? open override var accessibilityLabel: String? { get { var block: AXStringReturnBlock? @@ -537,15 +531,14 @@ open class Label: UILabel, ViewProtocol, UserInfoable { if let block { return block() } else { - return _accessibilityLabel + return super.accessibilityLabel } } set { - _accessibilityLabel = newValue + super.accessibilityLabel = newValue } } - private var _accessibilityHint: String? open override var accessibilityHint: String? { get { var block: AXStringReturnBlock? @@ -560,15 +553,14 @@ open class Label: UILabel, ViewProtocol, UserInfoable { if let block { return block() } else { - return _accessibilityHint + return super.accessibilityHint } } set { - _accessibilityHint = newValue + super.accessibilityHint = newValue } } - private var _accessibilityValue: String? open override var accessibilityValue: String? { get { var block: AXStringReturnBlock? @@ -584,11 +576,11 @@ open class Label: UILabel, ViewProtocol, UserInfoable { if let block{ return block() } else { - return _accessibilityValue + return super.accessibilityValue } } set { - _accessibilityValue = newValue + super.accessibilityValue = newValue } } diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index 0108c874..05dc30f2 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -236,7 +236,6 @@ open class TextField: UITextField, ViewProtocol, Errorable { //-------------------------------------------------- open var accessibilityAction: ((TextField) -> Void)? - private var _isAccessibilityElement: Bool = false open override var isAccessibilityElement: Bool { get { var block: AXBoolReturnBlock? @@ -252,15 +251,14 @@ open class TextField: UITextField, ViewProtocol, Errorable { if let block { return block() } else { - return _isAccessibilityElement + return super.isAccessibilityElement } } set { - _isAccessibilityElement = newValue + super.isAccessibilityElement = newValue } } - private var _accessibilityLabel: String? open override var accessibilityLabel: String? { get { var block: AXStringReturnBlock? @@ -275,15 +273,14 @@ open class TextField: UITextField, ViewProtocol, Errorable { if let block { return block() } else { - return _accessibilityLabel + return super.accessibilityLabel } } set { - _accessibilityLabel = newValue + super.accessibilityLabel = newValue } } - private var _accessibilityHint: String? open override var accessibilityHint: String? { get { var block: AXStringReturnBlock? @@ -298,15 +295,14 @@ open class TextField: UITextField, ViewProtocol, Errorable { if let block { return block() } else { - return _accessibilityHint + return super.accessibilityHint } } set { - _accessibilityHint = newValue + super.accessibilityHint = newValue } } - private var _accessibilityValue: String? open override var accessibilityValue: String? { get { var block: AXStringReturnBlock? @@ -322,11 +318,11 @@ open class TextField: UITextField, ViewProtocol, Errorable { if let block{ return block() } else { - return _accessibilityValue + return super.accessibilityValue } } set { - _accessibilityValue = newValue + super.accessibilityValue = newValue } } diff --git a/VDS/Components/TextFields/TextArea/TextView.swift b/VDS/Components/TextFields/TextArea/TextView.swift index ed622f90..54fa9452 100644 --- a/VDS/Components/TextFields/TextArea/TextView.swift +++ b/VDS/Components/TextFields/TextArea/TextView.swift @@ -151,7 +151,6 @@ open class TextView: UITextView, ViewProtocol, Errorable { //-------------------------------------------------- open var accessibilityAction: ((TextView) -> Void)? - private var _isAccessibilityElement: Bool = false open override var isAccessibilityElement: Bool { get { var block: AXBoolReturnBlock? @@ -167,15 +166,14 @@ open class TextView: UITextView, ViewProtocol, Errorable { if let block { return block() } else { - return _isAccessibilityElement + return super.isAccessibilityElement } } set { - _isAccessibilityElement = newValue + super.isAccessibilityElement = newValue } } - private var _accessibilityLabel: String? open override var accessibilityLabel: String? { get { var block: AXStringReturnBlock? @@ -190,15 +188,14 @@ open class TextView: UITextView, ViewProtocol, Errorable { if let block { return block() } else { - return _accessibilityLabel + return super.accessibilityLabel } } set { - _accessibilityLabel = newValue + super.accessibilityLabel = newValue } } - private var _accessibilityHint: String? open override var accessibilityHint: String? { get { var block: AXStringReturnBlock? @@ -213,15 +210,14 @@ open class TextView: UITextView, ViewProtocol, Errorable { if let block { return block() } else { - return _accessibilityHint + return super.accessibilityHint } } set { - _accessibilityHint = newValue + super.accessibilityHint = newValue } } - private var _accessibilityValue: String? open override var accessibilityValue: String? { get { var block: AXStringReturnBlock? @@ -237,11 +233,11 @@ open class TextView: UITextView, ViewProtocol, Errorable { if let block{ return block() } else { - return _accessibilityValue + return super.accessibilityValue } } set { - _accessibilityValue = newValue + super.accessibilityValue = newValue } } From a432534e1a095953c1336003460abcb9d51dc87a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 26 Jun 2024 13:28:39 -0500 Subject: [PATCH 042/117] updated constraints Signed-off-by: Matt Bruce --- .../TileContainer/TileContainer.swift | 49 ++++++++++++------- 1 file changed, 31 insertions(+), 18 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index b6014e8f..3e3b2ddf 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -179,10 +179,12 @@ open class TileContainerBase: Control where Padding //-------------------------------------------------- internal var widthConstraint: NSLayoutConstraint? internal var heightConstraint: NSLayoutConstraint? - - internal var containerViewBottomConstraint: NSLayoutConstraint? + + internal var containerViewCenterXConstraint: NSLayoutConstraint? + internal var containerViewLeadingConstraint: NSLayoutConstraint? + internal var containerViewLeadingGreaterThanOrEqualConstraint: NSLayoutConstraint? + internal var containerViewTrailingConstraint: NSLayoutConstraint? - internal var containerViewBottomLessThanOrEqualConstraint: NSLayoutConstraint? internal var containerViewTrailingLessThanOrEqualConstraint: NSLayoutConstraint? internal var contentViewTopConstraint: NSLayoutConstraint? @@ -233,14 +235,15 @@ open class TileContainerBase: Control where Padding containerView .pinTop() - .pinLeading() + .pinBottom() + containerViewCenterXConstraint = containerView.pinCenterX(anchor: centerXAnchor)?.deactivate() + containerViewLeadingConstraint = containerView.pinLeading(anchor: leadingAnchor)?.deactivate() + containerViewLeadingGreaterThanOrEqualConstraint = containerView.pinLeadingGreaterThanOrEqualTo(anchor: leadingAnchor)?.deactivate() + containerViewTrailingConstraint = containerView.pinTrailing(anchor: trailingAnchor)?.deactivate() - containerViewBottomConstraint = containerView.pinBottom(anchor: bottomAnchor)?.deactivate() - containerViewTrailingLessThanOrEqualConstraint = containerView.pinTrailingLessThanOrEqualTo(anchor: trailingAnchor)?.deactivate() - containerViewBottomLessThanOrEqualConstraint = containerView.pinBottomLessThanOrEqualTo(anchor: bottomAnchor)?.deactivate() - + backgroundImageView.pinToSuperView() widthConstraint = containerView.widthAnchor.constraint(equalToConstant: 0).deactivate() @@ -315,31 +318,40 @@ open class TileContainerBase: Control where Padding //deactivate everything widthConstraint?.deactivate() heightConstraint?.deactivate() + + containerViewCenterXConstraint?.deactivate() + + containerViewLeadingGreaterThanOrEqualConstraint?.deactivate() + containerViewLeadingConstraint?.deactivate() + containerViewTrailingLessThanOrEqualConstraint?.deactivate() - containerViewTrailingLessThanOrEqualConstraint?.deactivate() - containerViewBottomConstraint?.deactivate() containerViewTrailingConstraint?.deactivate() + print("Width: \(width?.description ?? "none")") + print("Height: \(height?.description ?? "none")") + //run logic to determine which to activate if let width, aspectRatio == .none && height == nil{ widthConstraint?.constant = width widthConstraint?.activate() - containerViewTrailingConstraint?.activate() - containerViewBottomConstraint?.activate() + containerViewCenterXConstraint?.activate() + containerViewLeadingGreaterThanOrEqualConstraint?.activate() + containerViewTrailingLessThanOrEqualConstraint?.activate() } else if let height, aspectRatio == .none && width == nil{ heightConstraint?.constant = height heightConstraint?.activate() + containerViewLeadingConstraint?.activate() containerViewTrailingConstraint?.activate() - containerViewBottomConstraint?.activate() } else if let height, let width { widthConstraint?.constant = width heightConstraint?.constant = height heightConstraint?.activate() widthConstraint?.activate() + containerViewCenterXConstraint?.activate() + containerViewLeadingGreaterThanOrEqualConstraint?.activate() containerViewTrailingLessThanOrEqualConstraint?.activate() - containerViewBottomLessThanOrEqualConstraint?.activate() } else if let width { let size = ratioSize(for: width) @@ -347,22 +359,23 @@ open class TileContainerBase: Control where Padding heightConstraint?.constant = size.height widthConstraint?.activate() heightConstraint?.activate() + containerViewCenterXConstraint?.activate() + containerViewLeadingGreaterThanOrEqualConstraint?.activate() containerViewTrailingLessThanOrEqualConstraint?.activate() - containerViewBottomLessThanOrEqualConstraint?.activate() } else if let height { let size = ratioSize(for: height) - //enforce exact height/width widthConstraint?.constant = size.width heightConstraint?.constant = size.height widthConstraint?.activate() heightConstraint?.activate() + containerViewCenterXConstraint?.activate() + containerViewLeadingGreaterThanOrEqualConstraint?.activate() containerViewTrailingLessThanOrEqualConstraint?.activate() - containerViewBottomLessThanOrEqualConstraint?.activate() } else { //set to the parent view - containerViewBottomConstraint?.activate() + containerViewLeadingConstraint?.activate() containerViewTrailingConstraint?.activate() } From db3ba4f1a1726d869d5fc1dabffbe10e6895066e Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 26 Jun 2024 13:29:45 -0500 Subject: [PATCH 043/117] reset to start with none Signed-off-by: Matt Bruce --- VDS/Components/TileContainer/TileContainer.swift | 4 ++-- VDS/Components/Tilelet/Tilelet.swift | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 3e3b2ddf..d2425464 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -126,7 +126,7 @@ open class TileContainerBase: Control where Padding } /// This controls the aspect ratio for the component. - open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } } + open var aspectRatio: AspectRatio = .none { didSet { setNeedsUpdate() } } /// Sets the background color for the component. open var color: BackgroundColor? { didSet { setNeedsUpdate() } } @@ -290,7 +290,7 @@ open class TileContainerBase: Control where Padding super.reset() shouldUpdateView = false color = .white - aspectRatio = .ratio1x1 + aspectRatio = .none imageFallbackColor = .light width = nil height = nil diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index 95acf895..0a7178e2 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -302,7 +302,6 @@ open class Tilelet: TileContainerBase { /// 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() - aspectRatio = .none color = .black addContentView(stackView) @@ -386,7 +385,6 @@ open class Tilelet: TileContainerBase { /// Resets to default settings. open override func reset() { shouldUpdateView = false - aspectRatio = .none color = .black //models badgeModel = nil @@ -405,11 +403,7 @@ open class Tilelet: TileContainerBase { updateBadge() updateTitleLockup() updateIcons() - ///Content-driven height Tilelets - Minimum height is configurable. - ///if width != nil && (aspectRatio != .none || height != nil) then tilelet is not self growing, so we can apply text position alignments. - if width != nil && (aspectRatio != .none || height != nil) { - updateTextPositionAlignment() - } + updateTextPositionAlignment() setNeedsLayout() } @@ -584,6 +578,7 @@ open class Tilelet: TileContainerBase { } private func updateTextPositionAlignment() { + guard width != nil && (aspectRatio != .none || height != nil) else { return } switch textPostion { case .top: titleLockupTopConstraint?.activate() From bd908aeb471559b44a03cc3a22a0d93774a9177b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 26 Jun 2024 13:31:29 -0500 Subject: [PATCH 044/117] removed logic Signed-off-by: Matt Bruce --- VDS/Components/TileContainer/TileContainer.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index d2425464..ad356065 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -326,10 +326,7 @@ open class TileContainerBase: Control where Padding containerViewTrailingLessThanOrEqualConstraint?.deactivate() containerViewTrailingConstraint?.deactivate() - - print("Width: \(width?.description ?? "none")") - print("Height: \(height?.description ?? "none")") - + //run logic to determine which to activate if let width, aspectRatio == .none && height == nil{ widthConstraint?.constant = width From 0f368b5132fb0108b387092c6fd1e8d0d31c01c4 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 26 Jun 2024 15:46:37 -0500 Subject: [PATCH 045/117] updated constraints Signed-off-by: Matt Bruce --- VDS/Components/TileContainer/TileContainer.swift | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index ad356065..c2d03aea 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -180,7 +180,6 @@ open class TileContainerBase: Control where Padding internal var widthConstraint: NSLayoutConstraint? internal var heightConstraint: NSLayoutConstraint? - internal var containerViewCenterXConstraint: NSLayoutConstraint? internal var containerViewLeadingConstraint: NSLayoutConstraint? internal var containerViewLeadingGreaterThanOrEqualConstraint: NSLayoutConstraint? @@ -235,9 +234,9 @@ open class TileContainerBase: Control where Padding containerView .pinTop() + .pinCenterX() .pinBottom() - containerViewCenterXConstraint = containerView.pinCenterX(anchor: centerXAnchor)?.deactivate() containerViewLeadingConstraint = containerView.pinLeading(anchor: leadingAnchor)?.deactivate() containerViewLeadingGreaterThanOrEqualConstraint = containerView.pinLeadingGreaterThanOrEqualTo(anchor: leadingAnchor)?.deactivate() @@ -318,9 +317,7 @@ open class TileContainerBase: Control where Padding //deactivate everything widthConstraint?.deactivate() heightConstraint?.deactivate() - - containerViewCenterXConstraint?.deactivate() - + containerViewLeadingGreaterThanOrEqualConstraint?.deactivate() containerViewLeadingConstraint?.deactivate() @@ -331,7 +328,6 @@ open class TileContainerBase: Control where Padding if let width, aspectRatio == .none && height == nil{ widthConstraint?.constant = width widthConstraint?.activate() - containerViewCenterXConstraint?.activate() containerViewLeadingGreaterThanOrEqualConstraint?.activate() containerViewTrailingLessThanOrEqualConstraint?.activate() @@ -346,7 +342,6 @@ open class TileContainerBase: Control where Padding heightConstraint?.constant = height heightConstraint?.activate() widthConstraint?.activate() - containerViewCenterXConstraint?.activate() containerViewLeadingGreaterThanOrEqualConstraint?.activate() containerViewTrailingLessThanOrEqualConstraint?.activate() @@ -356,7 +351,6 @@ open class TileContainerBase: Control where Padding heightConstraint?.constant = size.height widthConstraint?.activate() heightConstraint?.activate() - containerViewCenterXConstraint?.activate() containerViewLeadingGreaterThanOrEqualConstraint?.activate() containerViewTrailingLessThanOrEqualConstraint?.activate() @@ -366,7 +360,6 @@ open class TileContainerBase: Control where Padding heightConstraint?.constant = size.height widthConstraint?.activate() heightConstraint?.activate() - containerViewCenterXConstraint?.activate() containerViewLeadingGreaterThanOrEqualConstraint?.activate() containerViewTrailingLessThanOrEqualConstraint?.activate() From 742bae9169e9f95ff3a6fd3f427f7062bda82dab Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 26 Jun 2024 17:19:17 -0500 Subject: [PATCH 046/117] Signed-off-by: Matt Bruce --- VDS/Components/TileContainer/TileContainer.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index c2d03aea..03e320e7 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -364,7 +364,6 @@ open class TileContainerBase: Control where Padding containerViewTrailingLessThanOrEqualConstraint?.activate() } else { - //set to the parent view containerViewLeadingConstraint?.activate() containerViewTrailingConstraint?.activate() } From 5fcc59d8af7bff1bbacd6e62a82176001509595e Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 27 Jun 2024 09:48:52 -0500 Subject: [PATCH 047/117] added helpers methods to layoutconstrainable Signed-off-by: Matt Bruce --- VDS/Protocols/LayoutConstraintable.swift | 124 +++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/VDS/Protocols/LayoutConstraintable.swift b/VDS/Protocols/LayoutConstraintable.swift index 3e40885b..3661bae0 100644 --- a/VDS/Protocols/LayoutConstraintable.swift +++ b/VDS/Protocols/LayoutConstraintable.swift @@ -631,6 +631,130 @@ extension LayoutConstraintable { return centerYAnchor.constraint(greaterThanOrEqualTo: found, constant: -constant).with { $0.priority = priority; $0.isActive = true } } } + +// alignment +public enum LayoutAlignment: String, CaseIterable { + case fill + case leading + case top + case center + case trailing + case bottom +} + +public enum LayoutDistribution: String, CaseIterable { + case fill + case fillProportionally +} + +extension LayoutConstraintable { + public func removeConstraints() { + guard let view = self as? UIView, let superview = view.superview else { return } + + // Remove all existing constraints on the containerView + let superviewConstraints = superview.constraints + for constraint in superviewConstraints { + if constraint.firstItem as? UIView == view + || constraint.secondItem as? UIView == view { + superview.removeConstraint(constraint) + } + } + } + + public func applyAlignment(_ alignment: LayoutAlignment, edges: UIEdgeInsets = UIEdgeInsets.zero) { + guard let superview = superview else { return } + + removeConstraints() + + switch alignment { + case .fill: + pinToSuperView(edges) + + case .leading: + pinTop(edges.top) + pinLeading(edges.left) + pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right) + pinBottom(edges.bottom) + + case .trailing: + pinTop(edges.top) + pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left) + pinTrailing(edges.right) + pinBottom(edges.bottom) + + case .top: + pinTop(edges.top) + pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left) + pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right) + pinBottomLessThanOrEqualTo(anchor: superview.bottomAnchor, constant: edges.bottom) + + case .bottom: + pinTopGreaterThanOrEqualTo(anchor: superview.topAnchor, constant: edges.top) + pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left) + pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right) + pinBottom(edges.bottom) + + case .center: + pinCenterX() + pinTop(edges.top) + pinLeadingGreaterThanOrEqualTo(anchor: superview.leadingAnchor, constant: edges.left) + pinTrailingLessThanOrEqualTo(anchor: superview.trailingAnchor, constant: edges.right) + pinBottom(edges.bottom) + + } + } + + // Method to check if the view is pinned to its superview + public func isPinnedToSuperview() -> Bool { + isPinnedVerticallyToSuperview() && isPinnedHorizontallyToSuperview() + } + + public func isPinnedHorizontallyToSuperview() -> Bool { + guard let view = self as? UIView, let superview = view.superview else { return false } + let constraints = superview.constraints + var leadingPinned = false + var trailingPinned = false + + for constraint in constraints { + if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .leading && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .leading && constraint.relation == .equal && constraint.firstItem as? UIView == superview) || + (constraint.firstItem as? UIView == view && constraint.firstAttribute == .left && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .left && constraint.relation == .equal && constraint.firstItem as? UIView == superview) { + leadingPinned = true + } + if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .trailing && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .trailing && constraint.relation == .equal && constraint.firstItem as? UIView == superview) || + (constraint.firstItem as? UIView == view && constraint.firstAttribute == .right && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .right && constraint.relation == .equal && constraint.firstItem as? UIView == superview) { + trailingPinned = true + } + } + + return leadingPinned && trailingPinned + } + + public func isPinnedVerticallyToSuperview() -> Bool { + guard let view = self as? UIView, let superview = view.superview else { return false } + let constraints = superview.constraints + var topPinned = false + var bottomPinned = false + + for constraint in constraints { + if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .top && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .top && constraint.relation == .equal && constraint.firstItem as? UIView == superview) { + topPinned = true + } + if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .bottom && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .bottom && constraint.relation == .equal && constraint.firstItem as? UIView == superview) { + bottomPinned = true + } + } + + return topPinned && bottomPinned + } +} + + //-------------------------------------------------- // MARK: - Implementations //-------------------------------------------------- From 289353a4432dbb774122f8ff75a5237e6eaf4a83 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 27 Jun 2024 09:55:37 -0500 Subject: [PATCH 048/117] refactored to final fix, hopefully :D Signed-off-by: Matt Bruce --- .../TileContainer/TileContainer.swift | 162 ++++++++---------- 1 file changed, 75 insertions(+), 87 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 03e320e7..9b121aa1 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -44,6 +44,7 @@ open class TileContainer: TileContainerBase { } open class TileContainerBase: Control where PaddingType.ValueType == CGFloat { + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -109,7 +110,16 @@ open class TileContainerBase: Control where Padding $0.clipsToBounds = true } - internal var containerView = View() + open var containerView = View().with { + $0.setContentHuggingPriority(.defaultLow, for: .horizontal) + $0.setContentHuggingPriority(.defaultLow, for: .vertical) + $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + $0.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) + } + + open var distribution: LayoutDistribution = .fill { didSet { setNeedsUpdate() } } + + open var alignment: LayoutAlignment = .center { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Public Properties @@ -180,12 +190,6 @@ open class TileContainerBase: Control where Padding internal var widthConstraint: NSLayoutConstraint? internal var heightConstraint: NSLayoutConstraint? - internal var containerViewLeadingConstraint: NSLayoutConstraint? - internal var containerViewLeadingGreaterThanOrEqualConstraint: NSLayoutConstraint? - - internal var containerViewTrailingConstraint: NSLayoutConstraint? - internal var containerViewTrailingLessThanOrEqualConstraint: NSLayoutConstraint? - internal var contentViewTopConstraint: NSLayoutConstraint? internal var contentViewBottomConstraint: NSLayoutConstraint? internal var contentViewLeadingConstraint: NSLayoutConstraint? @@ -232,17 +236,6 @@ open class TileContainerBase: Control where Padding containerView.addSubview(contentView) containerView.addSubview(highlightView) - containerView - .pinTop() - .pinCenterX() - .pinBottom() - - containerViewLeadingConstraint = containerView.pinLeading(anchor: leadingAnchor)?.deactivate() - containerViewLeadingGreaterThanOrEqualConstraint = containerView.pinLeadingGreaterThanOrEqualTo(anchor: leadingAnchor)?.deactivate() - - containerViewTrailingConstraint = containerView.pinTrailing(anchor: trailingAnchor)?.deactivate() - containerViewTrailingLessThanOrEqualConstraint = containerView.pinTrailingLessThanOrEqualTo(anchor: trailingAnchor)?.deactivate() - backgroundImageView.pinToSuperView() widthConstraint = containerView.widthAnchor.constraint(equalToConstant: 0).deactivate() @@ -313,70 +306,10 @@ open class TileContainerBase: Control where Padding contentViewLeadingConstraint?.constant = padding.value contentViewBottomConstraint?.constant = padding.value contentViewTrailingConstraint?.constant = padding.value - - //deactivate everything - widthConstraint?.deactivate() - heightConstraint?.deactivate() - containerViewLeadingGreaterThanOrEqualConstraint?.deactivate() - containerViewLeadingConstraint?.deactivate() - - containerViewTrailingLessThanOrEqualConstraint?.deactivate() - containerViewTrailingConstraint?.deactivate() - - //run logic to determine which to activate - if let width, aspectRatio == .none && height == nil{ - widthConstraint?.constant = width - widthConstraint?.activate() - containerViewLeadingGreaterThanOrEqualConstraint?.activate() - containerViewTrailingLessThanOrEqualConstraint?.activate() - - } else if let height, aspectRatio == .none && width == nil{ - heightConstraint?.constant = height - heightConstraint?.activate() - containerViewLeadingConstraint?.activate() - containerViewTrailingConstraint?.activate() - - } else if let height, let width { - widthConstraint?.constant = width - heightConstraint?.constant = height - heightConstraint?.activate() - widthConstraint?.activate() - containerViewLeadingGreaterThanOrEqualConstraint?.activate() - containerViewTrailingLessThanOrEqualConstraint?.activate() - - } else if let width { - let size = ratioSize(for: width) - widthConstraint?.constant = size.width - heightConstraint?.constant = size.height - widthConstraint?.activate() - heightConstraint?.activate() - containerViewLeadingGreaterThanOrEqualConstraint?.activate() - containerViewTrailingLessThanOrEqualConstraint?.activate() - - } else if let height { - let size = ratioSize(for: height) - widthConstraint?.constant = size.width - heightConstraint?.constant = size.height - widthConstraint?.activate() - heightConstraint?.activate() - containerViewLeadingGreaterThanOrEqualConstraint?.activate() - containerViewTrailingLessThanOrEqualConstraint?.activate() - - } else { - containerViewLeadingConstraint?.activate() - containerViewTrailingConstraint?.activate() - } - - applyBackgroundEffects() - - if showDropShadow, surface == .light { - containerView.addDropShadow(dropShadowConfiguration) - } else { - containerView.removeDropShadows() - } + updateContainerView() } - + open override var accessibilityElements: [Any]? { get { var items = [Any]() @@ -403,13 +336,6 @@ open class TileContainerBase: Control where Padding set {} } - /// Used to update frames for the added CAlayers to our view - open override func layoutSubviews() { - super.layoutSubviews() - containerView.dropShadowLayers?.forEach { $0.frame = bounds } - containerView.gradientLayers?.forEach { $0.frame = bounds } - } - //-------------------------------------------------- // MARK: - Public Methods //-------------------------------------------------- @@ -485,7 +411,69 @@ open class TileContainerBase: Control where Padding return CGSize(width: width, height: height) } + + private func sizeContainerView(width: CGFloat? = nil, height: CGFloat? = nil) { + if let width, width > 0 { + widthConstraint?.constant = width + widthConstraint?.activate() + } + + if let height, height > 0 { + heightConstraint?.constant = height + heightConstraint?.activate() + } + } + + private func updateContainerView() { + applyBackgroundEffects() + + widthConstraint?.deactivate() + heightConstraint?.deactivate() + if showDropShadow, surface == .light { + containerView.addDropShadow(dropShadowConfiguration) + } else { + containerView.removeDropShadows() + } + + containerView.dropShadowLayers?.forEach { $0.frame = bounds } + containerView.gradientLayers?.forEach { $0.frame = bounds } + + if width != nil || height != nil { + var containerViewWidth: CGFloat? + var containerViewHeight: CGFloat? + //run logic to determine which to activate + if let width, aspectRatio == .none && height == nil{ + containerViewWidth = width + + } else if let height, aspectRatio == .none && width == nil{ + containerViewHeight = height + + } else if let height, let width { + containerViewWidth = width + containerViewHeight = height + + } else if let width { + let size = ratioSize(for: width) + containerViewWidth = size.width + containerViewHeight = size.height + + } else if let height { + let size = ratioSize(for: height) + containerViewWidth = size.width + containerViewHeight = size.height + } + + sizeContainerView(width: containerViewWidth, height: containerViewHeight) + containerView.applyAlignment(alignment) + + } else { + containerView.applyAlignment(distribution == .fill ? .fill : alignment) + if let superview, distribution == .fill, !isPinnedHorizontallyToSuperview() { + sizeContainerView(width: superview.frame.size.width) + } + } + } } extension TileContainerBase { From 1044282c33f8c90ee757736ad8fa3071eb5afb65 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 27 Jun 2024 10:24:18 -0500 Subject: [PATCH 049/117] fixed bug Signed-off-by: Matt Bruce --- VDS/Components/TileContainer/TileContainer.swift | 6 +++--- VDS/Protocols/LayoutConstraintable.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 9b121aa1..72ef86d0 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -436,9 +436,9 @@ open class TileContainerBase: Control where Padding containerView.removeDropShadows() } - containerView.dropShadowLayers?.forEach { $0.frame = bounds } - containerView.gradientLayers?.forEach { $0.frame = bounds } - + containerView.dropShadowLayers?.forEach { $0.frame = containerView.bounds } + containerView.gradientLayers?.forEach { $0.frame = containerView.bounds } + if width != nil || height != nil { var containerViewWidth: CGFloat? var containerViewHeight: CGFloat? diff --git a/VDS/Protocols/LayoutConstraintable.swift b/VDS/Protocols/LayoutConstraintable.swift index 3661bae0..18c89c14 100644 --- a/VDS/Protocols/LayoutConstraintable.swift +++ b/VDS/Protocols/LayoutConstraintable.swift @@ -714,7 +714,7 @@ extension LayoutConstraintable { let constraints = superview.constraints var leadingPinned = false var trailingPinned = false - + for constraint in constraints { if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .leading && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || (constraint.secondItem as? UIView == view && constraint.secondAttribute == .leading && constraint.relation == .equal && constraint.firstItem as? UIView == superview) || From 25ebcff2d904cbf7fb1c83958751ea87af14bc11 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 27 Jun 2024 12:52:38 -0500 Subject: [PATCH 050/117] rotation checking update size Signed-off-by: Matt Bruce --- VDS/Components/TileContainer/TileContainer.swift | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 72ef86d0..62e96890 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -266,6 +266,15 @@ open class TileContainerBase: Control where Padding containerView.bridge_isAccessibilityElementBlock = { [weak self] in self?.onClickSubscriber != nil } containerView.accessibilityHint = "Double tap to open." containerView.accessibilityLabel = nil + + NotificationCenter.default + .publisher(for: UIDevice.orientationDidChangeNotification) + .sink() { [weak self] _ in + DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) { [weak self] in + guard let self else { return } + setNeedsUpdate() + } + }.store(in: &subscribers) } @@ -423,7 +432,7 @@ open class TileContainerBase: Control where Padding heightConstraint?.activate() } } - + private func updateContainerView() { applyBackgroundEffects() @@ -469,8 +478,8 @@ open class TileContainerBase: Control where Padding } else { containerView.applyAlignment(distribution == .fill ? .fill : alignment) - if let superview, distribution == .fill, !isPinnedHorizontallyToSuperview() { - sizeContainerView(width: superview.frame.size.width) + if distribution == .fill, !isPinnedHorizontallyToSuperview(), let size = horizontalPinnedSize() { + sizeContainerView(width: size.width) } } } From 8dfcb416914921bb055a5623fbd4ee326be6a423 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 27 Jun 2024 12:52:52 -0500 Subject: [PATCH 051/117] refactor for layoutguide Signed-off-by: Matt Bruce --- VDS/Protocols/LayoutConstraintable.swift | 89 ++++++++++++++++++++---- 1 file changed, 77 insertions(+), 12 deletions(-) diff --git a/VDS/Protocols/LayoutConstraintable.swift b/VDS/Protocols/LayoutConstraintable.swift index 18c89c14..ba08e3c2 100644 --- a/VDS/Protocols/LayoutConstraintable.swift +++ b/VDS/Protocols/LayoutConstraintable.swift @@ -709,6 +709,69 @@ extension LayoutConstraintable { isPinnedVerticallyToSuperview() && isPinnedHorizontallyToSuperview() } + public func horizontalPinnedSize() -> CGSize? { + guard let view = self as? UIView, let superview = view.superview else { return nil } + let constraints = superview.constraints + + var leadingPinnedObject: AnyObject? + var trailingPinnedObject: AnyObject? + + for constraint in constraints { + if (constraint.firstItem === view && (constraint.firstAttribute == .leading || constraint.firstAttribute == .left)) { + leadingPinnedObject = constraint.secondItem as AnyObject? + } else if (constraint.secondItem === view && (constraint.secondAttribute == .leading || constraint.secondAttribute == .left)) { + leadingPinnedObject = constraint.firstItem as AnyObject? + } else if (constraint.firstItem === view && (constraint.firstAttribute == .trailing || constraint.firstAttribute == .right)) { + trailingPinnedObject = constraint.secondItem as AnyObject? + } else if (constraint.secondItem === view && (constraint.secondAttribute == .trailing || constraint.secondAttribute == .right)) { + trailingPinnedObject = constraint.firstItem as AnyObject? + } + } + + // Ensure both leading and trailing pinned objects are identified + if let leadingObject = leadingPinnedObject, let trailingObject = trailingPinnedObject { + + // Calculate the size based on the pinned objects + if let leadingView = leadingObject as? UIView, let trailingView = trailingObject as? UIView { + let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x + let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width + return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) + + } else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingGuide = trailingObject as? UILayoutGuide { + let leadingPosition = leadingGuide.layoutFrame.minX + let trailingPosition = trailingGuide.layoutFrame.maxX + return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) + + } else if let leadingView = leadingObject as? UIView, let trailingGuide = trailingObject as? UILayoutGuide { + let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x + let trailingPosition = trailingGuide.layoutFrame.maxX + return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) + + } else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingView = trailingObject as? UIView { + let leadingPosition = leadingGuide.layoutFrame.minX + let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width + return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) + } + + } else if let pinnedObject = leadingPinnedObject { + if let view = pinnedObject as? UIView { + return view.bounds.size + } else if let layoutGuide = pinnedObject as? UILayoutGuide { + return layoutGuide.layoutFrame.size + } + + } else if let pinnedObject = trailingPinnedObject { + if let view = pinnedObject as? UIView { + return view.bounds.size + } else if let layoutGuide = pinnedObject as? UILayoutGuide { + return layoutGuide.layoutFrame.size + } + + } + + return nil + } + public func isPinnedHorizontallyToSuperview() -> Bool { guard let view = self as? UIView, let superview = view.superview else { return false } let constraints = superview.constraints @@ -716,16 +779,16 @@ extension LayoutConstraintable { var trailingPinned = false for constraint in constraints { - if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .leading && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || - (constraint.secondItem as? UIView == view && constraint.secondAttribute == .leading && constraint.relation == .equal && constraint.firstItem as? UIView == superview) || - (constraint.firstItem as? UIView == view && constraint.firstAttribute == .left && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || - (constraint.secondItem as? UIView == view && constraint.secondAttribute == .left && constraint.relation == .equal && constraint.firstItem as? UIView == superview) { + if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .leading && constraint.relation == .equal) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .leading && constraint.relation == .equal) || + (constraint.firstItem as? UIView == view && constraint.firstAttribute == .left && constraint.relation == .equal) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .left && constraint.relation == .equal) { leadingPinned = true } - if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .trailing && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || - (constraint.secondItem as? UIView == view && constraint.secondAttribute == .trailing && constraint.relation == .equal && constraint.firstItem as? UIView == superview) || - (constraint.firstItem as? UIView == view && constraint.firstAttribute == .right && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || - (constraint.secondItem as? UIView == view && constraint.secondAttribute == .right && constraint.relation == .equal && constraint.firstItem as? UIView == superview) { + if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .trailing && constraint.relation == .equal) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .trailing && constraint.relation == .equal) || + (constraint.firstItem as? UIView == view && constraint.firstAttribute == .right && constraint.relation == .equal) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .right && constraint.relation == .equal) { trailingPinned = true } } @@ -740,18 +803,20 @@ extension LayoutConstraintable { var bottomPinned = false for constraint in constraints { - if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .top && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || - (constraint.secondItem as? UIView == view && constraint.secondAttribute == .top && constraint.relation == .equal && constraint.firstItem as? UIView == superview) { + if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .top && constraint.relation == .equal) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .top && constraint.relation == .equal) { topPinned = true } - if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .bottom && constraint.relation == .equal && constraint.secondItem as? UIView == superview) || - (constraint.secondItem as? UIView == view && constraint.secondAttribute == .bottom && constraint.relation == .equal && constraint.firstItem as? UIView == superview) { + if (constraint.firstItem as? UIView == view && constraint.firstAttribute == .bottom && constraint.relation == .equal) || + (constraint.secondItem as? UIView == view && constraint.secondAttribute == .bottom && constraint.relation == .equal) { bottomPinned = true } } return topPinned && bottomPinned } + + } From 9db5257132e4d5d1c0c9760c96730a6f9d81c28a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 27 Jun 2024 13:16:52 -0500 Subject: [PATCH 052/117] align left Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index cb2b5b88..17ededf4 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -370,7 +370,7 @@ extension DatePicker { var popoverY: CGFloat = 0 // Calculate horizontal position - if sourceFrameInParent.width < popoverWidth { + if sourceFrameInParent.width <= popoverWidth { if sourceFrameInParent.midX - popoverWidth / 2 < 0 { // Align to left popoverX = sourceFrameInParent.minX @@ -382,7 +382,7 @@ extension DatePicker { popoverX = sourceFrameInParent.midX - popoverWidth / 2 } } else { - popoverX = sourceFrameInParent.midX - popoverWidth / 2 + popoverX = sourceFrameInParent.minX //sourceFrameInParent.midX - popoverWidth / 2 } // Ensure the popover is within the parent's bounds horizontally From 7d399a42ef216f5474fd85a561266c9f980f09fa Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 27 Jun 2024 15:10:30 -0500 Subject: [PATCH 053/117] fixed animation Signed-off-by: Matt Bruce --- VDS/Components/Toggle/ToggleView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/Toggle/ToggleView.swift b/VDS/Components/Toggle/ToggleView.swift index 935ed519..dc5e9570 100644 --- a/VDS/Components/Toggle/ToggleView.swift +++ b/VDS/Components/Toggle/ToggleView.swift @@ -219,7 +219,7 @@ open class ToggleView: Control, Changeable, FormFieldable { } knobTrailingConstraint?.isActive = true knobLeadingConstraint?.isActive = true - setNeedsLayout() + layoutIfNeeded() } private func updateToggle() { From c51093cfaf3788e567c4ee29c560e5b104a9125b Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Fri, 28 Jun 2024 17:04:00 +0530 Subject: [PATCH 054/117] Digital ACT-191 ONEAPP-7013 story: update view as per aspect ratio --- VDS/Components/Carousel/Carousel.swift | 46 +++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 80c55b16..c5d0ab0e 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -355,6 +355,8 @@ open class Carousel: View { $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) } scrollView.addSubview(carouselSlot) + let size = ratioSize(for: minimumSlotWidth) + slotHeight = size.height carouselSlot .pinTop() .pinBottom() @@ -382,17 +384,51 @@ open class Carousel: View { } open override func reset() { - for subview in subviews { - for recognizer in subview.gestureRecognizers ?? [] { - subview.removeGestureRecognizer(recognizer) - } - } super.reset() + shouldUpdateView = false + aspectRatio = .ratio1x1 + layout = UIDevice.isIPad ? .threeUP : .oneUP + pagination = .init(kind: .lowContrast, floating: true) + paginationDisplay = .none + paginationInset = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X + gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX + peek = .standard + width = nil } //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- + private func ratioSize(for width: CGFloat) -> CGSize { + var height: CGFloat = width + + switch aspectRatio { + case .ratio1x1: + break; + case .ratio3x4: + height = (4 / 3) * width + case .ratio4x3: + height = (3 / 4) * width + case .ratio2x3: + height = (3 / 2) * width + case .ratio3x2: + height = (2 / 3) * width + case .ratio9x16: + height = (16 / 9) * width + case .ratio16x9: + height = (9 / 16) * width + case .ratio1x2: + height = (2 / 1) * width + case .ratio2x1: + height = (1 / 2) * width + + default: + break + } + + return CGSize(width: width, height: height) + } + func addlisteners() { nextButton.onClick = { _ in self.nextButtonClick() } previousButton.onClick = { _ in self.previousButtonClick() } From 532cb6c6809914cca81aec2e6641c15362f83cbb Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 27 Jun 2024 15:10:30 -0500 Subject: [PATCH 055/117] fixed animation Signed-off-by: Matt Bruce --- VDS/Components/Toggle/ToggleView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/Toggle/ToggleView.swift b/VDS/Components/Toggle/ToggleView.swift index 935ed519..dc5e9570 100644 --- a/VDS/Components/Toggle/ToggleView.swift +++ b/VDS/Components/Toggle/ToggleView.swift @@ -219,7 +219,7 @@ open class ToggleView: Control, Changeable, FormFieldable { } knobTrailingConstraint?.isActive = true knobLeadingConstraint?.isActive = true - setNeedsLayout() + layoutIfNeeded() } private func updateToggle() { From 99db2d3b1c1ebb48754e334e3ae8eb9a3f61f436 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 27 Jun 2024 13:16:52 -0500 Subject: [PATCH 056/117] align left Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index cb2b5b88..17ededf4 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -370,7 +370,7 @@ extension DatePicker { var popoverY: CGFloat = 0 // Calculate horizontal position - if sourceFrameInParent.width < popoverWidth { + if sourceFrameInParent.width <= popoverWidth { if sourceFrameInParent.midX - popoverWidth / 2 < 0 { // Align to left popoverX = sourceFrameInParent.minX @@ -382,7 +382,7 @@ extension DatePicker { popoverX = sourceFrameInParent.midX - popoverWidth / 2 } } else { - popoverX = sourceFrameInParent.midX - popoverWidth / 2 + popoverX = sourceFrameInParent.minX //sourceFrameInParent.midX - popoverWidth / 2 } // Ensure the popover is within the parent's bounds horizontally From 51b151a73f3a130caa20b0ee77c6630d202242f1 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 28 Jun 2024 17:27:37 -0500 Subject: [PATCH 057/117] partial bugfix Signed-off-by: Matt Bruce --- .../TextFields/InputField/FieldTypes/CreditCard.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift index 429b68be..f0a40bc7 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/CreditCard.swift @@ -51,6 +51,14 @@ extension InputField { } } + public var accessibilityLabel: String { + switch self { + case .generic, .placeholder: return "credit card" + default: return rawValue + } + } + + func separatorIndices(_ length: Int) -> [Int] { var indices: [Int] = [4, 8, 12] switch self { @@ -135,7 +143,7 @@ extension InputField { fileprivate func updateLeftImage(_ inputField: InputField) { let imageName = inputField.cardType.imageName(surface: inputField.surface) creditCardImageView.image = BundleManager.shared.image(for: imageName) - creditCardImageView.accessibilityLabel = inputField.cardType.rawValue + creditCardImageView.accessibilityLabel = inputField.cardType.accessibilityLabel } override func updateView(_ inputField: InputField) { From 842bb6f0ad55c36cf7fadaf5c3ff40205db3e04e Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 28 Jun 2024 18:08:50 -0500 Subject: [PATCH 058/117] updated to work at 100% width of parent if not set as well as calculating height if there is an aspect ration. --- .../TileContainer/TileContainer.swift | 63 +++++++++---------- VDS/Components/Tilelet/Tilelet.swift | 4 ++ 2 files changed, 33 insertions(+), 34 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 62e96890..d7c3f74f 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -117,10 +117,6 @@ open class TileContainerBase: Control where Padding $0.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) } - open var distribution: LayoutDistribution = .fill { didSet { setNeedsUpdate() } } - - open var alignment: LayoutAlignment = .center { didSet { setNeedsUpdate() } } - //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- @@ -136,7 +132,7 @@ open class TileContainerBase: Control where Padding } /// This controls the aspect ratio for the component. - open var aspectRatio: AspectRatio = .none { didSet { setNeedsUpdate() } } + open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } } /// Sets the background color for the component. open var color: BackgroundColor? { didSet { setNeedsUpdate() } } @@ -190,11 +186,6 @@ open class TileContainerBase: Control where Padding internal var widthConstraint: NSLayoutConstraint? internal var heightConstraint: NSLayoutConstraint? - internal var contentViewTopConstraint: NSLayoutConstraint? - internal var contentViewBottomConstraint: NSLayoutConstraint? - internal var contentViewLeadingConstraint: NSLayoutConstraint? - internal var contentViewTrailingConstraint: NSLayoutConstraint? - //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- @@ -231,15 +222,21 @@ open class TileContainerBase: Control where Padding open override func setup() { super.setup() isAccessibilityElement = false + addSubview(containerView) + containerView.pinToSuperView() + containerView.addSubview(backgroundImageView) - containerView.addSubview(contentView) - containerView.addSubview(highlightView) - backgroundImageView.pinToSuperView() - widthConstraint = containerView.widthAnchor.constraint(equalToConstant: 0).deactivate() - heightConstraint = containerView.heightAnchor.constraint(equalToConstant: 0).deactivate() + containerView.addSubview(contentView) + contentView.pinToSuperView() + + containerView.addSubview(highlightView) + highlightView.pinToSuperView() + + widthConstraint = widthAnchor.constraint(equalToConstant: 0).deactivate() + heightConstraint = heightAnchor.constraint(equalToConstant: 0).deactivate() backgroundImageView.setContentHuggingPriority(.defaultLow, for: .horizontal) backgroundImageView.setContentHuggingPriority(.defaultLow, for: .vertical) @@ -248,19 +245,11 @@ open class TileContainerBase: Control where Padding backgroundImageView.isUserInteractionEnabled = false backgroundImageView.isHidden = true - contentViewTopConstraint = contentView.pinTop(anchor: containerView.topAnchor, constant: padding.value) - contentViewBottomConstraint = containerView.pinBottom(anchor: contentView.bottomAnchor, constant: padding.value) - contentViewLeadingConstraint = contentView.pinLeading(anchor: containerView.leadingAnchor, constant: padding.value) - contentViewTrailingConstraint = containerView.pinTrailing(anchor: contentView.trailingAnchor, constant: padding.value) - - highlightView.pin(containerView) highlightView.isHidden = true highlightView.backgroundColor = .clear //corner radius containerView.layer.cornerRadius = cornerRadius - backgroundImageView.layer.cornerRadius = cornerRadius - highlightView.layer.cornerRadius = cornerRadius containerView.clipsToBounds = true containerView.bridge_isAccessibilityElementBlock = { [weak self] in self?.onClickSubscriber != nil } @@ -311,11 +300,9 @@ open class TileContainerBase: Control where Padding containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor containerView.layer.borderWidth = showBorder ? VDSFormControls.borderWidth : 0 - contentViewTopConstraint?.constant = padding.value - contentViewLeadingConstraint?.constant = padding.value - contentViewBottomConstraint?.constant = padding.value - contentViewTrailingConstraint?.constant = padding.value - + contentView.removeConstraints() + contentView.pinToSuperView(.uniform(padding.value)) + updateContainerView() } @@ -472,14 +459,22 @@ open class TileContainerBase: Control where Padding containerViewWidth = size.width containerViewHeight = size.height } - sizeContainerView(width: containerViewWidth, height: containerViewHeight) - containerView.applyAlignment(alignment) - } else { - containerView.applyAlignment(distribution == .fill ? .fill : alignment) - if distribution == .fill, !isPinnedHorizontallyToSuperview(), let size = horizontalPinnedSize() { - sizeContainerView(width: size.width) + if let parentSize = horizontalPinnedSize() { + + var containerViewWidth: CGFloat? + var containerViewHeight: CGFloat? + + let size = ratioSize(for: parentSize.width) + if aspectRatio == .none { + containerViewWidth = size.width + } else { + containerViewWidth = size.width + containerViewHeight = size.height + } + + sizeContainerView(width: containerViewWidth, height: containerViewHeight) } } } diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index 0a7178e2..0da20709 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -303,6 +303,8 @@ open class Tilelet: TileContainerBase { open override func setup() { super.setup() color = .black + aspectRatio = .none + addContentView(stackView) //badge @@ -385,6 +387,8 @@ open class Tilelet: TileContainerBase { /// Resets to default settings. open override func reset() { shouldUpdateView = false + super.reset() + aspectRatio = .none color = .black //models badgeModel = nil From 540dc35e2f88e292a2f08711d255fff0a60f6beb Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Sat, 29 Jun 2024 09:45:14 -0500 Subject: [PATCH 059/117] added cornerRadius back Signed-off-by: Matt Bruce --- VDS/Components/TileContainer/TileContainer.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index d7c3f74f..6eeaf316 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -250,6 +250,8 @@ open class TileContainerBase: Control where Padding //corner radius containerView.layer.cornerRadius = cornerRadius + backgroundImageView.layer.cornerRadius = cornerRadius + highlightView.layer.cornerRadius = cornerRadius containerView.clipsToBounds = true containerView.bridge_isAccessibilityElementBlock = { [weak self] in self?.onClickSubscriber != nil } From 90e88f37da1ff404e688356629c9e802a561f63c Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Sat, 29 Jun 2024 10:24:28 -0500 Subject: [PATCH 060/117] updated helper Signed-off-by: Matt Bruce --- VDS/Components/Tilelet/Tilelet.swift | 6 +++--- VDS/Extensions/UIView+Accessibility.swift | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index 0da20709..615c0b40 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -414,7 +414,7 @@ open class Tilelet: TileContainerBase { /// Used to update any Accessibility properties. open override var accessibilityElements: [Any]? { get { - var views = [UIView]() + var views = [AnyObject]() // grab the available views in order if badgeModel != nil { @@ -422,9 +422,9 @@ open class Tilelet: TileContainerBase { } if titleModel != nil || subTitleModel != nil || eyebrowModel != nil { - views.append(titleLockup) + let titleLockupViews = gatherAccessibilityElements(from: titleLockup) + views.append(contentsOf: titleLockupViews) } - containerView.setAccessibilityLabel(for: views) // get the views to return diff --git a/VDS/Extensions/UIView+Accessibility.swift b/VDS/Extensions/UIView+Accessibility.swift index ee247175..153c517d 100644 --- a/VDS/Extensions/UIView+Accessibility.swift +++ b/VDS/Extensions/UIView+Accessibility.swift @@ -14,14 +14,14 @@ extension UIView { /// - views: Array of Views that you want to join the accessibilityLabel. /// - separator: Separator used between the accessibilityLabel for each UIView. /// - Returns: Joined String. - public func combineAccessibilityLabel(for views: [UIView], separator: String = ", ") -> String? { - let labels = views.map({($0.accessibilityLabel?.isEmpty ?? true) ? nil : $0.accessibilityLabel}).compactMap({$0}) + public func combineAccessibilityLabel(for views: [AnyObject], separator: String = ", ") -> String? { + let labels: [String] = views.map({($0.accessibilityLabel?.isEmpty ?? true) ? nil : $0.accessibilityLabel}).compactMap({$0}) return labels.joined(separator: separator) } /// AccessibilityLabel helper for joining the accessibilityLabel property of all views passed in. /// - Parameter views: Array of Views that you want to join the accessibilityLabel. - public func setAccessibilityLabel(for views: [UIView]) { + public func setAccessibilityLabel(for views: [AnyObject]) { accessibilityLabel = combineAccessibilityLabel(for: views) } @@ -50,8 +50,8 @@ extension UIView { return isIntersecting } - public func gatherAccessibilityElements(from view: UIView) -> [Any] { - var elements: [Any] = [] + public func gatherAccessibilityElements(from view: AnyObject) -> [AnyObject] { + var elements: [AnyObject] = [] for subview in view.subviews { if subview.isAccessibilityElement && subview.isVisibleOnScreen { From fa5051d258f21cc82f4a0da0c2690280819a3a7f Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Mon, 1 Jul 2024 10:32:06 +0530 Subject: [PATCH 061/117] Digital ACT-191 ONEAPP-7013 story: flexible width support, Carousel can be set to any pixel or percentage --- VDS/Components/Carousel/Carousel.swift | 124 +++++++++++++++---------- 1 file changed, 74 insertions(+), 50 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index c5d0ab0e..5b5c99bf 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -79,7 +79,7 @@ open class Carousel: View { // MARK: - Public Properties //-------------------------------------------------- /// Aspect-ratio options for tilelet in the carousel. If 'none' is passed, the tilelet will take the height of the tallest item in the carousel. - open var aspectRatio: Tilelet.AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } } + open var aspectRatio: Tilelet.AspectRatio = .none { didSet { setNeedsUpdate() } } /// Data used to render tilelets in the carousel. open var data: [Any] = [] { didSet { setNeedsUpdate() } } @@ -92,10 +92,13 @@ open class Carousel: View { switch newValue { case .percentage(let percentage): if percentage >= 10 && percentage <= 100.0 { - _width = newValue + let expectedWidth = safeAreaLayoutGuide.layoutFrame.size.width * (percentage/100) + if expectedWidth > carouselScrollbarMinWidth { + _width = newValue + } } case .value(let value): - if value > minimumSlotWidth { /*(size.minimumSlotWidth)*/ + if value > carouselScrollbarMinWidth { _width = newValue } } @@ -256,6 +259,7 @@ open class Carousel: View { private var containerViewHeightConstraint: NSLayoutConstraint? private var prevButtonLeadingConstraint: NSLayoutConstraint? private var nextButtonTrailingConstraint: NSLayoutConstraint? + private var containerLeadingConstraint: NSLayoutConstraint? // The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile. let scrollbarTopSpace = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X @@ -263,6 +267,7 @@ open class Carousel: View { var slotHeight = 100.0 var peekMinimum = 24.0 var minimumSlotWidth = 0.0 + var carouselScrollbarMinWidth = 96.0 //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -279,11 +284,12 @@ open class Carousel: View { containerView .pinTop() .pinBottom() - .pinLeading() + .pinLeadingGreaterThanOrEqualTo() .pinTrailing() .heightGreaterThanEqualTo(containerSize.height) - containerView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() - + containerView.centerYAnchor.constraint(equalTo: centerYAnchor).activate() + containerLeadingConstraint = containerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 0) + // add content stackview containerView.addSubview(contentStackView) @@ -312,13 +318,30 @@ open class Carousel: View { .pinLeading() .pinTrailing() .heightGreaterThanEqualTo(containerSize.height) - contentStackView.centerXAnchor.constraint(equalTo: centerXAnchor).activate() addlisteners() } open override func updateView() { super.updateView() + + if containerView.frame.size.width > 0 { + if let width { + containerLeadingConstraint?.deactivate() + switch width { + case .value(let value): + var expectedWidth = value + let fullWidth = safeAreaLayoutGuide.layoutFrame.size.width + expectedWidth = expectedWidth > fullWidth ? fullWidth : expectedWidth + containerLeadingConstraint?.constant = safeAreaLayoutGuide.layoutFrame.size.width - expectedWidth + case .percentage(let percentage): + let expectedWidth = safeAreaLayoutGuide.layoutFrame.size.width * (percentage/100) + containerLeadingConstraint?.constant = safeAreaLayoutGuide.layoutFrame.size.width - expectedWidth + } + containerLeadingConstraint?.activate() + } + } + carouselScrollBar.numberOfSlides = data.count carouselScrollBar.layout = _layout carouselScrollBar.position = (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) ? 1 : carouselScrollBar.position @@ -335,58 +358,59 @@ open class Carousel: View { peek = .standard } - containerViewHeightConstraint?.isActive = false - containerStackHeightConstraint?.isActive = false updatePaginationControls() getSlotWidth() - - // perform a loop to iterate each subView - scrollView.subviews.forEach { subView in - // removing subView from its parent view - subView.removeFromSuperview() - } - - // add carousel items - if data.count > 0 { - var xPos = 0.0 - for x in 0...data.count - 1 { - let carouselSlot = View().with { - $0.clipsToBounds = true - $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) - } - scrollView.addSubview(carouselSlot) - let size = ratioSize(for: minimumSlotWidth) - slotHeight = size.height - carouselSlot - .pinTop() - .pinBottom() - .pinLeading(xPos) - .width(minimumSlotWidth) - .height(slotHeight) - carouselSlot.layer.cornerRadius = 12.0 - xPos = xPos + minimumSlotWidth + gutter.value + + if containerView.frame.size.width > 0 { + containerViewHeightConstraint?.isActive = false + containerStackHeightConstraint?.isActive = false + + // perform a loop to iterate each subView + scrollView.subviews.forEach { subView in + // removing subView from its parent view + subView.removeFromSuperview() } - scrollView.contentSize = CGSize(width: xPos - gutter.value, height: slotHeight) + + // add carousel items + if data.count > 0 { + var xPos = 0.0 + for x in 0...data.count - 1 { + let carouselSlot = View().with { + $0.clipsToBounds = true + $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) + } + scrollView.addSubview(carouselSlot) + let size = ratioSize(for: minimumSlotWidth) + slotHeight = size.height + carouselSlot + .pinTop() + .pinBottom() + .pinLeading(xPos) + .width(minimumSlotWidth) + .height(slotHeight) + carouselSlot.layer.cornerRadius = 12.0 + xPos = xPos + minimumSlotWidth + gutter.value + } + scrollView.contentSize = CGSize(width: xPos - gutter.value, height: slotHeight) + } + + let containerHeight = slotHeight + scrollbarTopSpace + containerSize.height + if carouselScrollBar.isHidden { + containerStackHeightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: slotHeight) + containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: slotHeight) + } else { + containerStackHeightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: containerHeight) + containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: containerHeight) + } + containerViewHeightConstraint?.isActive = true + containerStackHeightConstraint?.isActive = true } - - let containerHeight = slotHeight + scrollbarTopSpace + containerSize.height - if carouselScrollBar.isHidden { - containerStackHeightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: slotHeight) - containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: slotHeight) - } else { - containerStackHeightConstraint = contentStackView.heightAnchor.constraint(equalToConstant: containerHeight) - containerViewHeightConstraint = containerView.heightAnchor.constraint(equalToConstant: containerHeight) - } - containerViewHeightConstraint?.isActive = true - containerStackHeightConstraint?.isActive = true - - layoutIfNeeded() } open override func reset() { super.reset() shouldUpdateView = false - aspectRatio = .ratio1x1 + aspectRatio = .none layout = UIDevice.isIPad ? .threeUP : .oneUP pagination = .init(kind: .lowContrast, floating: true) paginationDisplay = .none From 6f5e94887e6de1f480ff95e7abe442516e177ac0 Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Mon, 1 Jul 2024 14:13:44 +0530 Subject: [PATCH 062/117] activating leading constratint --- VDS/Components/Carousel/Carousel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 5b5c99bf..e461bc99 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -289,6 +289,7 @@ open class Carousel: View { .heightGreaterThanEqualTo(containerSize.height) containerView.centerYAnchor.constraint(equalTo: centerYAnchor).activate() containerLeadingConstraint = containerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 0) + containerLeadingConstraint?.activate() // add content stackview containerView.addSubview(contentStackView) From f13451ea37fb80aea10823fc4b86eb1ce42b2303 Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Mon, 1 Jul 2024 15:03:00 +0530 Subject: [PATCH 063/117] Digital ACT-191 ONEAPP-7013 story: Standard Peek supported for all Tablet viewports and layouts and for 1up layouts on Mobile viewports only. --- VDS/Components/Carousel/Carousel.swift | 46 +++++++++++++++----------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index e461bc99..0347efc0 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -358,10 +358,34 @@ open class Carousel: View { if UIDevice.isIPad && peek == .minimum { peek = .standard } + + // Standard(Default) Peek - Supported for all Tablet viewports and layouts. Supported only for 1up layouts on Mobile viewports. + if peek == .standard && !UIDevice.isIPad && layout != CarouselScrollbar.Layout.oneUP { + peek = .minimum + } updatePaginationControls() getSlotWidth() - + addCarouselSlots() + } + + open override func reset() { + super.reset() + shouldUpdateView = false + aspectRatio = .none + layout = UIDevice.isIPad ? .threeUP : .oneUP + pagination = .init(kind: .lowContrast, floating: true) + paginationDisplay = .none + paginationInset = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X + gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX + peek = .standard + width = nil + } + + //-------------------------------------------------- + // MARK: - Private Methods + //-------------------------------------------------- + private func addCarouselSlots() { if containerView.frame.size.width > 0 { containerViewHeightConstraint?.isActive = false containerStackHeightConstraint?.isActive = false @@ -408,22 +432,6 @@ open class Carousel: View { } } - open override func reset() { - super.reset() - shouldUpdateView = false - aspectRatio = .none - layout = UIDevice.isIPad ? .threeUP : .oneUP - pagination = .init(kind: .lowContrast, floating: true) - paginationDisplay = .none - paginationInset = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X - gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX - peek = .standard - width = nil - } - - //-------------------------------------------------- - // MARK: - Private Methods - //-------------------------------------------------- private func ratioSize(for width: CGFloat) -> CGSize { var height: CGFloat = width @@ -543,10 +551,10 @@ open class Carousel: View { func updateScrollPosition(position: Int, callbackText: String) { if carouselScrollBar.numberOfSlides > 0 { - let contentOffsetWidth = scrollView.contentSize.width + let scrollContentSizeWidth = scrollView.contentSize.width let totalPositions = totalPositions() let multiplier: Float = (position == 1) ? 0 : Float((position)-1) / Float(totalPositions) - let xPos = (position == totalPositions) ? (contentOffsetWidth - containerView.frame.size.width) : CGFloat(Float(contentOffsetWidth) * multiplier) + let xPos = (position == totalPositions) ? (scrollContentSizeWidth - containerView.frame.size.width) : CGFloat(Float(scrollContentSizeWidth) * multiplier) carouselScrollBar.scrubberId = position let yPos = scrollView.contentOffset.y scrollView.setContentOffset(CGPoint(x: xPos, y: yPos), animated: true) From bf731a8a84597b1d571c0049ae037caee311c0b0 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 1 Jul 2024 14:24:08 -0500 Subject: [PATCH 064/117] added in accessibility to icons Signed-off-by: Matt Bruce --- VDS/Components/Tilelet/Tilelet.swift | 29 ++++++++++++++----- .../Tilelet/TileletIconModels.swift | 8 +++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index 615c0b40..e52332ff 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -205,15 +205,10 @@ open class Tilelet: TileContainerBase { } /// Descriptive Icon positioned in the contentView. - open var descriptiveIcon = Icon().with { - $0.isAccessibilityElement = false - } + open var descriptiveIcon = Icon() /// Directional Icon positioned in the contentView. - open var directionalIcon = Icon().with { - $0.isAccessibilityElement = false - $0.name = .rightArrow - } + open var directionalIcon = Icon() private var _textWidth: TextWidth? @@ -304,7 +299,7 @@ open class Tilelet: TileContainerBase { super.setup() color = .black aspectRatio = .none - + addContentView(stackView) //badge @@ -382,6 +377,16 @@ open class Tilelet: TileContainerBase { titleLockupSubTitleLabelHeightGreaterThanConstraint = titleLockup.subTitleLabel.heightGreaterThanEqualTo(constant: titleLockup.subTitleLabel.minimumLineHeight) titleLockupSubTitleLabelHeightGreaterThanConstraint?.priority = .defaultHigh titleLockupSubTitleLabelHeightGreaterThanConstraint?.activate() + + directionalIcon.bridge_accessibilityLabelBlock = { [weak self] in + guard let self, let directionalIconModel else { return nil } + return directionalIconModel.accessibleText + } + + descriptiveIcon.bridge_accessibilityLabelBlock = { [weak self] in + guard let self, let descriptiveIconModel else { return nil } + return descriptiveIconModel.accessibleText + } } /// Resets to default settings. @@ -425,6 +430,14 @@ open class Tilelet: TileContainerBase { let titleLockupViews = gatherAccessibilityElements(from: titleLockup) views.append(contentsOf: titleLockupViews) } + + if descriptiveIconModel != nil { + views.append(descriptiveIcon) + + } else if directionalIconModel != nil { + views.append(directionalIcon) + } + containerView.setAccessibilityLabel(for: views) // get the views to return diff --git a/VDS/Components/Tilelet/TileletIconModels.swift b/VDS/Components/Tilelet/TileletIconModels.swift index 58057942..2a8b6dac 100644 --- a/VDS/Components/Tilelet/TileletIconModels.swift +++ b/VDS/Components/Tilelet/TileletIconModels.swift @@ -66,6 +66,10 @@ extension Tilelet { public var iconName: Icon.Name { return self == .rightArrow ? .rightArrow : .externalLink } + + public var accessibilityLabel: String { + self == .rightArrow ? "Right Arrow" : "External Link" + } } public enum IconSize: String, EnumSubset { @@ -80,7 +84,7 @@ extension Tilelet { public var iconColor: IconColor? /// Accessible Text for the Icon - public var accessibleText: String + public var accessibleText: String? /// Enum for a icon type you want shown.. public var iconType: IconType @@ -95,7 +99,7 @@ extension Tilelet { self.iconType = iconType self.iconColor = iconColor - self.accessibleText = accessibleText ?? iconType.iconName.rawValue + self.accessibleText = accessibleText ?? iconType.accessibilityLabel self.size = size } } From 72e86dafc29cd7dbddff4b75fd3fc03f5f03a694 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 1 Jul 2024 14:26:41 -0500 Subject: [PATCH 065/117] CXTDT-560485 - updated text Signed-off-by: Matt Bruce --- VDS/Components/Tilelet/TileletIconModels.swift | 2 +- VDS/SupportingFiles/ReleaseNotes.txt | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/VDS/Components/Tilelet/TileletIconModels.swift b/VDS/Components/Tilelet/TileletIconModels.swift index 2a8b6dac..03b2d225 100644 --- a/VDS/Components/Tilelet/TileletIconModels.swift +++ b/VDS/Components/Tilelet/TileletIconModels.swift @@ -68,7 +68,7 @@ extension Tilelet { } public var accessibilityLabel: String { - self == .rightArrow ? "Right Arrow" : "External Link" + self == .rightArrow ? "Directional right arrow" : "External link" } } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 54be0815..12661050 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,3 +1,7 @@ +1.0.70 +---------------- +- CXTDT-560485 - Tilelet - Accessibility Icons + 1.0.69 ---------------- - DatePicker - Refactored how this is shown From 315601e048c5e4e2b8bf205c94c563e5d945418c Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 1 Jul 2024 15:26:51 -0500 Subject: [PATCH 066/117] CXTDT-577463 - InputField - Accessibility - #1 Typing Feedback Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 8 +++++++- VDS/Components/TextFields/TextArea/TextArea.swift | 11 ++++++++++- VDS/SupportingFiles/ReleaseNotes.txt | 2 ++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index f7f54896..11a8ae17 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -105,6 +105,8 @@ open class InputField: EntryFieldBase { $0.translatesAutoresizingMaskIntoConstraints = false $0.textStyle = TextStyle.bodyLarge $0.isAccessibilityElement = false + $0.autocorrectionType = .no + $0.spellCheckingType = .no } /// Color configuration for the textField. @@ -360,7 +362,11 @@ extension InputField: UITextFieldDelegate { } public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - return fieldType.handler().textField(self, textField: textField, shouldChangeCharactersIn: range, replacementString: string) + let shouldChange = fieldType.handler().textField(self, textField: textField, shouldChangeCharactersIn: range, replacementString: string) + if shouldChange { + UIAccessibility.post(notification: .announcement, argument: string) + } + return shouldChange } } diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index d0396d7a..c668a851 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -101,6 +101,7 @@ open class TextArea: EntryFieldBase { $0.isScrollEnabled = true $0.textContainerInset = .zero $0.autocorrectionType = .no + $0.spellCheckingType = .no $0.textContainer.lineFragmentPadding = 0 } @@ -132,6 +133,7 @@ open class TextArea: EntryFieldBase { super.setup() accessibilityHintText = "Double tap to edit" + textView.delegate = self //events textView @@ -226,7 +228,7 @@ open class TextArea: EntryFieldBase { } } - func textViewDidChange(_ textView: UITextView) { + public func textViewDidChange(_ textView: UITextView) { //dynamic textView Height sizing based on Figma //if you want it to work "as-is" delete this code @@ -288,3 +290,10 @@ open class TextArea: EntryFieldBase { //-------------------------------------------------- var countRule = CharacterCountRule() } + +extension TextArea: UITextViewDelegate { + public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { + UIAccessibility.post(notification: .announcement, argument: text) + return true + } +} diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 12661050..0be4ba37 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,6 +1,8 @@ 1.0.70 ---------------- +- CXTDT-577463 - InputField - Accessibility - #1 Typing Feedback - CXTDT-560485 - Tilelet - Accessibility Icons +- DatePicker - Final logic for how the calendar shows. 1.0.69 ---------------- From 801b488eb43408e228bc876fdffa7e55a33f6a60 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 1 Jul 2024 15:28:44 -0500 Subject: [PATCH 067/117] 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 a5403c49..ee0265b4 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -1535,7 +1535,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 68; + CURRENT_PROJECT_VERSION = 70; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1573,7 +1573,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 68; + CURRENT_PROJECT_VERSION = 70; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; From 668b20f77b8942f76a29f3a0ca06b02fc4fe00f7 Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Tue, 2 Jul 2024 13:22:39 +0530 Subject: [PATCH 068/117] Digital ACT-191 ONEAPP-7013 story: Updating scrollbar position on scrolling carousel content --- VDS/Components/Carousel/Carousel.swift | 57 ++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 0347efc0..498e1dc5 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -365,7 +365,6 @@ open class Carousel: View { } updatePaginationControls() - getSlotWidth() addCarouselSlots() } @@ -386,6 +385,7 @@ open class Carousel: View { // MARK: - Private Methods //-------------------------------------------------- private func addCarouselSlots() { + getSlotWidth() if containerView.frame.size.width > 0 { containerViewHeightConstraint?.isActive = false containerStackHeightConstraint?.isActive = false @@ -405,6 +405,7 @@ open class Carousel: View { $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) } scrollView.addSubview(carouselSlot) + scrollView.delegate = self let size = ratioSize(for: minimumSlotWidth) slotHeight = size.height carouselSlot @@ -553,15 +554,49 @@ open class Carousel: View { if carouselScrollBar.numberOfSlides > 0 { let scrollContentSizeWidth = scrollView.contentSize.width let totalPositions = totalPositions() - let multiplier: Float = (position == 1) ? 0 : Float((position)-1) / Float(totalPositions) - let xPos = (position == totalPositions) ? (scrollContentSizeWidth - containerView.frame.size.width) : CGFloat(Float(scrollContentSizeWidth) * multiplier) - carouselScrollBar.scrubberId = position + var xPos = 0.0 + if position == 1 { + xPos = 0.0 + } else if position == totalPositions { + xPos = scrollContentSizeWidth - containerView.frame.size.width + } else { + let isScrollbarSuppressed = data.count > 0 && layout.value == data.count + let isPeekMinimumOnTablet = UIDevice.isIPad && peek == .minimum + if !isScrollbarSuppressed { + let subpart = minimumSlotWidth + gutter.value + let xPosition = CGFloat( Float(position-1) * Float(layout.value) * Float(subpart)) + switch peek { + case .standard: + if UIDevice.isIPad { + xPos = xPosition - (minimumSlotWidth/(CGFloat(layout.value) + 3))/2 + } else if layout == .oneUP { + xPos = xPosition - gutter.value - (minimumSlotWidth/4)/2 + } + case .minimum: + xPos = isPeekMinimumOnTablet ? xPosition : xPosition - peekMinimum/2 + case .none: + xPos = xPosition + } + } + } + carouselScrollBar.scrubberId = position+1 let yPos = scrollView.contentOffset.y scrollView.setContentOffset(CGPoint(x: xPos, y: yPos), animated: true) showPaginationControls() } } + func updateScrollbarPosition(targetContentOffsetXPos:CGFloat) { + let scrollContentSizeWidth = scrollView.contentSize.width + let totalPositions = totalPositions() + let layoutSpace = Int (floor( Double(scrollContentSizeWidth / Double(totalPositions)))) + let remindSpace = Int(targetContentOffsetXPos) % layoutSpace + var contentPos = (Int(targetContentOffsetXPos) / layoutSpace) + 1 + contentPos = remindSpace > layoutSpace/2 ? contentPos+1 : contentPos + carouselScrollBar.position = contentPos + updateScrollPosition(position: contentPos, callbackText: "ScrollViewMoved") + } + private func totalPositions() -> Int { return Int (ceil (Double(carouselScrollBar.numberOfSlides) / Double(_layout.value))) } @@ -576,3 +611,17 @@ open class Carousel: View { } } } + +extension Carousel: UIScrollViewDelegate { + //-------------------------------------------------- + // MARK: - UIScrollView Delegate + //-------------------------------------------------- + public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + updateScrollbarPosition(targetContentOffsetXPos: targetContentOffset.pointee.x) + } + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + var visibleRect = CGRect() + visibleRect.origin = scrollView.contentOffset + updateScrollbarPosition(targetContentOffsetXPos: visibleRect.origin.x) + } +} From 8619c641096645cf2b80bf8a288cecb93030074b Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Tue, 2 Jul 2024 14:47:20 +0530 Subject: [PATCH 069/117] Digital ACT-191 ONEAPP-7013 story: refactored code --- VDS/Components/Carousel/Carousel.swift | 247 +++++++++++++------------ 1 file changed, 126 insertions(+), 121 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 498e1dc5..dbc10d98 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -183,13 +183,11 @@ open class Carousel: View { /// If provided, will set the alignment for slot content when the slots has different heights. open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } } - + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - // Sizes are from InVision design specs. - internal var containerSize: CGSize { CGSize(width: 320, height: 44) } - + internal var containerSize: CGSize { CGSize(width: frame.size.width, height: 44) } private let contentStackView = UIStackView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.axis = .vertical @@ -260,7 +258,7 @@ open class Carousel: View { private var prevButtonLeadingConstraint: NSLayoutConstraint? private var nextButtonTrailingConstraint: NSLayoutConstraint? private var containerLeadingConstraint: NSLayoutConstraint? - + // The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile. let scrollbarTopSpace = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X @@ -268,18 +266,21 @@ open class Carousel: View { var peekMinimum = 24.0 var minimumSlotWidth = 0.0 var carouselScrollbarMinWidth = 96.0 + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- + /// Executed on initialization for this View. open override func initialSetup() { super.initialSetup() } + /// 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() isAccessibilityElement = false - // add containerView + // Add containerView addSubview(containerView) containerView .pinTop() @@ -287,18 +288,19 @@ open class Carousel: View { .pinLeadingGreaterThanOrEqualTo() .pinTrailing() .heightGreaterThanEqualTo(containerSize.height) + containerView.centerYAnchor.constraint(equalTo: centerYAnchor).activate() containerLeadingConstraint = containerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 0) containerLeadingConstraint?.activate() - - // add content stackview + + // Add content stackview containerView.addSubview(contentStackView) - // add scrollview + // Add scrollview scrollContainerView.addSubview(scrollView) scrollView.pinToSuperView() - // add pagination button icons + // Add pagination button icons scrollContainerView.addSubview(previousButton) previousButton .pinLeadingGreaterThanOrEqualTo() @@ -309,7 +311,7 @@ open class Carousel: View { .pinTrailingLessThanOrEqualTo() .pinCenterY() - // add scroll container view & carousel scrollbar + // Add scroll container view & carousel scrollbar contentStackView.addArrangedSubview(scrollContainerView) contentStackView.addArrangedSubview(carouselScrollBar) contentStackView.setCustomSpacing(scrollbarTopSpace, after: scrollContainerView) @@ -322,7 +324,8 @@ open class Carousel: View { addlisteners() } - + + /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() @@ -353,7 +356,7 @@ open class Carousel: View { if peek == .none { paginationDisplay = .persistent } - + // Minimum (Mobile only) Supported only on Mobile viewports. If a user passes Minimum for tablet carousel, the peek reverts to Standard. if UIDevice.isIPad && peek == .minimum { peek = .standard @@ -363,11 +366,12 @@ open class Carousel: View { if peek == .standard && !UIDevice.isIPad && layout != CarouselScrollbar.Layout.oneUP { peek = .minimum } - + updatePaginationControls() addCarouselSlots() } + /// Resets to default settings. open override func reset() { super.reset() shouldUpdateView = false @@ -384,19 +388,69 @@ open class Carousel: View { //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- + private func addlisteners() { + nextButton.onClick = { _ in self.nextButtonClick() } + previousButton.onClick = { _ in self.previousButtonClick() } + + /// Will be called when the thumb move forward. + carouselScrollBar.onMoveForward = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onMoveForward") + } + + /// Will be called when the thumb move backward. + carouselScrollBar.onMoveBackward = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onMoveBackward") + } + + /// Will be called when the thumb touch start. + carouselScrollBar.onThumbTouchStart = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchStart") + } + + /// Will be called when the thumb touch end. + carouselScrollBar.onThumbTouchEnd = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchEnd") + } + } + + private func updatePaginationControls() { + containerView.surface = surface + showPaginationControls() + previousButton.kind = pagination.kind + previousButton.floating = pagination.floating + nextButton.kind = pagination.kind + nextButton.floating = pagination.floating + previousButton.surface = surface + nextButton.surface = surface + } + + private func showPaginationControls() { + if carouselScrollBar.numberOfSlides == _layout.value { + previousButton.isHidden = true + nextButton.isHidden = true + } else { + previousButton.isHidden = (carouselScrollBar.position == 1) || (paginationDisplay == .none) + nextButton.isHidden = (carouselScrollBar.position == totalPositions()) || (paginationDisplay == .none) + } + } + private func addCarouselSlots() { getSlotWidth() if containerView.frame.size.width > 0 { containerViewHeightConstraint?.isActive = false containerStackHeightConstraint?.isActive = false - // perform a loop to iterate each subView + // Perform a loop to iterate each subView scrollView.subviews.forEach { subView in - // removing subView from its parent view + // Removing subView from its parent view subView.removeFromSuperview() } - // add carousel items + // Add carousel items if data.count > 0 { var xPos = 0.0 for x in 0...data.count - 1 { @@ -433,86 +487,7 @@ open class Carousel: View { } } - private func ratioSize(for width: CGFloat) -> CGSize { - var height: CGFloat = width - - switch aspectRatio { - case .ratio1x1: - break; - case .ratio3x4: - height = (4 / 3) * width - case .ratio4x3: - height = (3 / 4) * width - case .ratio2x3: - height = (3 / 2) * width - case .ratio3x2: - height = (2 / 3) * width - case .ratio9x16: - height = (16 / 9) * width - case .ratio16x9: - height = (9 / 16) * width - case .ratio1x2: - height = (2 / 1) * width - case .ratio2x1: - height = (1 / 2) * width - - default: - break - } - - return CGSize(width: width, height: height) - } - - func addlisteners() { - nextButton.onClick = { _ in self.nextButtonClick() } - previousButton.onClick = { _ in self.previousButtonClick() } - - /// will be called when the thumb move forward. - carouselScrollBar.onMoveForward = { [weak self] scrubberId in - guard let self else { return } - updateScrollPosition(position: scrubberId, callbackText:"onMoveForward") - } - - /// will be called when the thumb move backward. - carouselScrollBar.onMoveBackward = { [weak self] scrubberId in - guard let self else { return } - updateScrollPosition(position: scrubberId, callbackText:"onMoveBackward") - } - - /// will be called when the thumb touch start. - carouselScrollBar.onThumbTouchStart = { [weak self] scrubberId in - guard let self else { return } - updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchStart") - } - - /// will be called when the thumb touch end. - carouselScrollBar.onThumbTouchEnd = { [weak self] scrubberId in - guard let self else { return } - updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchEnd") - } - } - - func updatePaginationControls() { - containerView.surface = surface - showPaginationControls() - previousButton.kind = pagination.kind - previousButton.floating = pagination.floating - nextButton.kind = pagination.kind - nextButton.floating = pagination.floating - previousButton.surface = surface - nextButton.surface = surface - } - - func updatePaginationInset() { - prevButtonLeadingConstraint?.isActive = false - nextButtonTrailingConstraint?.isActive = false - prevButtonLeadingConstraint = previousButton.leadingAnchor.constraint(equalTo: scrollContainerView.leadingAnchor, constant: paginationInset) - nextButtonTrailingConstraint = nextButton.trailingAnchor.constraint(equalTo: scrollContainerView.trailingAnchor, constant: -paginationInset) - prevButtonLeadingConstraint?.isActive = true - nextButtonTrailingConstraint?.isActive = true - } - - func getSlotWidth() { + private func getSlotWidth() { let actualWidth = containerView.frame.size.width let isScrollbarSuppressed = data.count > 0 && layout.value == data.count let isPeekMinimumOnTablet = UIDevice.isIPad && peek == .minimum @@ -538,19 +513,69 @@ open class Carousel: View { minimumSlotWidth = minimumSlotWidth / CGFloat(layout.value) } - func nextButtonClick() { + private func nextButtonClick() { carouselScrollBar.position = carouselScrollBar.position+1 showPaginationControls() updateScrollPosition(position: carouselScrollBar.position, callbackText:"pageControlClicks") } - func previousButtonClick() { + private func previousButtonClick() { carouselScrollBar.position = carouselScrollBar.position-1 showPaginationControls() updateScrollPosition(position: carouselScrollBar.position, callbackText:"pageControlClicks") } - func updateScrollPosition(position: Int, callbackText: String) { + private func ratioSize(for width: CGFloat) -> CGSize { + var height: CGFloat = width + + switch aspectRatio { + case .ratio1x1: + break; + case .ratio3x4: + height = (4 / 3) * width + case .ratio4x3: + height = (3 / 4) * width + case .ratio2x3: + height = (3 / 2) * width + case .ratio3x2: + height = (2 / 3) * width + case .ratio9x16: + height = (16 / 9) * width + case .ratio16x9: + height = (9 / 16) * width + case .ratio1x2: + height = (2 / 1) * width + case .ratio2x1: + height = (1 / 2) * width + + default: + break + } + + return CGSize(width: width, height: height) + } + + private func updatePaginationInset() { + prevButtonLeadingConstraint?.isActive = false + nextButtonTrailingConstraint?.isActive = false + prevButtonLeadingConstraint = previousButton.leadingAnchor.constraint(equalTo: scrollContainerView.leadingAnchor, constant: paginationInset) + nextButtonTrailingConstraint = nextButton.trailingAnchor.constraint(equalTo: scrollContainerView.trailingAnchor, constant: -paginationInset) + prevButtonLeadingConstraint?.isActive = true + nextButtonTrailingConstraint?.isActive = true + } + + private func updateScrollbarPosition(targetContentOffsetXPos:CGFloat) { + let scrollContentSizeWidth = scrollView.contentSize.width + let totalPositions = totalPositions() + let layoutSpace = Int (floor( Double(scrollContentSizeWidth / Double(totalPositions)))) + let remindSpace = Int(targetContentOffsetXPos) % layoutSpace + var contentPos = (Int(targetContentOffsetXPos) / layoutSpace) + 1 + contentPos = remindSpace > layoutSpace/2 ? contentPos+1 : contentPos + carouselScrollBar.position = contentPos + updateScrollPosition(position: contentPos, callbackText: "ScrollViewMoved") + } + + private func updateScrollPosition(position: Int, callbackText: String) { if carouselScrollBar.numberOfSlides > 0 { let scrollContentSizeWidth = scrollView.contentSize.width let totalPositions = totalPositions() @@ -586,30 +611,9 @@ open class Carousel: View { } } - func updateScrollbarPosition(targetContentOffsetXPos:CGFloat) { - let scrollContentSizeWidth = scrollView.contentSize.width - let totalPositions = totalPositions() - let layoutSpace = Int (floor( Double(scrollContentSizeWidth / Double(totalPositions)))) - let remindSpace = Int(targetContentOffsetXPos) % layoutSpace - var contentPos = (Int(targetContentOffsetXPos) / layoutSpace) + 1 - contentPos = remindSpace > layoutSpace/2 ? contentPos+1 : contentPos - carouselScrollBar.position = contentPos - updateScrollPosition(position: contentPos, callbackText: "ScrollViewMoved") - } - private func totalPositions() -> Int { return Int (ceil (Double(carouselScrollBar.numberOfSlides) / Double(_layout.value))) } - - func showPaginationControls() { - if carouselScrollBar.numberOfSlides == _layout.value { - previousButton.isHidden = true - nextButton.isHidden = true - } else { - previousButton.isHidden = (carouselScrollBar.position == 1) || (paginationDisplay == .none) - nextButton.isHidden = (carouselScrollBar.position == totalPositions()) || (paginationDisplay == .none) - } - } } extension Carousel: UIScrollViewDelegate { @@ -619,6 +623,7 @@ extension Carousel: UIScrollViewDelegate { public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { updateScrollbarPosition(targetContentOffsetXPos: targetContentOffset.pointee.x) } + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { var visibleRect = CGRect() visibleRect.origin = scrollView.contentOffset From 1e0b0279b7d9cd6b81fe5aa917486ef761bd63a4 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 2 Jul 2024 09:33:54 -0500 Subject: [PATCH 070/117] CXTDT-577463 - InputField - Accessibility - Issue #5 Signed-off-by: Matt Bruce --- .../TextFields/InputField/FieldTypes/Password.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Password.swift b/VDS/Components/TextFields/InputField/FieldTypes/Password.swift index 00c00a0d..c08af9c8 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Password.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Password.swift @@ -42,6 +42,13 @@ extension InputField { self.passwordActionType = nextPasswordActionType inputField.setNeedsUpdate() }) + // set the accessibilityLabel + if let labelText = inputField.labelText { + inputField.actionTextLink.bridge_accessibilityLabelBlock = { + return "\(buttonText) \(labelText)" + } + } + } else { passwordActionType = .show } From 0ee459946f04e7c8a6d17f6ca5317841d4312136 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 2 Jul 2024 09:37:36 -0500 Subject: [PATCH 071/117] CXTDT-577463 - InputField - Accessibility - #5 Password / Inline Action Signed-off-by: Matt Bruce --- .../TextFields/InputField/FieldTypes/FieldType.swift | 6 ++++++ .../TextFields/InputField/FieldTypes/Password.swift | 9 +-------- VDS/SupportingFiles/ReleaseNotes.txt | 1 + 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift b/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift index b3c2a92b..180478af 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/FieldType.swift @@ -68,6 +68,12 @@ extension InputField { actionModel.onClick(inputField) } inputField.actionTextLink.isHidden = false + // set the accessibilityLabel + if let labelText = inputField.labelText { + inputField.actionTextLink.bridge_accessibilityLabelBlock = { + return "\(actionModel.text) \(labelText)" + } + } inputField.fieldStackView.setCustomSpacing(VDSLayout.space2X, after: inputField.statusIcon) } else { inputField.actionTextLink.isHidden = true diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Password.swift b/VDS/Components/TextFields/InputField/FieldTypes/Password.swift index c08af9c8..7d45d091 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Password.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Password.swift @@ -41,14 +41,7 @@ extension InputField { guard let self else { return } self.passwordActionType = nextPasswordActionType inputField.setNeedsUpdate() - }) - // set the accessibilityLabel - if let labelText = inputField.labelText { - inputField.actionTextLink.bridge_accessibilityLabelBlock = { - return "\(buttonText) \(labelText)" - } - } - + }) } else { passwordActionType = .show } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 0be4ba37..f831af12 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,6 +1,7 @@ 1.0.70 ---------------- - CXTDT-577463 - InputField - Accessibility - #1 Typing Feedback +- CXTDT-577463 - InputField - Accessibility - #5 Password / Inline Action - CXTDT-560485 - Tilelet - Accessibility Icons - DatePicker - Final logic for how the calendar shows. From 88fdcabe67b2af2847633a940aaf13ab049f3d52 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 2 Jul 2024 14:52:58 -0500 Subject: [PATCH 072/117] updated to use generic Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 26 ++++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 17ededf4..22ab9d58 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -223,7 +223,7 @@ extension DatePicker { //find scrollView if scrollView == nil { - scrollView = findScrollView(from: containerView) + scrollView = containerView.findSuperview(ofType: UIScrollView.self) scrollViewContentSize = scrollView?.contentSize } @@ -348,17 +348,6 @@ extension DatePicker { } } - private func findScrollView(from view: UIView) -> UIScrollView? { - var currentView = view - while let superview = currentView.superview { - if let scrollView = superview as? UIScrollView { - return scrollView - } - currentView = superview - } - return nil - } - private func calculatePopoverPosition(relativeTo sourceView: UIView, in parentView: UIView, size: CGSize, with spacing: CGFloat) -> CGPoint? { let sourceFrameInParent = sourceView.convert(sourceView.bounds, to: parentView) let parentBounds = parentView.bounds @@ -442,3 +431,16 @@ extension DatePicker { } } } + +extension UIView { + public func findSuperview(ofType type: T.Type) -> T? { + var currentView: UIView? = self + while let view = currentView { + if let superview = view.superview as? T { + return superview + } + currentView = view.superview + } + return nil + } +} From 2b60e8cff33af8266d8755c3cde9f48176e8fb9a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 3 Jul 2024 12:19:30 -0500 Subject: [PATCH 073/117] CXTDT-581803 - Date Picker - Calendar does not switch to Dark Mode Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 2 +- VDS/Components/DatePicker/DatePickerCalendarModel.swift | 6 +----- VDS/SupportingFiles/ReleaseNotes.txt | 4 ++++ 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 22ab9d58..83450403 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -214,7 +214,7 @@ extension DatePicker { calendar.indicators = calendarModel.indicators calendar.maxDate = calendarModel.maxDate calendar.minDate = calendarModel.minDate - calendar.surface = calendarModel.surface + calendar.surface = surface calendar.setNeedsLayout() calendar.layoutIfNeeded() diff --git a/VDS/Components/DatePicker/DatePickerCalendarModel.swift b/VDS/Components/DatePicker/DatePickerCalendarModel.swift index ccceb5c9..16a46e61 100644 --- a/VDS/Components/DatePicker/DatePickerCalendarModel.swift +++ b/VDS/Components/DatePicker/DatePickerCalendarModel.swift @@ -10,8 +10,6 @@ import UIKit extension DatePicker { public struct CalendarModel { - public let surface: Surface - /// If set to true, the calendar will not have a border. public let hideContainerBorder: Bool @@ -35,15 +33,13 @@ extension DatePicker { /// Array of ``CalendarIndicatorModel`` you are wanting to show on legend. public let indicators: [CalendarBase.CalendarIndicatorModel] - public init(surface: Surface = .light, - hideContainerBorder: Bool = false, + public init(hideContainerBorder: Bool = false, hideCurrentDateIndicator: Bool = false, activeDates: [Date] = [], inactiveDates: [Date] = [], minDate: Date = Date().startOfMonth, maxDate: Date = Date().endOfMonth, indicators: [CalendarBase.CalendarIndicatorModel] = []) { - self.surface = surface self.hideContainerBorder = hideContainerBorder self.hideCurrentDateIndicator = hideCurrentDateIndicator self.activeDates = activeDates diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index f831af12..6ea73a9b 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,3 +1,7 @@ +1.0.71 +---------------- +- CXTDT-581803 - DatePicker - Calendar does not switch to Dark Mode + 1.0.70 ---------------- - CXTDT-577463 - InputField - Accessibility - #1 Typing Feedback From 77288e4c547cf8a2248912ec5bce506c62fbc604 Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Thu, 4 Jul 2024 20:24:33 +0530 Subject: [PATCH 074/117] Digital ACT-191 ONEAPP-7013 story: slot alignment and rendering data --- VDS.xcodeproj/project.pbxproj | 10 +- VDS/Components/Carousel/Carousel.swift | 157 +++++++++++++++--- .../Carousel/CarouselRenderItemStyle.swift | 33 ++++ .../Carousel/CarouselSlotAlignmentModel.swift | 14 +- .../Carousel/CarouselSlotItemModel.swift | 31 ++++ 5 files changed, 212 insertions(+), 33 deletions(-) create mode 100644 VDS/Components/Carousel/CarouselRenderItemStyle.swift create mode 100644 VDS/Components/Carousel/CarouselSlotItemModel.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 4c72a920..64ec60bb 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 18013CED2C355BF900907F18 /* CarouselSlotItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18013CEC2C355BF900907F18 /* CarouselSlotItemModel.swift */; }; + 18013CEF2C355C5200907F18 /* CarouselRenderItemStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18013CEE2C355C5200907F18 /* CarouselRenderItemStyle.swift */; }; 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; }; 1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; }; 1842B1DF2BECE28B0021AFCA /* CalendarDateViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */; }; @@ -19,11 +21,9 @@ 18A65A022B96E848006602CC /* Breadcrumbs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A012B96E848006602CC /* Breadcrumbs.swift */; }; 18A65A042B96F050006602CC /* BreadcrumbItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18A65A032B96F050006602CC /* BreadcrumbItem.swift */; }; 18AE87502C06FDA60075F181 /* Carousel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18AE874F2C06FDA60075F181 /* Carousel.swift */; }; - 18AE87542C06FE610075F181 /* CarouselChangeLog.txt in Resources */ = {isa = PBXBuildFile; fileRef = 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */; }; 18B42AC62C09D197008D6262 /* CarouselSlotAlignmentModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */; }; 18B463A42BBD3C46005C4528 /* DropdownOptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B463A32BBD3C46005C4528 /* DropdownOptionModel.swift */; }; 18B9763F2C11BA4A009271DF /* CarouselPaginationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18B9763E2C11BA4A009271DF /* CarouselPaginationModel.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 */; }; 445BA07829C07B3D0036A7C5 /* Notification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 445BA07729C07B3D0036A7C5 /* Notification.swift */; }; @@ -209,6 +209,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 18013CEC2C355BF900907F18 /* CarouselSlotItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselSlotItemModel.swift; sourceTree = ""; }; + 18013CEE2C355C5200907F18 /* CarouselRenderItemStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselRenderItemStyle.swift; sourceTree = ""; }; 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = ""; }; 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = ""; }; 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = ""; }; @@ -502,6 +504,8 @@ 18AE874F2C06FDA60075F181 /* Carousel.swift */, 18B9763E2C11BA4A009271DF /* CarouselPaginationModel.swift */, 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */, + 18013CEC2C355BF900907F18 /* CarouselSlotItemModel.swift */, + 18013CEE2C355C5200907F18 /* CarouselRenderItemStyle.swift */, 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */, ); path = Carousel; @@ -1363,6 +1367,7 @@ EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */, EAF7F0AB289B13FD00B287F5 /* TextStyleLabelAttribute.swift in Sources */, 18AE87502C06FDA60075F181 /* Carousel.swift in Sources */, + 18013CEF2C355C5200907F18 /* CarouselRenderItemStyle.swift in Sources */, EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */, EA81410B2A0E8E3C004F60D2 /* ButtonIcon.swift in Sources */, EA985BE629688F6A00F2FF2E /* TileletBadgeModel.swift in Sources */, @@ -1405,6 +1410,7 @@ EA596ABF2A16B4F500300C4B /* Tabs.swift in Sources */, EAD062A72A3B67770015965D /* UIView+CALayer.swift in Sources */, EAD068942A560C13002E3A2D /* LoaderLaunchable.swift in Sources */, + 18013CED2C355BF900907F18 /* CarouselSlotItemModel.swift in Sources */, 18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */, EA985BEC2968A91200F2FF2E /* TitleLockupTitleModel.swift in Sources */, 5FC35BE328D51405004EBEAC /* Button.swift in Sources */, diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index dbc10d98..a3cb1f1c 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -33,21 +33,6 @@ open class Carousel: View { //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- - /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. - public enum Gutter: String, CaseIterable { - case twelvePX = "12px" - case twentyFourPX = "24px" - - var value: CGFloat { - switch self { - case .twelvePX: - VDSLayout.space3X - case .twentyFourPX: - VDSLayout.space6X - } - } - } - /// Enum used to describe the pagination display for this component. public enum PaginationDisplay: String, CaseIterable { case persistent, none @@ -75,6 +60,21 @@ open class Carousel: View { case value(CGFloat) } + /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. + public enum Gutter: String, CaseIterable { + case twelvePX = "12px" + case twentyFourPX = "24px" + + var value: CGFloat { + switch self { + case .twelvePX: + VDSLayout.space3X + case .twentyFourPX: + VDSLayout.space6X + } + } + } + //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- @@ -123,11 +123,12 @@ open class Carousel: View { get { return _layout } set { _layout = newValue + carouselScrollBar.position = 0 setNeedsUpdate() } } - /// A callback when moving the carousel. Returns event object and selectedGroupIndex. + /// A callback when moving the carousel. Returns initial visible slide's index in the carousel. open var onChange: ((Int) -> Void)? { get { nil } set { @@ -182,7 +183,42 @@ open class Carousel: View { open var selectedIndex: Int? { didSet { setNeedsUpdate() } } /// If provided, will set the alignment for slot content when the slots has different heights. - open var slotAlignment: [CarouselSlotAlignmentModel] = [] { didSet { setNeedsUpdate() } } + open var slotAlignment: CarouselSlotAlignmentModel? { + get { return _slotAlignment } + set { + if let newValue { + _slotAlignment = newValue + } else { + _slotAlignment = nil + } + setNeedsUpdate() + } + } + + /// Render item style. If provided, the slot gets the background, width, height, border-radius. + open var renderItemStyle: CarouselRenderItemStyle? { + get { return _renderItemStyle } + set { + if let newValue { + _renderItemStyle = newValue + } else { + _renderItemStyle = nil + } + setNeedsUpdate() + } + } + + /// Render item. It passes a data array object and expects the styled component to apply in return. + open var renderItem: CarouselSlotItemModel? { + get { _renderItem } + set { + if let newValue { + _renderItem = newValue + } else { + _renderItem = nil + } + } + } //-------------------------------------------------- // MARK: - Private Properties @@ -250,9 +286,12 @@ open class Carousel: View { internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX internal var _peek: Peek = .standard internal var _numberOfSlides: Int = 1 + internal var _slotAlignment: CarouselSlotAlignmentModel? = nil + internal var _renderItemStyle: CarouselRenderItemStyle? = nil + internal var _renderItem: CarouselSlotItemModel? = nil private var _width: Width? = nil - private var selectedGroupIndex: Int? { didSet { setNeedsUpdate() } } + private var selectedGroupIndex: Int? = nil private var containerStackHeightConstraint: NSLayoutConstraint? private var containerViewHeightConstraint: NSLayoutConstraint? private var prevButtonLeadingConstraint: NSLayoutConstraint? @@ -323,6 +362,7 @@ open class Carousel: View { .heightGreaterThanEqualTo(containerSize.height) addlisteners() + updatePaginationInset() } /// Used to make changes to the View based off a change events or from local properties. @@ -348,7 +388,9 @@ open class Carousel: View { carouselScrollBar.numberOfSlides = data.count carouselScrollBar.layout = _layout - carouselScrollBar.position = (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) ? 1 : carouselScrollBar.position + if (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) { + carouselScrollBar.position = 1 + } carouselScrollBar.isHidden = (totalPositions() <= 1) ? true : false // Mobile/Tablet layouts without peek - must show pagination controls. @@ -392,31 +434,32 @@ open class Carousel: View { nextButton.onClick = { _ in self.nextButtonClick() } previousButton.onClick = { _ in self.previousButtonClick() } - /// Will be called when the thumb move forward. + /// Will be called when the scrollbar thumb move forward. carouselScrollBar.onMoveForward = { [weak self] scrubberId in guard let self else { return } updateScrollPosition(position: scrubberId, callbackText:"onMoveForward") } - /// Will be called when the thumb move backward. + /// Will be called when the scrollbar thumb move backward. carouselScrollBar.onMoveBackward = { [weak self] scrubberId in guard let self else { return } updateScrollPosition(position: scrubberId, callbackText:"onMoveBackward") } - /// Will be called when the thumb touch start. + /// Will be called when the scrollbar thumb touch start. carouselScrollBar.onThumbTouchStart = { [weak self] scrubberId in guard let self else { return } updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchStart") } - /// Will be called when the thumb touch end. + /// Will be called when the scrollbar thumb touch end. carouselScrollBar.onThumbTouchEnd = { [weak self] scrubberId in guard let self else { return } updateScrollPosition(position: scrubberId, callbackText:"onThumbTouchEnd") } } + // Update pagination buttons with selected surface, kind, floating values private func updatePaginationControls() { containerView.surface = surface showPaginationControls() @@ -428,6 +471,7 @@ open class Carousel: View { nextButton.surface = surface } + // Show/Hide pagination buttons of Carousel based on First or Middle or Last private func showPaginationControls() { if carouselScrollBar.numberOfSlides == _layout.value { previousButton.isHidden = true @@ -438,6 +482,7 @@ open class Carousel: View { } } + // Add carousel slots and load data if any private func addCarouselSlots() { getSlotWidth() if containerView.frame.size.width > 0 { @@ -454,6 +499,8 @@ open class Carousel: View { if data.count > 0 { var xPos = 0.0 for x in 0...data.count - 1 { + + // Add Carousel Slot let carouselSlot = View().with { $0.clipsToBounds = true $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) @@ -470,6 +517,29 @@ open class Carousel: View { .height(slotHeight) carouselSlot.layer.cornerRadius = 12.0 xPos = xPos + minimumSlotWidth + gutter.value + + // Add subview for content to Carousel Slot + let contentView = View().with { + $0.clipsToBounds = true + $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) + } + carouselSlot.addSubview(contentView) + + // Add received component + let item : CarouselSlotItemModel = .init(style: renderItemStyle, component: data[x] as? UIView) + if let component = item.component { + if slotAlignment != nil { + setSlotAlignment(contentView: contentView, parentView: carouselSlot) + } else { + contentView.pinToSuperView() + } + contentView.addSubview(component) + component.pinToSuperView() + contentView.layer.cornerRadius = component.layer.cornerRadius + if var surfacedView = component as? Surfaceable { + surfacedView.surface = surface + } + } } scrollView.contentSize = CGSize(width: xPos - gutter.value, height: slotHeight) } @@ -487,6 +557,37 @@ open class Carousel: View { } } + // Set slot alignment if provided. Used only when slot content have different heights or widths. + private func setSlotAlignment(contentView: View, parentView: View) { + parentView.backgroundColor = .clear + switch slotAlignment?.vertical { + case .top: + contentView.topAnchor.constraint(equalTo: parentView.topAnchor).activate() + break + case .middle: + contentView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor).activate() + break + case .bottom: + contentView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor).activate() + break + default: break + } + + switch slotAlignment?.horizontal { + case .left: + contentView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).activate() + break + case .center: + contentView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor).activate() + break + case .right: + parentView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).activate() + break + default: break + } + } + + // Get the slot width relative to the peak private func getSlotWidth() { let actualWidth = containerView.frame.size.width let isScrollbarSuppressed = data.count > 0 && layout.value == data.count @@ -505,7 +606,7 @@ open class Carousel: View { case .minimum: // Peek Mimumum Width: 24px from edge of container (at the default view of the carousel with one peek visible) // Minimum (Mobile only) Supported only on Mobile viewports. If a user passes Minimum for tablet carousel, the peek reverts to Standard. - minimumSlotWidth = isPeekMinimumOnTablet ? minimumSlotWidth : minimumSlotWidth - peekMinimum + minimumSlotWidth = isPeekMinimumOnTablet ? minimumSlotWidth : minimumSlotWidth - peekMinimum - gutter.value case .none: break } @@ -525,6 +626,7 @@ open class Carousel: View { updateScrollPosition(position: carouselScrollBar.position, callbackText:"pageControlClicks") } + // The size of slot depends on the selected aspect ratio private func ratioSize(for width: CGFloat) -> CGSize { var height: CGFloat = width @@ -575,6 +677,7 @@ open class Carousel: View { updateScrollPosition(position: contentPos, callbackText: "ScrollViewMoved") } + // Update scrollview offset relative to scrollbar thumb position private func updateScrollPosition(position: Int, callbackText: String) { if carouselScrollBar.numberOfSlides > 0 { let scrollContentSizeWidth = scrollView.contentSize.width @@ -598,7 +701,7 @@ open class Carousel: View { xPos = xPosition - gutter.value - (minimumSlotWidth/4)/2 } case .minimum: - xPos = isPeekMinimumOnTablet ? xPosition : xPosition - peekMinimum/2 + xPos = isPeekMinimumOnTablet ? xPosition : xPosition - peekMinimum case .none: xPos = xPosition } @@ -608,9 +711,13 @@ open class Carousel: View { let yPos = scrollView.contentOffset.y scrollView.setContentOffset(CGPoint(x: xPos, y: yPos), animated: true) showPaginationControls() + selectedIndex = ((position-1) * layout.value) + 1 + onChangePublisher.send(selectedIndex ?? 1) + selectedGroupIndex = position } } + // Get the overall positions of the carousel scrollbar relative to the slides and selected layout private func totalPositions() -> Int { return Int (ceil (Double(carouselScrollBar.numberOfSlides) / Double(_layout.value))) } diff --git a/VDS/Components/Carousel/CarouselRenderItemStyle.swift b/VDS/Components/Carousel/CarouselRenderItemStyle.swift new file mode 100644 index 00000000..c447dfa1 --- /dev/null +++ b/VDS/Components/Carousel/CarouselRenderItemStyle.swift @@ -0,0 +1,33 @@ +// +// CarouselRenderItemStyle.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 30/06/24. +// + +import Foundation +import UIKit +import VDSCoreTokens + +/// A custom data type that holds the style props if provided any. +public struct CarouselRenderItemStyle { + + /// BackgroundColor for slot + public let backgroundColor: String? + + /// Height for slot + public var height: CGFloat? + + /// BorderRadius for slot + public var borderRadius: CGFloat? + + /// Width for slot + public var width: CGFloat? + + public init(backgroundColor: String?, height: CGFloat?, width: CGFloat?, borderRadius: CGFloat?) { + self.backgroundColor = backgroundColor + self.height = height + self.borderRadius = borderRadius ?? 12.0 + self.width = width + } +} diff --git a/VDS/Components/Carousel/CarouselSlotAlignmentModel.swift b/VDS/Components/Carousel/CarouselSlotAlignmentModel.swift index 5a8ec661..ee1d491b 100644 --- a/VDS/Components/Carousel/CarouselSlotAlignmentModel.swift +++ b/VDS/Components/Carousel/CarouselSlotAlignmentModel.swift @@ -7,17 +7,19 @@ import Foundation -/// Custom data type for slotAlignment prop for 'Carousel' component. +/// Custom data type for the SlotAlignment prop for the 'carousel' component. extension Carousel { + + /// Used only when slot content have different heights or widths. public struct CarouselSlotAlignmentModel { - /// Text that shown to an indicator for legend - public var vertical: String + /// Used for vertical alignment of slot alignment. + public var vertical: Carousel.Vertical - /// Date to an indicator - public var horizontal: String + /// Used for horizontal alignment of slot alignment. + public var horizontal: Carousel.Horizontal - public init(vertical: String, horizontal: String) { + public init(vertical: Carousel.Vertical, horizontal: Carousel.Horizontal) { self.vertical = vertical self.horizontal = horizontal } diff --git a/VDS/Components/Carousel/CarouselSlotItemModel.swift b/VDS/Components/Carousel/CarouselSlotItemModel.swift new file mode 100644 index 00000000..3830b29f --- /dev/null +++ b/VDS/Components/Carousel/CarouselSlotItemModel.swift @@ -0,0 +1,31 @@ +// +// CarouselSlotItemModel.swift +// VDS +// +// Created by Kanamarlapudi, Vasavi on 30/06/24. +// + +import Foundation +import UIKit +import VDSCoreTokens + +/// A custom data type that holds the style and component for a slot of the 'Carousel' component. +public struct CarouselSlotItemModel { + + /// Style props if provided any + public var style: CarouselRenderItemStyle? + + /// Component to be show on Carousel slot + public var component: UIView? + + public init(style: CarouselRenderItemStyle? = nil, component: UIView? = nil) { + self.style = style + self.component = component + if let color = style?.backgroundColor { + self.component?.backgroundColor = .init(hexString: color) + } + if let borderRadius = style?.borderRadius { + self.component?.layer.cornerRadius = borderRadius + } + } +} From d3a7d45ab9719b463666a6b559750b3d2ae8f7c9 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 9 Jul 2024 09:38:17 -0500 Subject: [PATCH 075/117] fixed bug in radiobox Signed-off-by: Matt Bruce --- VDS/Components/RadioBox/RadioBoxItem.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/RadioBox/RadioBoxItem.swift b/VDS/Components/RadioBox/RadioBoxItem.swift index 562e1e54..872a4d05 100644 --- a/VDS/Components/RadioBox/RadioBoxItem.swift +++ b/VDS/Components/RadioBox/RadioBoxItem.swift @@ -214,7 +214,7 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { selectorView.isAccessibilityElement = true selectorView.accessibilityTraits = .button addSubview(selectorView) - selectorView.isUserInteractionEnabled = true + selectorView.isUserInteractionEnabled = false selectorView.addSubview(selectorStackView) From 1c01fabef07b181a07880694512ef0f0cc9c8238 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 9 Jul 2024 10:50:36 -0500 Subject: [PATCH 076/117] =?UTF-8?q?CXTDT-584278=20=E2=80=93=20InputField?= =?UTF-8?q?=20-=20Accessibility?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 7 +++++++ VDS/SupportingFiles/ReleaseNotes.txt | 1 + 2 files changed, 8 insertions(+) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 11a8ae17..a7e0bbb7 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -184,6 +184,8 @@ open class InputField: EntryFieldBase { super.setup() accessibilityHintText = "Double tap to edit" + actionTextLink.accessibilityTraits = .button + textField.heightAnchor.constraint(equalToConstant: 20).isActive = true textField.delegate = self bottomContainerStackView.insertArrangedSubview(successLabel, at: 0) @@ -246,6 +248,11 @@ open class InputField: EntryFieldBase { return nil } } + + containerView.bridge_accessibilityValueBlock = { [weak self] in + guard let self else { return "" } + return textField.isSecureTextEntry ? "\(textField.text.count) stars" : value + } } open override func getFieldContainer() -> UIView { diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 6ea73a9b..973e8ed8 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,6 +1,7 @@ 1.0.71 ---------------- - CXTDT-581803 - DatePicker - Calendar does not switch to Dark Mode +- CXTDT-584278 – InputField - Accessibility 1.0.70 ---------------- From d7086b070275eb446ef06cba74e254b56c598a06 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 10 Jul 2024 16:21:50 -0500 Subject: [PATCH 077/117] added a placeholder Signed-off-by: Matt Bruce --- .../TextFields/TextArea/TextView.swift | 28 +++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TextFields/TextArea/TextView.swift b/VDS/Components/TextFields/TextArea/TextView.swift index 54fa9452..3f40ae38 100644 --- a/VDS/Components/TextFields/TextArea/TextView.swift +++ b/VDS/Components/TextFields/TextArea/TextView.swift @@ -41,10 +41,23 @@ open class TextView: UITextView, ViewProtocol, Errorable { // MARK: - Private Properties //-------------------------------------------------- private var initialSetupPerformed = false - + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + open var placeholder: String? { + didSet { + placeholderLabel.text = placeholder + } + } + + open var placeholderLabel = Label().with { + $0.textColorConfiguration = ViewColorConfiguration().with { + $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) + $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: false) + }.eraseToAnyColorable() + } + /// Key of whether or not updateView() is called in setNeedsUpdate() open var shouldUpdateView: Bool = true @@ -88,6 +101,7 @@ open class TextView: UITextView, ViewProtocol, Errorable { if textAlignment != oldValue { // Text alignment can be part of our paragraph style, so we may need to // re-style when changed + placeholderLabel.textAlignment = textAlignment updateLabel() } } @@ -118,6 +132,9 @@ open class TextView: UITextView, ViewProtocol, Errorable { done.pinCenterY() .pinTrailing(16) inputAccessoryView = accessView + + addSubview(placeholderLabel) + placeholderLabel.pinToSuperView() } @objc func doneButtonAction() { @@ -145,7 +162,11 @@ open class TextView: UITextView, ViewProtocol, Errorable { setNeedsUpdate() } - + open override func layoutSubviews() { + super.layoutSubviews() + placeholderLabel.preferredMaxLayoutWidth = textContainer.size.width - textContainer.lineFragmentPadding * 2 + } + //-------------------------------------------------- // MARK: - Accessibility //-------------------------------------------------- @@ -297,6 +318,9 @@ open class TextView: UITextView, ViewProtocol, Errorable { } else { attributedText = nil } + placeholderLabel.textStyle = textStyle + placeholderLabel.isHidden = !text.isEmpty + placeholderLabel.surface = surface } } From d943202e8389b2b666499e753694fa515b46138f Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 10 Jul 2024 16:33:54 -0500 Subject: [PATCH 078/117] updated placeholder logic Signed-off-by: Matt Bruce --- VDS/Components/TextFields/TextArea/TextView.swift | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/VDS/Components/TextFields/TextArea/TextView.swift b/VDS/Components/TextFields/TextArea/TextView.swift index 3f40ae38..3c5dc5ee 100644 --- a/VDS/Components/TextFields/TextArea/TextView.swift +++ b/VDS/Components/TextFields/TextArea/TextView.swift @@ -45,11 +45,7 @@ open class TextView: UITextView, ViewProtocol, Errorable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - open var placeholder: String? { - didSet { - placeholderLabel.text = placeholder - } - } + open var placeholder: String? { didSet { setNeedsUpdate() } } open var placeholderLabel = Label().with { $0.textColorConfiguration = ViewColorConfiguration().with { @@ -319,8 +315,9 @@ open class TextView: UITextView, ViewProtocol, Errorable { attributedText = nil } placeholderLabel.textStyle = textStyle - placeholderLabel.isHidden = !text.isEmpty placeholderLabel.surface = surface + placeholderLabel.text = placeholder + placeholderLabel.isHidden = !text.isEmpty } } From 46d4098cf2a1c762e81c686670f543260a98aabb Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 10:53:41 -0500 Subject: [PATCH 079/117] fixed issue with showing errorIcon without error message Signed-off-by: Matt Bruce --- .../TextFields/EntryFieldBase.swift | 93 ++++++++++--------- 1 file changed, 50 insertions(+), 43 deletions(-) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 5bd50351..89186a3b 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -28,7 +28,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { public required init?(coder: NSCoder) { super.init(coder: coder) } - + //-------------------------------------------------- // MARK: - Enums //-------------------------------------------------- @@ -91,7 +91,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.spacing = VDSLayout.space2X } }() - + /// This is the view that will be wrapped with the border for userInteraction. /// The only subview of this view is the fieldStackView internal var containerView = View().with { @@ -115,7 +115,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { internal var widthConstraint: NSLayoutConstraint? internal var trailingEqualsConstraint: NSLayoutConstraint? internal var trailingLessThanEqualsConstraint: NSLayoutConstraint? - + //-------------------------------------------------- // MARK: - Configuration Properties //-------------------------------------------------- @@ -133,14 +133,14 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forDisabled: true) $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: false) } - + internal var backgroundColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .normal) $0.setSurfaceColors(VDSFormControlsColor.backgroundOnlight, VDSFormControlsColor.backgroundOndark, forState: .disabled) $0.setSurfaceColors(VDSColor.feedbackErrorBackgroundOnlight, VDSColor.feedbackErrorBackgroundOndark, forState: .error) $0.setSurfaceColors(VDSColor.feedbackErrorBackgroundOnlight, VDSColor.feedbackErrorBackgroundOndark, forState: [.error, .focused]) } - + internal var borderColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSFormControlsColor.borderOnlight, VDSFormControlsColor.borderOndark, forState: .normal) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forState: .focused) @@ -155,7 +155,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .error) } - + internal var readOnlyBorderColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSFormControlsColor.borderReadonlyOnlight, VDSFormControlsColor.borderReadonlyOndark, forState: .normal) } @@ -164,7 +164,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { // MARK: - Public Properties //-------------------------------------------------- open var onChangeSubscriber: AnyCancellable? - + open var titleLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) $0.textStyle = .bodySmall @@ -185,7 +185,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.size = .medium $0.isAccessibilityElement = true } - + open var labelText: String? { didSet { setNeedsUpdate() } } open var helperText: String? { didSet { setNeedsUpdate() } } @@ -195,7 +195,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { /// FormFieldValidator open var validator: (any FormFieldValidatorable)? - + /// Override UIControl state to add the .error state if showError is true. open override var state: UIControl.State { get { @@ -214,17 +214,17 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { return state } } - + open var errorText: String? { didSet { setNeedsUpdate() } } - + open var tooltipModel: Tooltip.TooltipModel? { didSet { setNeedsUpdate() } } - + open var transparentBackground: Bool = false { didSet { setNeedsUpdate() } } open var width: CGFloat? { didSet { setNeedsUpdate() } } - + open var inputId: String? { didSet { setNeedsUpdate() } } - + /// The text of this textField. open var value: String? { get { fatalError("must be read from subclass")} @@ -235,21 +235,21 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open var isRequired: Bool = false { didSet { setNeedsUpdate() } } open var isReadOnly: Bool = false { didSet { setNeedsUpdate() } } - + open var helperTextPlacement: HelperTextPlacement = .bottom { didSet { updateHelperTextPosition() } } - + open var rules = [AnyRule]() - + open var accessibilityHintText: String = "Double tap to open" //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- - + /// 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() @@ -371,7 +371,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { titleLabel.textStyle = .bodySmall errorLabel.textStyle = .bodySmall helperLabel.textStyle = .bodySmall - + labelText = nil helperText = nil showError = false @@ -389,19 +389,19 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open override var canBecomeFirstResponder: Bool { responder?.canBecomeFirstResponder ?? super.canBecomeFirstResponder } - + open override func becomeFirstResponder() -> Bool { responder?.becomeFirstResponder() ?? super.becomeFirstResponder() } - + open override var canResignFirstResponder: Bool { responder?.canResignFirstResponder ?? super.canResignFirstResponder } - + open override func resignFirstResponder() -> Bool { responder?.resignFirstResponder() ?? super.resignFirstResponder() } - + //-------------------------------------------------- // MARK: - Public Methods //-------------------------------------------------- @@ -409,21 +409,21 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { open func getFieldContainer() -> UIView { fatalError("Subclass must return the view that contains the field/view the user will interact with.") } - + /// Container for the area in which helper or error text presents. open func getBottomContainer() -> UIView { return bottomContainerStackView } - + open func validate(){ updateRules() validator = FormFieldValidator(field: self, rules: rules) validator?.validate() setNeedsUpdate() } - + open func updateTitleLabel() { - + //update the local vars for the label since we no //long have a model var attributes: [any LabelAttributeModel] = [] @@ -444,36 +444,43 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { if let tooltipModel { attributes.append(TooltipLabelAttribute(surface: surface, model: tooltipModel, presenter: self)) } - + //set the titleLabel titleLabel.text = updatedLabelText titleLabel.attributes = attributes titleLabel.surface = surface titleLabel.isEnabled = isEnabled } - + open func updateErrorLabel(){ - if showError, let errorText { - errorLabel.text = errorText - errorLabel.surface = surface - errorLabel.isEnabled = isEnabled - errorLabel.isHidden = false - statusIcon.name = .error - statusIcon.surface = surface - statusIcon.isHidden = !isEnabled || state.contains(.focused) - } else if hasInternalError, let internalErrorText { - errorLabel.text = internalErrorText - errorLabel.surface = surface - errorLabel.isEnabled = isEnabled - errorLabel.isHidden = false + + /// always show the errorIcon if there is an error + if showError || hasInternalError { statusIcon.name = .error statusIcon.surface = surface statusIcon.isHidden = !isEnabled || state.contains(.focused) } else { statusIcon.isHidden = true - errorLabel.isHidden = true } statusIcon.color = iconColorConfiguration.getColor(self) + + // only show errorLabel if there is a message + var message: String? + if showError, let errorText { + message = errorText + } else if hasInternalError, let internalErrorText { + message = internalErrorText + } + + if let message { + errorLabel.text = message + errorLabel.surface = surface + errorLabel.isEnabled = isEnabled + errorLabel.isHidden = false + } else { + errorLabel.isHidden = true + } + } open func updateHelperLabel(){ From c2e7465c4e6d4089e2ddcb23e8c74df330f20ce0 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 12:39:34 -0500 Subject: [PATCH 080/117] fixed bug in textarea maxLength Signed-off-by: Matt Bruce --- VDS/Components/TextFields/TextArea/TextArea.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index c668a851..f8a9a6b3 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -111,7 +111,9 @@ open class TextArea: EntryFieldBase { } didSet { - validate() + if textView.isFirstResponder { + validate() + } } } From fae53c4f5be00bb71430aaf6eb7945c768840f14 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 12:39:46 -0500 Subject: [PATCH 081/117] added variable to shut off internal required Signed-off-by: Matt Bruce --- VDS/Components/TextFields/EntryFieldBase.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 89186a3b..bc2c31ba 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -186,6 +186,8 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.isAccessibilityElement = true } + open var useRequiredRule: Bool = true { didSet { setNeedsUpdate() } } + open var labelText: String? { didSet { setNeedsUpdate() } } open var helperText: String? { didSet { setNeedsUpdate() } } @@ -522,7 +524,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //-------------------------------------------------- internal func updateRules() { rules.removeAll() - if self.isRequired { + if isRequired && useRequiredRule { let rule = RequiredRule() if let errorText, !errorText.isEmpty { rule.errorMessage = errorText From 11622b84d3495b5e161ae293966cc79901b834c4 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 14:03:07 -0500 Subject: [PATCH 082/117] updated icon accessibility issue Signed-off-by: Matt Bruce --- VDS/Components/Icon/Icon.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/VDS/Components/Icon/Icon.swift b/VDS/Components/Icon/Icon.swift index 2ac22ac8..140f7b87 100644 --- a/VDS/Components/Icon/Icon.swift +++ b/VDS/Components/Icon/Icon.swift @@ -93,12 +93,15 @@ open class Icon: View { backgroundColor = .clear isAccessibilityElement = true - accessibilityTraits = .image + accessibilityTraits = .none + accessibilityHint = "image" bridge_accessibilityLabelBlock = { [weak self] in guard let self else { return "" } return name?.rawValue ?? "icon" } + + } From 0fdc9c9109e1af9afdb74c54acae94f294a289f9 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 14:03:18 -0500 Subject: [PATCH 083/117] updated inputfield accessibility issue Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index a7e0bbb7..5a8604fc 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -209,11 +209,11 @@ open class InputField: EntryFieldBase { accessibilityLabels.append(text) } - if let formatText = textField.formatText, !formatText.isEmpty { + if let formatText = textField.formatText, !formatText.isEmpty, textField.text.isEmpty { accessibilityLabels.append("format, \(formatText)") } - if let placeholderText = textField.placeholder, !placeholderText.isEmpty { + if let placeholderText = textField.placeholder, !placeholderText.isEmpty, textField.text.isEmpty { accessibilityLabels.append("placeholder, \(placeholderText)") } From 405a84b3598d3e2d552f8b58d372602426f3a416 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 14:03:48 -0500 Subject: [PATCH 084/117] containerview public Signed-off-by: Matt Bruce --- VDS/Components/TextFields/EntryFieldBase.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index bc2c31ba..cf2dffa2 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -91,13 +91,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { $0.spacing = VDSLayout.space2X } }() - - /// This is the view that will be wrapped with the border for userInteraction. - /// The only subview of this view is the fieldStackView - internal var containerView = View().with { - $0.isAccessibilityElement = true - } - + /// This is set by a local method. internal var bottomContainerView: UIView! @@ -163,6 +157,12 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- + /// This is the view that will be wrapped with the border for userInteraction. + /// The only subview of this view is the fieldStackView + open var containerView = View().with { + $0.isAccessibilityElement = true + } + open var onChangeSubscriber: AnyCancellable? open var titleLabel = Label().with { From acd688405d011757dbce22031ad158c361156979 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 14:03:56 -0500 Subject: [PATCH 085/117] updates public Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index a7e0bbb7..71ad6b04 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -107,6 +107,9 @@ open class InputField: EntryFieldBase { $0.isAccessibilityElement = false $0.autocorrectionType = .no $0.spellCheckingType = .no + $0.smartQuotesType = .no + $0.smartDashesType = .no + $0.smartInsertDeleteType = .no } /// Color configuration for the textField. From d573002be536075c275d961247c30f3d3505b5e3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 11 Jul 2024 14:04:07 -0500 Subject: [PATCH 086/117] refactored textfield Signed-off-by: Matt Bruce --- .../TextFields/InputField/TextField.swift | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index 05dc30f2..b56f3423 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -47,6 +47,11 @@ open class TextField: UITextField, ViewProtocol, Errorable { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + /// Set to true to hide the blinking textField cursor. + open var hideBlinkingCaret = false + open var enableClipboardActions: Bool = true + open var onDidDeleteBackwards: (() -> Void)? + /// Key of whether or not updateView() is called in setNeedsUpdate() open var shouldUpdateView: Bool = true @@ -209,6 +214,23 @@ open class TextField: UITextField, ViewProtocol, Errorable { return success } + open override func caretRect(for position: UITextPosition) -> CGRect { + + if hideBlinkingCaret { + return .zero + } + + let caretRect = super.caretRect(for: position) + return CGRect(origin: caretRect.origin, size: CGSize(width: 1, height: caretRect.height)) + } + + open override func deleteBackward() { + super.deleteBackward() + onDidDeleteBackwards?() + } + + open override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { enableClipboardActions } + //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- From cdede872c0a50b73ad6aab6f1d7a2a0ba167ab1d Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 08:36:07 -0500 Subject: [PATCH 087/117] make open to override Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 7de89b97..e1609f96 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -350,19 +350,19 @@ open class InputField: EntryFieldBase { } extension InputField: UITextFieldDelegate { - public func textFieldDidBeginEditing(_ textField: UITextField) { + open func textFieldDidBeginEditing(_ textField: UITextField) { fieldType.handler().textFieldDidBeginEditing(self, textField: textField) updateContainerView() updateErrorLabel() } - public func textFieldDidEndEditing(_ textField: UITextField) { + open func textFieldDidEndEditing(_ textField: UITextField) { fieldType.handler().textFieldDidEndEditing(self, textField: textField) validate() UIAccessibility.post(notification: .layoutChanged, argument: self.containerView) } - public func textFieldDidChangeSelection(_ textField: UITextField) { + open func textFieldDidChangeSelection(_ textField: UITextField) { fieldType.handler().textFieldDidChangeSelection(self, textField: textField) if fieldType.handler().validateOnChange { validate() @@ -371,7 +371,7 @@ extension InputField: UITextFieldDelegate { setNeedsUpdate() } - public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + open func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let shouldChange = fieldType.handler().textField(self, textField: textField, shouldChangeCharactersIn: range, replacementString: string) if shouldChange { UIAccessibility.post(notification: .announcement, argument: string) From 15515b6815b5661b15ce516a2e13f17a3e367981 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 08:37:05 -0500 Subject: [PATCH 088/117] added telephone updating Signed-off-by: Matt Bruce --- .../TextFields/InputField/FieldTypes/Telephone.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift b/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift index bfcd9ef7..89417c19 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift @@ -67,7 +67,14 @@ extension InputField { } - internal func formatUSNumber(_ number: String) -> String { + override func textFieldDidEndEditing(_ inputField: InputField, textField: UITextField) { + if let text = inputField.text { + let rawNumber = text.filter { $0.isNumber } + textField.text = formatUSNumber(rawNumber) + } + } + + func formatUSNumber(_ number: String) -> String { // Format the number in the style XXX-XXX-XXXX let areaCodeLength = 3 let centralOfficeCodeLength = 3 From f95317eb24b0006fcc545c9881c23da52046d506 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 14:36:09 -0500 Subject: [PATCH 089/117] added var to protocol Signed-off-by: Matt Bruce --- VDS/Protocols/FormFieldable.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/VDS/Protocols/FormFieldable.swift b/VDS/Protocols/FormFieldable.swift index 8a620765..82666772 100644 --- a/VDS/Protocols/FormFieldable.swift +++ b/VDS/Protocols/FormFieldable.swift @@ -20,6 +20,9 @@ public protocol FormFieldable { /// Protocol for FormFieldable that require internal validation. public protocol FormFieldInternalValidatable: FormFieldable, Errorable { + /// Rules that drive the validator + var rules: [AnyRule] { get set } + /// Is there an internalError var hasInternalError: Bool { get } /// Internal Error Message that will show. From 95f4ad06a1a9772704dccf87c9710086d4564e7a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 14:36:27 -0500 Subject: [PATCH 090/117] refactored updating of rules Signed-off-by: Matt Bruce --- VDS/Components/TextFields/EntryFieldBase.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index cf2dffa2..78c1077a 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -356,6 +356,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { /// Updates the UI open override func updateView() { super.updateView() + updateRules() updateContainerView() updateContainerWidth() updateTitleLabel() @@ -418,7 +419,6 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { } open func validate(){ - updateRules() validator = FormFieldValidator(field: self, rules: rules) validator?.validate() setNeedsUpdate() From 4ca74fa445f8cacb99523538b7c5b2646f65ed52 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 12 Jul 2024 14:36:37 -0500 Subject: [PATCH 091/117] dynamically add rules Signed-off-by: Matt Bruce --- VDS/Components/TextFields/TextArea/TextArea.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index f8a9a6b3..8768352e 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -111,6 +111,8 @@ open class TextArea: EntryFieldBase { } didSet { + setNeedsUpdate() + if textView.isFirstResponder { validate() } @@ -191,8 +193,9 @@ open class TextArea: EntryFieldBase { override func updateRules() { super.updateRules() - - rules.append(.init(countRule)) + if let maxLength, maxLength > 0 { + rules.append(.init(countRule)) + } } open override func getFieldContainer() -> UIView { From 699626a4b821cffed1f1f2328a4cc13e8e74d3ef Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 15 Jul 2024 08:43:19 -0500 Subject: [PATCH 092/117] updated in the didChange Signed-off-by: Matt Bruce --- VDS/Components/TextFields/InputField/InputField.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index e1609f96..fd36ecac 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -364,11 +364,11 @@ extension InputField: UITextFieldDelegate { open func textFieldDidChangeSelection(_ textField: UITextField) { fieldType.handler().textFieldDidChangeSelection(self, textField: textField) + text = textField.text + sendActions(for: .valueChanged) if fieldType.handler().validateOnChange { validate() } - sendActions(for: .valueChanged) - setNeedsUpdate() } open func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { From 10f4f1db1186e005e15a01732ae858cd9041a953 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Mon, 15 Jul 2024 08:43:27 -0500 Subject: [PATCH 093/117] added new validator Signed-off-by: Matt Bruce --- .../InputField/FieldTypes/Telephone.swift | 41 +++++++++++++++---- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift b/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift index 89417c19..88a52723 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift @@ -10,6 +10,35 @@ import UIKit extension InputField { + public class TelephoneNumberValidator: Rule, Withable { + public var format: String + public var errorMessage: String = "Please enter a valid telephone number" + + public init(format: String) { + self.format = format + } + + public func isValid(value: String?) -> Bool { + guard let value, !value.isEmpty else { return true } + let regex = createRegex(from: format) + let predicate = NSPredicate(format: "SELF MATCHES %@", regex) + let valid = predicate.evaluate(with: value) + return valid + } + + private func createRegex(from format: String) -> String { + // Escape special regex characters in the format string + let escapedFormat = NSRegularExpression.escapedPattern(for: format) + + // Replace placeholder characters with regex patterns + let regex = escapedFormat + .replacingOccurrences(of: "X", with: "\\d") + + return "^" + regex + "$" + } + } + + class TelephoneHandler: FieldTypeHandler { static let shared = TelephoneHandler() @@ -25,14 +54,7 @@ extension InputField { } override func appendRules(_ inputField: InputField) { - if let text = inputField.textField.text, text.count > 0 { - let rule = CharacterCountRule().copyWith { - $0.maxLength = "XXX-XXX-XXXX".count - $0.compareType = .equals - $0.errorMessage = "Enter a valid telephone." - } - inputField.rules.append(.init(rule)) - } + inputField.rules.append(.init(TelephoneNumberValidator(format: "XXX-XXX-XXXX"))) } override func textField(_ inputField: InputField, textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { @@ -62,6 +84,8 @@ extension InputField { textField.selectedTextRange = textField.textRange(from: newPosition, to: newPosition) } + value = formattedNumber + // Prevent the default behavior return false @@ -71,6 +95,7 @@ extension InputField { if let text = inputField.text { let rawNumber = text.filter { $0.isNumber } textField.text = formatUSNumber(rawNumber) + value = textField.text } } From bf46f85622d714a5131acdcd65bf4c333264fa6c Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Tue, 16 Jul 2024 12:31:06 +0530 Subject: [PATCH 094/117] Fixing CXTDT-586497 issue, table contents alignment is based on the row type, is header --- VDS/Components/Table/Table.swift | 3 ++- VDS/Components/Table/TableCellItem.swift | 10 +++++----- VDS/Components/Table/TableRowModel.swift | 5 ++++- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift index 8ce5f462..9ce35ef6 100644 --- a/VDS/Components/Table/Table.swift +++ b/VDS/Components/Table/Table.swift @@ -147,7 +147,8 @@ 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].columns[indexPath.row] let shouldStrip = striped ? (indexPath.section % 2 != 0) : false - cell.updateCell(content: currentItem, surface: surface, striped: shouldStrip, padding: padding) + let isHeader = tableData[indexPath.section].isHeader + cell.updateCell(content: currentItem, surface: surface, striped: shouldStrip, padding: padding, isHeader: isHeader) return cell } diff --git a/VDS/Components/Table/TableCellItem.swift b/VDS/Components/Table/TableCellItem.swift index 23d2df74..ddd35277 100644 --- a/VDS/Components/Table/TableCellItem.swift +++ b/VDS/Components/Table/TableCellItem.swift @@ -58,7 +58,7 @@ final class TableCellItem: UICollectionViewCell { //-------------------------------------------------- /// 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) { + public func updateCell(content: TableItemModel, surface: Surface, striped: Bool = false, padding: Table.Padding = .standard, isHeader: Bool = false) { containerView.subviews.forEach({ $0.removeFromSuperview() }) self.padding = padding @@ -83,10 +83,10 @@ final class TableCellItem: UICollectionViewCell { 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) + containerView.trailingAnchor.constraint(greaterThanOrEqualTo: component.trailingAnchor, constant: padding.horizontalValue()) ]) + + component.topAnchor.constraint(equalTo: containerView.topAnchor, constant: padding.verticalValue()).isActive = !isHeader + containerView.bottomAnchor.constraint(equalTo: component.bottomAnchor, constant: padding.verticalValue()).isActive = isHeader } } diff --git a/VDS/Components/Table/TableRowModel.swift b/VDS/Components/Table/TableRowModel.swift index a838438f..22f428ce 100644 --- a/VDS/Components/Table/TableRowModel.swift +++ b/VDS/Components/Table/TableRowModel.swift @@ -11,11 +11,14 @@ public struct TableRowModel { public var columns: [TableItemModel] + public var isHeader: Bool = false + public var columnsCount: Int { return columns.count } - public init(columns: [TableItemModel]) { + public init(columns: [TableItemModel], isHeader: Bool = false) { self.columns = columns + self.isHeader = isHeader } } From 6661e603333738fd661d96d0b3740df44ba82ccb Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 16 Jul 2024 16:17:27 -0500 Subject: [PATCH 095/117] abstract to String extension method Signed-off-by: Matt Bruce --- .../InputField/FieldTypes/Telephone.swift | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift b/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift index 88a52723..10255be6 100644 --- a/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift +++ b/VDS/Components/TextFields/InputField/FieldTypes/Telephone.swift @@ -71,7 +71,7 @@ extension InputField { let rawNumber = newText.filter { $0.isNumber } // Format the number with dashes - let formattedNumber = formatUSNumber(rawNumber) + let formattedNumber = rawNumber.formatUSNumber() // Set the formatted text textField.text = formattedNumber @@ -93,44 +93,45 @@ extension InputField { override func textFieldDidEndEditing(_ inputField: InputField, textField: UITextField) { if let text = inputField.text { - let rawNumber = text.filter { $0.isNumber } - textField.text = formatUSNumber(rawNumber) + textField.text = text.formatUSNumber() value = textField.text } } - - func formatUSNumber(_ number: String) -> String { - // Format the number in the style XXX-XXX-XXXX - let areaCodeLength = 3 - let centralOfficeCodeLength = 3 - let lineNumberLength = 4 - - var formattedNumber = "" - - if number.count > 0 { - formattedNumber.append(contentsOf: number.prefix(areaCodeLength)) - } - - if number.count > areaCodeLength { - let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength) - let endIndex = number.index(startIndex, offsetBy: min(centralOfficeCodeLength, number.count - areaCodeLength)) - let centralOfficeCode = number[startIndex.. areaCodeLength + centralOfficeCodeLength { - let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength + centralOfficeCodeLength) - let endIndex = number.index(startIndex, offsetBy: min(lineNumberLength, number.count - areaCodeLength - centralOfficeCodeLength)) - let lineNumber = number[startIndex.. String { + // Format the number in the style XXX-XXX-XXXX + let areaCodeLength = 3 + let centralOfficeCodeLength = 3 + let lineNumberLength = 4 + + var formattedNumber = "" + let number = filter { $0.isNumber } + + if number.count > 0 { + formattedNumber.append(contentsOf: number.prefix(areaCodeLength)) + } + + if number.count > areaCodeLength { + let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength) + let endIndex = number.index(startIndex, offsetBy: min(centralOfficeCodeLength, number.count - areaCodeLength)) + let centralOfficeCode = number[startIndex.. areaCodeLength + centralOfficeCodeLength { + let startIndex = number.index(number.startIndex, offsetBy: areaCodeLength + centralOfficeCodeLength) + let endIndex = number.index(startIndex, offsetBy: min(lineNumberLength, number.count - areaCodeLength - centralOfficeCodeLength)) + let lineNumber = number[startIndex.. Date: Wed, 17 Jul 2024 18:32:04 +0530 Subject: [PATCH 096/117] Digital ACT-191 ONEAPP-9311 story: changes about PR notes --- VDS/Components/Carousel/Carousel.swift | 193 +++++------------- .../Carousel/CarouselSlotItemModel.swift | 4 +- 2 files changed, 58 insertions(+), 139 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index a3cb1f1c..90b35588 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -78,14 +78,11 @@ open class Carousel: View { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- - /// Aspect-ratio options for tilelet in the carousel. If 'none' is passed, the tilelet will take the height of the tallest item in the carousel. - open var aspectRatio: Tilelet.AspectRatio = .none { didSet { setNeedsUpdate() } } - - /// Data used to render tilelets in the carousel. - open var data: [Any] = [] { didSet { setNeedsUpdate() } } + /// views used to render view in the carousel slots. + open var views: [UIView] = [] { didSet { setNeedsUpdate() } } /// If provided, width of slots will be rendered based on this value. If omitted, default widths are rendered. - open var width : Width? { + open var width: Width? { get { _width } set { if let newValue { @@ -110,19 +107,11 @@ open class Carousel: View { } /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. - open var gutter: Gutter { - get { return _gutter } - set { - _gutter = newValue - setNeedsUpdate() - } - } + open var gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX { didSet { setNeedsUpdate() } } /// The amount of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. - open var layout: CarouselScrollbar.Layout { - get { return _layout } - set { - _layout = newValue + open var layout: CarouselScrollbar.Layout = UIDevice.isIPad ? .threeUP : .oneUP { + didSet { carouselScrollBar.position = 0 setNeedsUpdate() } @@ -143,82 +132,29 @@ open class Carousel: View { } /// Config object for pagination. - open var pagination: CarouselPaginationModel { - get { return _pagination } - set { - _pagination = newValue - setNeedsUpdate() - } - } + open var pagination: CarouselPaginationModel = .init(kind: .lowContrast, floating: true) { didSet {setNeedsUpdate() } } /// If provided, will determine the conditions to render the pagination arrows. - open var paginationDisplay: PaginationDisplay { - get { return _paginationDisplay } - set { - _paginationDisplay = newValue - setNeedsUpdate() - } - } + open var paginationDisplay: PaginationDisplay = .none { didSet {setNeedsUpdate() } } /// If provided, will apply margin to pagination arrows. Can be set to either positive or negative values. /// The default value will be 12px in tablet and 8px in mobile. These values are the default in order to avoid overlapping content within the carousel. - open var paginationInset: CGFloat { - get { return _paginationInset } - set { - _paginationInset = newValue - updatePaginationInset() - } - } + open var paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X { didSet { updatePaginationInset() } } /// Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices. - open var peek: Peek { - get { return _peek } - set { - _peek = newValue - setNeedsUpdate() - } - } + open var peek: Peek = .standard { didSet { setNeedsUpdate() } } /// The initial visible slide's index in the carousel. open var selectedIndex: Int? { didSet { setNeedsUpdate() } } /// If provided, will set the alignment for slot content when the slots has different heights. - open var slotAlignment: CarouselSlotAlignmentModel? { - get { return _slotAlignment } - set { - if let newValue { - _slotAlignment = newValue - } else { - _slotAlignment = nil - } - setNeedsUpdate() - } - } + open var slotAlignment: CarouselSlotAlignmentModel? = nil { didSet { setNeedsUpdate() } } /// Render item style. If provided, the slot gets the background, width, height, border-radius. - open var renderItemStyle: CarouselRenderItemStyle? { - get { return _renderItemStyle } - set { - if let newValue { - _renderItemStyle = newValue - } else { - _renderItemStyle = nil - } - setNeedsUpdate() - } - } + open var renderItemStyle: CarouselRenderItemStyle? = nil { didSet { setNeedsUpdate() } } - /// Render item. It passes a data array object and expects the styled component to apply in return. - open var renderItem: CarouselSlotItemModel? { - get { _renderItem } - set { - if let newValue { - _renderItem = newValue - } else { - _renderItem = nil - } - } - } +// /// Render item. It passes a data array object and expects the styled component to apply in return. +// open var renderItem: CarouselSlotItemModel? = nil { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Private Properties @@ -278,18 +214,6 @@ open class Carousel: View { /// A publisher for when the carousel moves. Passes parameters (data). open var onScrollPublisher = PassthroughSubject, Never>() private var onScrollCancellable: AnyCancellable? - - internal var _layout: CarouselScrollbar.Layout = UIDevice.isIPad ? .threeUP : .oneUP - internal var _pagination: CarouselPaginationModel = .init(kind: .lowContrast, floating: true) - internal var _paginationDisplay: PaginationDisplay = .none - internal var _paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X - internal var _gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX - internal var _peek: Peek = .standard - internal var _numberOfSlides: Int = 1 - internal var _slotAlignment: CarouselSlotAlignmentModel? = nil - internal var _renderItemStyle: CarouselRenderItemStyle? = nil - internal var _renderItem: CarouselSlotItemModel? = nil - private var _width: Width? = nil private var selectedGroupIndex: Int? = nil private var containerStackHeightConstraint: NSLayoutConstraint? @@ -301,7 +225,7 @@ open class Carousel: View { // The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile. let scrollbarTopSpace = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X - var slotHeight = 100.0 + var slotHeight = 50.0 var peekMinimum = 24.0 var minimumSlotWidth = 0.0 var carouselScrollbarMinWidth = 96.0 @@ -386,8 +310,8 @@ open class Carousel: View { } } - carouselScrollBar.numberOfSlides = data.count - carouselScrollBar.layout = _layout + carouselScrollBar.numberOfSlides = views.count + carouselScrollBar.layout = layout if (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) { carouselScrollBar.position = 1 } @@ -417,7 +341,6 @@ open class Carousel: View { open override func reset() { super.reset() shouldUpdateView = false - aspectRatio = .none layout = UIDevice.isIPad ? .threeUP : .oneUP pagination = .init(kind: .lowContrast, floating: true) paginationDisplay = .none @@ -473,7 +396,7 @@ open class Carousel: View { // Show/Hide pagination buttons of Carousel based on First or Middle or Last private func showPaginationControls() { - if carouselScrollBar.numberOfSlides == _layout.value { + if carouselScrollBar.numberOfSlides == layout.value { previousButton.isHidden = true nextButton.isHidden = true } else { @@ -482,6 +405,26 @@ open class Carousel: View { } } + private func estimateHeightFor(item: CarouselSlotItemModel, with width: CGFloat) -> CGFloat { + let itemWidth = width + let maxSize = CGSize(width: itemWidth, height: CGFloat.greatestFiniteMagnitude) + let estItemSize = item.component?.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) ?? CGSize(width: itemWidth, height: item.defaultHeight) + return estItemSize.height + } + + private func fetchCarouselHeight() { + if views.count > 0 { + var height = slotHeight + for x in 0...views.count - 1 { + // Add received component + let item : CarouselSlotItemModel = .init(style: renderItemStyle, component: views[x]) + slotHeight = estimateHeightFor(item: item, with: minimumSlotWidth) + height = slotHeight > height ? slotHeight : height + } + slotHeight = height + } + } + // Add carousel slots and load data if any private func addCarouselSlots() { getSlotWidth() @@ -494,11 +437,13 @@ open class Carousel: View { // Removing subView from its parent view subView.removeFromSuperview() } + + fetchCarouselHeight() // Add carousel items - if data.count > 0 { + if views.count > 0 { var xPos = 0.0 - for x in 0...data.count - 1 { + for x in 0...views.count - 1 { // Add Carousel Slot let carouselSlot = View().with { @@ -507,8 +452,7 @@ open class Carousel: View { } scrollView.addSubview(carouselSlot) scrollView.delegate = self - let size = ratioSize(for: minimumSlotWidth) - slotHeight = size.height + carouselSlot .pinTop() .pinBottom() @@ -518,24 +462,28 @@ open class Carousel: View { carouselSlot.layer.cornerRadius = 12.0 xPos = xPos + minimumSlotWidth + gutter.value + // Add received component + let item : CarouselSlotItemModel = .init(style: renderItemStyle, component: views[x]) + let contentViewHeight = estimateHeightFor(item: item, with: minimumSlotWidth) + // Add subview for content to Carousel Slot let contentView = View().with { $0.clipsToBounds = true - $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) } carouselSlot.addSubview(contentView) - - // Add received component - let item : CarouselSlotItemModel = .init(style: renderItemStyle, component: data[x] as? UIView) + if let component = item.component { if slotAlignment != nil { + // If slotAlignment exist, should use expected height + contentView.widthAnchor.constraint(equalToConstant: minimumSlotWidth).activate() + contentView.heightAnchor.constraint(equalToConstant: contentViewHeight).activate() setSlotAlignment(contentView: contentView, parentView: carouselSlot) } else { +// // If no slotAlignment, should use full slot contentView.pinToSuperView() } contentView.addSubview(component) component.pinToSuperView() - contentView.layer.cornerRadius = component.layer.cornerRadius if var surfacedView = component as? Surfaceable { surfacedView.surface = surface } @@ -590,7 +538,7 @@ open class Carousel: View { // Get the slot width relative to the peak private func getSlotWidth() { let actualWidth = containerView.frame.size.width - let isScrollbarSuppressed = data.count > 0 && layout.value == data.count + let isScrollbarSuppressed = views.count > 0 && layout.value == views.count let isPeekMinimumOnTablet = UIDevice.isIPad && peek == .minimum let isPeekNone: Bool = peek == .none minimumSlotWidth = isScrollbarSuppressed || isPeekMinimumOnTablet || isPeekNone ? actualWidth - ((CGFloat(layout.value)-1) * gutter.value): actualWidth - (CGFloat(layout.value) * gutter.value) @@ -626,37 +574,6 @@ open class Carousel: View { updateScrollPosition(position: carouselScrollBar.position, callbackText:"pageControlClicks") } - // The size of slot depends on the selected aspect ratio - private func ratioSize(for width: CGFloat) -> CGSize { - var height: CGFloat = width - - switch aspectRatio { - case .ratio1x1: - break; - case .ratio3x4: - height = (4 / 3) * width - case .ratio4x3: - height = (3 / 4) * width - case .ratio2x3: - height = (3 / 2) * width - case .ratio3x2: - height = (2 / 3) * width - case .ratio9x16: - height = (16 / 9) * width - case .ratio16x9: - height = (9 / 16) * width - case .ratio1x2: - height = (2 / 1) * width - case .ratio2x1: - height = (1 / 2) * width - - default: - break - } - - return CGSize(width: width, height: height) - } - private func updatePaginationInset() { prevButtonLeadingConstraint?.isActive = false nextButtonTrailingConstraint?.isActive = false @@ -688,7 +605,7 @@ open class Carousel: View { } else if position == totalPositions { xPos = scrollContentSizeWidth - containerView.frame.size.width } else { - let isScrollbarSuppressed = data.count > 0 && layout.value == data.count + let isScrollbarSuppressed = views.count > 0 && layout.value == views.count let isPeekMinimumOnTablet = UIDevice.isIPad && peek == .minimum if !isScrollbarSuppressed { let subpart = minimumSlotWidth + gutter.value @@ -719,7 +636,7 @@ open class Carousel: View { // Get the overall positions of the carousel scrollbar relative to the slides and selected layout private func totalPositions() -> Int { - return Int (ceil (Double(carouselScrollBar.numberOfSlides) / Double(_layout.value))) + return Int (ceil (Double(carouselScrollBar.numberOfSlides) / Double(layout.value))) } } diff --git a/VDS/Components/Carousel/CarouselSlotItemModel.swift b/VDS/Components/Carousel/CarouselSlotItemModel.swift index 3830b29f..a52192c4 100644 --- a/VDS/Components/Carousel/CarouselSlotItemModel.swift +++ b/VDS/Components/Carousel/CarouselSlotItemModel.swift @@ -14,7 +14,9 @@ public struct CarouselSlotItemModel { /// Style props if provided any public var style: CarouselRenderItemStyle? - + + public let defaultHeight: CGFloat = 50.0 + /// Component to be show on Carousel slot public var component: UIView? From 4ae07b640246a241291fdd7ded7dac02915c5dd2 Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Thu, 18 Jul 2024 12:25:04 +0530 Subject: [PATCH 097/117] Digital ACT-191 ONEAPP-7013 story: removing width prop, and passing selectedGroupIndex through onChange --- VDS/Components/Carousel/Carousel.swift | 96 ++++--------------- .../Carousel/CarouselSlotItemModel.swift | 12 +-- 2 files changed, 20 insertions(+), 88 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 90b35588..5595ef68 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -81,31 +81,6 @@ open class Carousel: View { /// views used to render view in the carousel slots. open var views: [UIView] = [] { didSet { setNeedsUpdate() } } - /// If provided, width of slots will be rendered based on this value. If omitted, default widths are rendered. - open var width: Width? { - get { _width } - set { - if let newValue { - switch newValue { - case .percentage(let percentage): - if percentage >= 10 && percentage <= 100.0 { - let expectedWidth = safeAreaLayoutGuide.layoutFrame.size.width * (percentage/100) - if expectedWidth > carouselScrollbarMinWidth { - _width = newValue - } - } - case .value(let value): - if value > carouselScrollbarMinWidth { - _width = newValue - } - } - } else { - _width = nil - } - setNeedsUpdate() - } - } - /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. open var gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX { didSet { setNeedsUpdate() } } @@ -117,7 +92,7 @@ open class Carousel: View { } } - /// A callback when moving the carousel. Returns initial visible slide's index in the carousel. + /// A callback when moving the carousel. Returns selectedGroupIndex. open var onChange: ((Int) -> Void)? { get { nil } set { @@ -150,12 +125,6 @@ open class Carousel: View { /// If provided, will set the alignment for slot content when the slots has different heights. open var slotAlignment: CarouselSlotAlignmentModel? = nil { didSet { setNeedsUpdate() } } - /// Render item style. If provided, the slot gets the background, width, height, border-radius. - open var renderItemStyle: CarouselRenderItemStyle? = nil { didSet { setNeedsUpdate() } } - -// /// Render item. It passes a data array object and expects the styled component to apply in return. -// open var renderItem: CarouselSlotItemModel? = nil { didSet { setNeedsUpdate() } } - //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -207,15 +176,10 @@ open class Carousel: View { $0.icon.customSize = UIDevice.isIPad ? 16 : 12 } - /// A publisher for when the scrubber position changes. Passes parameters (position). + /// A publisher for when moving the carousel. Passes parameters selectedGroupIndex (position). open var onChangePublisher = PassthroughSubject() private var onChangeCancellable: AnyCancellable? - /// A publisher for when the carousel moves. Passes parameters (data). - open var onScrollPublisher = PassthroughSubject, Never>() - private var onScrollCancellable: AnyCancellable? - private var _width: Width? = nil - private var selectedGroupIndex: Int? = nil private var containerStackHeightConstraint: NSLayoutConstraint? private var containerViewHeightConstraint: NSLayoutConstraint? private var prevButtonLeadingConstraint: NSLayoutConstraint? @@ -225,7 +189,7 @@ open class Carousel: View { // The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile. let scrollbarTopSpace = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X - var slotHeight = 50.0 + var slotDefaultHeight = 50.0 var peekMinimum = 24.0 var minimumSlotWidth = 0.0 var carouselScrollbarMinWidth = 96.0 @@ -248,13 +212,10 @@ open class Carousel: View { containerView .pinTop() .pinBottom() - .pinLeadingGreaterThanOrEqualTo() + .pinLeading() .pinTrailing() .heightGreaterThanEqualTo(containerSize.height) - containerView.centerYAnchor.constraint(equalTo: centerYAnchor).activate() - containerLeadingConstraint = containerView.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor, constant: 0) - containerLeadingConstraint?.activate() // Add content stackview containerView.addSubview(contentStackView) @@ -293,23 +254,6 @@ open class Carousel: View { open override func updateView() { super.updateView() - if containerView.frame.size.width > 0 { - if let width { - containerLeadingConstraint?.deactivate() - switch width { - case .value(let value): - var expectedWidth = value - let fullWidth = safeAreaLayoutGuide.layoutFrame.size.width - expectedWidth = expectedWidth > fullWidth ? fullWidth : expectedWidth - containerLeadingConstraint?.constant = safeAreaLayoutGuide.layoutFrame.size.width - expectedWidth - case .percentage(let percentage): - let expectedWidth = safeAreaLayoutGuide.layoutFrame.size.width * (percentage/100) - containerLeadingConstraint?.constant = safeAreaLayoutGuide.layoutFrame.size.width - expectedWidth - } - containerLeadingConstraint?.activate() - } - } - carouselScrollBar.numberOfSlides = views.count carouselScrollBar.layout = layout if (carouselScrollBar.position == 0 || carouselScrollBar.position > carouselScrollBar.numberOfSlides) { @@ -347,7 +291,6 @@ open class Carousel: View { paginationInset = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX peek = .standard - width = nil } //-------------------------------------------------- @@ -412,17 +355,16 @@ open class Carousel: View { return estItemSize.height } - private func fetchCarouselHeight() { + private func fetchCarouselHeight() -> CGFloat { + var height = slotDefaultHeight if views.count > 0 { - var height = slotHeight for x in 0...views.count - 1 { - // Add received component - let item : CarouselSlotItemModel = .init(style: renderItemStyle, component: views[x]) - slotHeight = estimateHeightFor(item: item, with: minimumSlotWidth) - height = slotHeight > height ? slotHeight : height + let item : CarouselSlotItemModel = .init(component: views[x]) + let estHeight = estimateHeightFor(item: item, with: minimumSlotWidth) + height = estHeight > height ? estHeight : height } - slotHeight = height } + return height } // Add carousel slots and load data if any @@ -431,14 +373,13 @@ open class Carousel: View { if containerView.frame.size.width > 0 { containerViewHeightConstraint?.isActive = false containerStackHeightConstraint?.isActive = false + let slotHeight = fetchCarouselHeight() // Perform a loop to iterate each subView scrollView.subviews.forEach { subView in // Removing subView from its parent view subView.removeFromSuperview() } - - fetchCarouselHeight() // Add carousel items if views.count > 0 { @@ -452,7 +393,7 @@ open class Carousel: View { } scrollView.addSubview(carouselSlot) scrollView.delegate = self - + carouselSlot .pinTop() .pinBottom() @@ -463,7 +404,7 @@ open class Carousel: View { xPos = xPos + minimumSlotWidth + gutter.value // Add received component - let item : CarouselSlotItemModel = .init(style: renderItemStyle, component: views[x]) + let item : CarouselSlotItemModel = .init(component: views[x]) let contentViewHeight = estimateHeightFor(item: item, with: minimumSlotWidth) // Add subview for content to Carousel Slot @@ -471,7 +412,7 @@ open class Carousel: View { $0.clipsToBounds = true } carouselSlot.addSubview(contentView) - + if let component = item.component { if slotAlignment != nil { // If slotAlignment exist, should use expected height @@ -479,12 +420,15 @@ open class Carousel: View { contentView.heightAnchor.constraint(equalToConstant: contentViewHeight).activate() setSlotAlignment(contentView: contentView, parentView: carouselSlot) } else { -// // If no slotAlignment, should use full slot + // If no slotAlignment, should use full slot contentView.pinToSuperView() } + carouselSlot.backgroundColor = .clear + carouselSlot.layer.cornerRadius = 0 contentView.addSubview(component) component.pinToSuperView() if var surfacedView = component as? Surfaceable { + contentView.surface = surface surfacedView.surface = surface } } @@ -628,9 +572,7 @@ open class Carousel: View { let yPos = scrollView.contentOffset.y scrollView.setContentOffset(CGPoint(x: xPos, y: yPos), animated: true) showPaginationControls() - selectedIndex = ((position-1) * layout.value) + 1 - onChangePublisher.send(selectedIndex ?? 1) - selectedGroupIndex = position + onChangePublisher.send(position-1) } } diff --git a/VDS/Components/Carousel/CarouselSlotItemModel.swift b/VDS/Components/Carousel/CarouselSlotItemModel.swift index a52192c4..3cb7240a 100644 --- a/VDS/Components/Carousel/CarouselSlotItemModel.swift +++ b/VDS/Components/Carousel/CarouselSlotItemModel.swift @@ -11,23 +11,13 @@ import VDSCoreTokens /// A custom data type that holds the style and component for a slot of the 'Carousel' component. public struct CarouselSlotItemModel { - - /// Style props if provided any - public var style: CarouselRenderItemStyle? public let defaultHeight: CGFloat = 50.0 /// Component to be show on Carousel slot public var component: UIView? - public init(style: CarouselRenderItemStyle? = nil, component: UIView? = nil) { - self.style = style + public init(component: UIView? = nil) { self.component = component - if let color = style?.backgroundColor { - self.component?.backgroundColor = .init(hexString: color) - } - if let borderRadius = style?.borderRadius { - self.component?.layer.cornerRadius = borderRadius - } } } From c2ec941a3cb4ef0d9be861774b8eb84ca1159dc4 Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Thu, 18 Jul 2024 12:49:12 +0530 Subject: [PATCH 098/117] Digital ACT-191 ONEAPP-7013 story: updating carousel position if receives selectedIndex --- VDS/Components/Carousel/Carousel.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 5595ef68..35357778 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -279,6 +279,12 @@ open class Carousel: View { updatePaginationControls() addCarouselSlots() + + // If selectedIndex is received, the carousel should update its position. + if let selectedIndex { + let totalPos = totalPositions() + carouselScrollBar.position = selectedIndex >= totalPos ? totalPos : selectedIndex+1 + } } /// Resets to default settings. @@ -300,6 +306,12 @@ open class Carousel: View { nextButton.onClick = { _ in self.nextButtonClick() } previousButton.onClick = { _ in self.previousButtonClick() } + /// Will be called when the scrubber position changes. + carouselScrollBar.onScrubberDrag = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onThumbPositionChange") + } + /// Will be called when the scrollbar thumb move forward. carouselScrollBar.onMoveForward = { [weak self] scrubberId in guard let self else { return } From 559423f629110d992625fd294cbe3e9c95345933 Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Thu, 18 Jul 2024 13:22:41 +0530 Subject: [PATCH 099/117] Digital ACT-191 ONEAPP-7013 story: removed unused file --- VDS.xcodeproj/project.pbxproj | 4 --- .../Carousel/CarouselRenderItemStyle.swift | 33 ------------------- 2 files changed, 37 deletions(-) delete mode 100644 VDS/Components/Carousel/CarouselRenderItemStyle.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 64ec60bb..e1bf1b05 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ 18013CED2C355BF900907F18 /* CarouselSlotItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18013CEC2C355BF900907F18 /* CarouselSlotItemModel.swift */; }; - 18013CEF2C355C5200907F18 /* CarouselRenderItemStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18013CEE2C355C5200907F18 /* CarouselRenderItemStyle.swift */; }; 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; }; 1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; }; 1842B1DF2BECE28B0021AFCA /* CalendarDateViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */; }; @@ -210,7 +209,6 @@ /* Begin PBXFileReference section */ 18013CEC2C355BF900907F18 /* CarouselSlotItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselSlotItemModel.swift; sourceTree = ""; }; - 18013CEE2C355C5200907F18 /* CarouselRenderItemStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselRenderItemStyle.swift; sourceTree = ""; }; 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = ""; }; 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = ""; }; 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = ""; }; @@ -505,7 +503,6 @@ 18B9763E2C11BA4A009271DF /* CarouselPaginationModel.swift */, 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */, 18013CEC2C355BF900907F18 /* CarouselSlotItemModel.swift */, - 18013CEE2C355C5200907F18 /* CarouselRenderItemStyle.swift */, 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */, ); path = Carousel; @@ -1367,7 +1364,6 @@ EAC71A1D2A2E155A00E47A9F /* Checkbox.swift in Sources */, EAF7F0AB289B13FD00B287F5 /* TextStyleLabelAttribute.swift in Sources */, 18AE87502C06FDA60075F181 /* Carousel.swift in Sources */, - 18013CEF2C355C5200907F18 /* CarouselRenderItemStyle.swift in Sources */, EAB1D29C28A5618900DAE764 /* RadioButtonGroup.swift in Sources */, EA81410B2A0E8E3C004F60D2 /* ButtonIcon.swift in Sources */, EA985BE629688F6A00F2FF2E /* TileletBadgeModel.swift in Sources */, diff --git a/VDS/Components/Carousel/CarouselRenderItemStyle.swift b/VDS/Components/Carousel/CarouselRenderItemStyle.swift deleted file mode 100644 index c447dfa1..00000000 --- a/VDS/Components/Carousel/CarouselRenderItemStyle.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// CarouselRenderItemStyle.swift -// VDS -// -// Created by Kanamarlapudi, Vasavi on 30/06/24. -// - -import Foundation -import UIKit -import VDSCoreTokens - -/// A custom data type that holds the style props if provided any. -public struct CarouselRenderItemStyle { - - /// BackgroundColor for slot - public let backgroundColor: String? - - /// Height for slot - public var height: CGFloat? - - /// BorderRadius for slot - public var borderRadius: CGFloat? - - /// Width for slot - public var width: CGFloat? - - public init(backgroundColor: String?, height: CGFloat?, width: CGFloat?, borderRadius: CGFloat?) { - self.backgroundColor = backgroundColor - self.height = height - self.borderRadius = borderRadius ?? 12.0 - self.width = width - } -} From 93004f6e1205114d60c6c1fee52d7b0064912f2e Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Thu, 18 Jul 2024 17:28:40 +0530 Subject: [PATCH 100/117] Digital ACT-191 ONEAPP-7013 story: Refactored code, removing redundant view and adding component to slot. --- VDS/Components/Carousel/Carousel.swift | 31 ++++++++++++-------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 35357778..9a9b3251 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -417,32 +417,29 @@ open class Carousel: View { // Add received component let item : CarouselSlotItemModel = .init(component: views[x]) - let contentViewHeight = estimateHeightFor(item: item, with: minimumSlotWidth) - - // Add subview for content to Carousel Slot - let contentView = View().with { - $0.clipsToBounds = true - } - carouselSlot.addSubview(contentView) - if let component = item.component { + carouselSlot.addSubview(component) if slotAlignment != nil { - // If slotAlignment exist, should use expected height - contentView.widthAnchor.constraint(equalToConstant: minimumSlotWidth).activate() - contentView.heightAnchor.constraint(equalToConstant: contentViewHeight).activate() - setSlotAlignment(contentView: contentView, parentView: carouselSlot) + // If slotAlignment exist, use component's own sizes + component.widthAnchor.constraint(lessThanOrEqualTo: carouselSlot.widthAnchor).activate() + setSlotAlignment(contentView: component, parentView: carouselSlot) } else { // If no slotAlignment, should use full slot - contentView.pinToSuperView() + component.pinToSuperView() } carouselSlot.backgroundColor = .clear carouselSlot.layer.cornerRadius = 0 - contentView.addSubview(component) - component.pinToSuperView() if var surfacedView = component as? Surfaceable { - contentView.surface = surface surfacedView.surface = surface } + + // TO BE REMOVED AFTER VQA - borderWidth and borderColor below did set only for Testing purpose to VQA + carouselSlot.layer.borderWidth = 1.0 + carouselSlot.layer.borderColor = SurfaceColorConfiguration().with { + $0.lightColor = VDSColor.elementsPrimaryOnlight + $0.darkColor = VDSColor.elementsPrimaryOndark + }.getColor(self).cgColor + } } scrollView.contentSize = CGSize(width: xPos - gutter.value, height: slotHeight) @@ -462,7 +459,7 @@ open class Carousel: View { } // Set slot alignment if provided. Used only when slot content have different heights or widths. - private func setSlotAlignment(contentView: View, parentView: View) { + private func setSlotAlignment(contentView: UIView, parentView: View) { parentView.backgroundColor = .clear switch slotAlignment?.vertical { case .top: From 7f13aebc7ef9a78137531f7c25aaa8bd2c1e6d8b Mon Sep 17 00:00:00 2001 From: Sumanth Nadigadda Date: Thu, 18 Jul 2024 19:52:15 +0530 Subject: [PATCH 101/117] Fix for CXTDT-586375, updating the way padding is set to view, based on striped status. --- VDS/Components/Table/Table.swift | 10 +++++----- VDS/Components/Table/TableCellItem.swift | 17 +++++++---------- VDS/Components/Table/TableFlowLayout.swift | 9 ++++++--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift index 9ce35ef6..39cc6698 100644 --- a/VDS/Components/Table/Table.swift +++ b/VDS/Components/Table/Table.swift @@ -51,10 +51,8 @@ open class Table: View { func horizontalValue() -> CGFloat { switch self { - case .standard: - return UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X - case .compact: - return UIDevice.isIPad ? VDSLayout.space8X : VDSLayout.space6X + case .standard, .compact: + return UIDevice.isIPad ? VDSLayout.space4X : VDSLayout.space3X } } @@ -148,7 +146,9 @@ extension Table: UICollectionViewDelegate, UICollectionViewDataSource, TableColl let currentItem = tableData[indexPath.section].columns[indexPath.row] let shouldStrip = striped ? (indexPath.section % 2 != 0) : false let isHeader = tableData[indexPath.section].isHeader - cell.updateCell(content: currentItem, surface: surface, striped: shouldStrip, padding: padding, isHeader: isHeader) + var edgePadding = UIEdgeInsets(top: padding.verticalValue(), left: 0, bottom: padding.verticalValue(), right: padding.horizontalValue()) + edgePadding.left = (indexPath.row == 0 && !striped) ? VDSLayout.space1X : padding.horizontalValue() + cell.updateCell(content: currentItem, surface: surface, striped: shouldStrip, padding: edgePadding, isHeader: isHeader) return cell } diff --git a/VDS/Components/Table/TableCellItem.swift b/VDS/Components/Table/TableCellItem.swift index ddd35277..0c87fb35 100644 --- a/VDS/Components/Table/TableCellItem.swift +++ b/VDS/Components/Table/TableCellItem.swift @@ -29,10 +29,7 @@ final class TableCellItem: UICollectionViewCell { /// 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 //-------------------------------------------------- @@ -58,10 +55,10 @@ final class TableCellItem: UICollectionViewCell { //-------------------------------------------------- /// 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, isHeader: Bool = false) { + public func updateCell(content: TableItemModel, surface: Surface, striped: Bool = false, padding: UIEdgeInsets, isHeader: Bool = false) { containerView.subviews.forEach({ $0.removeFromSuperview() }) - self.padding = padding + containerView.surface = surface containerView.backgroundColor = striped ? stripedColorConfiguration.getColor(surface) : backgroundColorConfiguration.getColor(surface) @@ -82,11 +79,11 @@ final class TableCellItem: UICollectionViewCell { } NSLayoutConstraint.activate([ - component.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: VDSLayout.space1X), - containerView.trailingAnchor.constraint(greaterThanOrEqualTo: component.trailingAnchor, constant: padding.horizontalValue()) + component.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: padding.left), + containerView.trailingAnchor.constraint(greaterThanOrEqualTo: component.trailingAnchor, constant: padding.right) ]) - component.topAnchor.constraint(equalTo: containerView.topAnchor, constant: padding.verticalValue()).isActive = !isHeader - containerView.bottomAnchor.constraint(equalTo: component.bottomAnchor, constant: padding.verticalValue()).isActive = isHeader + component.topAnchor.constraint(equalTo: containerView.topAnchor, constant: padding.top).isActive = !isHeader + containerView.bottomAnchor.constraint(equalTo: component.bottomAnchor, constant: padding.bottom).isActive = isHeader } } diff --git a/VDS/Components/Table/TableFlowLayout.swift b/VDS/Components/Table/TableFlowLayout.swift index 3513099d..2567f947 100644 --- a/VDS/Components/Table/TableFlowLayout.swift +++ b/VDS/Components/Table/TableFlowLayout.swift @@ -40,6 +40,9 @@ class MatrixFlowLayout : UICollectionViewFlowLayout { ///padding type to be set from Table component, which is used to calculate the size & position of the cell. var layoutPadding: Table.Padding = .standard + ///Striped status of Table, based on this status padding of leading attribute changes. + var striped: Bool = false + //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- @@ -77,7 +80,7 @@ class MatrixFlowLayout : UICollectionViewFlowLayout { let selectedItem = delegate.collectionView(collectionView, dataForItemAt: indexPath) ///Calculate the estimated height of the cell - let itemHeight = estimateHeightFor(item: selectedItem, with: itemWidth) + let itemHeight = estimateHeightFor(item: selectedItem, with: itemWidth, index: indexPath) layoutWidth += itemWidth @@ -108,8 +111,8 @@ class MatrixFlowLayout : UICollectionViewFlowLayout { } /// Fetches estimated height by calling the cell's component estimated height and adding padding - private func estimateHeightFor(item: TableItemModel, with width: CGFloat) -> CGFloat { - + private func estimateHeightFor(item: TableItemModel, with width: CGFloat, index: IndexPath) -> CGFloat { + let horizontalPadding = (index.row == 0 && !striped) ? (VDSLayout.space1X + layoutPadding.horizontalValue()) : (2 * layoutPadding.horizontalValue()) let itemWidth = width - layoutPadding.horizontalValue() - defaultLeadingPadding let maxSize = CGSize(width: itemWidth, height: CGFloat.greatestFiniteMagnitude) let estItemSize = item.component?.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) ?? CGSize(width: itemWidth, height: item.defaultHeight) From 076f4c082cb6aeb2b68be8137fc50c3471a4714c Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Thu, 18 Jul 2024 20:26:06 +0530 Subject: [PATCH 102/117] Digital ACT-191 ONEAPP-7013 story: removed border, background color to slot, removed unused file, updated slotAlignment method. --- VDS.xcodeproj/project.pbxproj | 4 - VDS/Components/Carousel/Carousel.swift | 80 +++++++------------ .../Carousel/CarouselSlotItemModel.swift | 23 ------ 3 files changed, 30 insertions(+), 77 deletions(-) delete mode 100644 VDS/Components/Carousel/CarouselSlotItemModel.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 93331c0e..59600512 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 18013CED2C355BF900907F18 /* CarouselSlotItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18013CEC2C355BF900907F18 /* CarouselSlotItemModel.swift */; }; 1808BEBC2BA41C3200129230 /* CarouselScrollbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */; }; 1832AC572BA0791D008AE476 /* BreadcrumbCellItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */; }; 1842B1DF2BECE28B0021AFCA /* CalendarDateViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1842B1DE2BECE28B0021AFCA /* CalendarDateViewCell.swift */; }; @@ -208,7 +207,6 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 18013CEC2C355BF900907F18 /* CarouselSlotItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselSlotItemModel.swift; sourceTree = ""; }; 1808BEBB2BA41C3200129230 /* CarouselScrollbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselScrollbar.swift; sourceTree = ""; }; 1808BEBF2BA456B700129230 /* CarouselScrollbarChangeLog.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = CarouselScrollbarChangeLog.txt; sourceTree = ""; }; 1832AC562BA0791D008AE476 /* BreadcrumbCellItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BreadcrumbCellItem.swift; sourceTree = ""; }; @@ -502,7 +500,6 @@ 18AE874F2C06FDA60075F181 /* Carousel.swift */, 18B9763E2C11BA4A009271DF /* CarouselPaginationModel.swift */, 18B42AC52C09D197008D6262 /* CarouselSlotAlignmentModel.swift */, - 18013CEC2C355BF900907F18 /* CarouselSlotItemModel.swift */, 18AE87532C06FE610075F181 /* CarouselChangeLog.txt */, ); path = Carousel; @@ -1406,7 +1403,6 @@ EA596ABF2A16B4F500300C4B /* Tabs.swift in Sources */, EAD062A72A3B67770015965D /* UIView+CALayer.swift in Sources */, EAD068942A560C13002E3A2D /* LoaderLaunchable.swift in Sources */, - 18013CED2C355BF900907F18 /* CarouselSlotItemModel.swift in Sources */, 18FEA1AD2BDD137500A56439 /* CalendarIndicatorModel.swift in Sources */, EA985BEC2968A91200F2FF2E /* TitleLockupTitleModel.swift in Sources */, 5FC35BE328D51405004EBEAC /* Button.swift in Sources */, diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 9a9b3251..0a6e71f9 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -123,7 +123,7 @@ open class Carousel: View { open var selectedIndex: Int? { didSet { setNeedsUpdate() } } /// If provided, will set the alignment for slot content when the slots has different heights. - open var slotAlignment: CarouselSlotAlignmentModel? = nil { didSet { setNeedsUpdate() } } + open var slotAlignment: CarouselSlotAlignmentModel? = .init(vertical: .top, horizontal: .left) { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Private Properties @@ -360,19 +360,17 @@ open class Carousel: View { } } - private func estimateHeightFor(item: CarouselSlotItemModel, with width: CGFloat) -> CGFloat { - let itemWidth = width + private func estimateHeightFor(component: UIView, with itemWidth: CGFloat) -> CGFloat { let maxSize = CGSize(width: itemWidth, height: CGFloat.greatestFiniteMagnitude) - let estItemSize = item.component?.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) ?? CGSize(width: itemWidth, height: item.defaultHeight) + let estItemSize = component.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel) return estItemSize.height } private func fetchCarouselHeight() -> CGFloat { var height = slotDefaultHeight if views.count > 0 { - for x in 0...views.count - 1 { - let item : CarouselSlotItemModel = .init(component: views[x]) - let estHeight = estimateHeightFor(item: item, with: minimumSlotWidth) + for index in 0...views.count - 1 { + let estHeight = estimateHeightFor(component: views[index], with: minimumSlotWidth) height = estHeight > height ? estHeight : height } } @@ -396,12 +394,11 @@ open class Carousel: View { // Add carousel items if views.count > 0 { var xPos = 0.0 - for x in 0...views.count - 1 { + for index in 0...views.count - 1 { // Add Carousel Slot let carouselSlot = View().with { $0.clipsToBounds = true - $0.backgroundColor = UIColor(red: CGFloat(216) / 255.0, green: CGFloat(218) / 255.0, blue: CGFloat(218) / 255.0, alpha: 1) } scrollView.addSubview(carouselSlot) scrollView.delegate = self @@ -412,35 +409,11 @@ open class Carousel: View { .pinLeading(xPos) .width(minimumSlotWidth) .height(slotHeight) - carouselSlot.layer.cornerRadius = 12.0 xPos = xPos + minimumSlotWidth + gutter.value - // Add received component - let item : CarouselSlotItemModel = .init(component: views[x]) - if let component = item.component { - carouselSlot.addSubview(component) - if slotAlignment != nil { - // If slotAlignment exist, use component's own sizes - component.widthAnchor.constraint(lessThanOrEqualTo: carouselSlot.widthAnchor).activate() - setSlotAlignment(contentView: component, parentView: carouselSlot) - } else { - // If no slotAlignment, should use full slot - component.pinToSuperView() - } - carouselSlot.backgroundColor = .clear - carouselSlot.layer.cornerRadius = 0 - if var surfacedView = component as? Surfaceable { - surfacedView.surface = surface - } - - // TO BE REMOVED AFTER VQA - borderWidth and borderColor below did set only for Testing purpose to VQA - carouselSlot.layer.borderWidth = 1.0 - carouselSlot.layer.borderColor = SurfaceColorConfiguration().with { - $0.lightColor = VDSColor.elementsPrimaryOnlight - $0.darkColor = VDSColor.elementsPrimaryOndark - }.getColor(self).cgColor - - } + let component = views[index] + carouselSlot.addSubview(component) + setSlotAlignment(contentView: component) } scrollView.contentSize = CGSize(width: xPos - gutter.value, height: slotHeight) } @@ -459,31 +432,38 @@ open class Carousel: View { } // Set slot alignment if provided. Used only when slot content have different heights or widths. - private func setSlotAlignment(contentView: UIView, parentView: View) { - parentView.backgroundColor = .clear + private func setSlotAlignment(contentView: UIView) { switch slotAlignment?.vertical { case .top: - contentView.topAnchor.constraint(equalTo: parentView.topAnchor).activate() - break + contentView + .pinTop() + .pinBottomLessThanOrEqualTo() case .middle: - contentView.centerYAnchor.constraint(equalTo: parentView.centerYAnchor).activate() - break + contentView + .pinTopGreaterThanOrEqualTo() + .pinBottomLessThanOrEqualTo() + .pinCenterY() case .bottom: - contentView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor).activate() - break + contentView + .pinTopGreaterThanOrEqualTo() + .pinBottom() default: break } switch slotAlignment?.horizontal { case .left: - contentView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).activate() - break + contentView + .pinLeading() + .pinTrailingLessThanOrEqualTo() case .center: - contentView.centerXAnchor.constraint(equalTo: parentView.centerXAnchor).activate() - break + contentView + .pinLeadingGreaterThanOrEqualTo() + .pinTrailingLessThanOrEqualTo() + .pinCenterX() case .right: - parentView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor).activate() - break + contentView + .pinLeadingGreaterThanOrEqualTo() + .pinTrailing() default: break } } diff --git a/VDS/Components/Carousel/CarouselSlotItemModel.swift b/VDS/Components/Carousel/CarouselSlotItemModel.swift deleted file mode 100644 index 3cb7240a..00000000 --- a/VDS/Components/Carousel/CarouselSlotItemModel.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// CarouselSlotItemModel.swift -// VDS -// -// Created by Kanamarlapudi, Vasavi on 30/06/24. -// - -import Foundation -import UIKit -import VDSCoreTokens - -/// A custom data type that holds the style and component for a slot of the 'Carousel' component. -public struct CarouselSlotItemModel { - - public let defaultHeight: CGFloat = 50.0 - - /// Component to be show on Carousel slot - public var component: UIView? - - public init(component: UIView? = nil) { - self.component = component - } -} From bf40368dc45aa68fa2248eeb01d7a134cd981004 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 18 Jul 2024 12:26:12 -0500 Subject: [PATCH 103/117] refactored layoutConstrainable for methods Signed-off-by: Matt Bruce --- VDS/Protocols/LayoutConstraintable.swift | 88 ++++++++++++++++++++---- 1 file changed, 75 insertions(+), 13 deletions(-) diff --git a/VDS/Protocols/LayoutConstraintable.swift b/VDS/Protocols/LayoutConstraintable.swift index ba08e3c2..bee145ad 100644 --- a/VDS/Protocols/LayoutConstraintable.swift +++ b/VDS/Protocols/LayoutConstraintable.swift @@ -705,11 +705,11 @@ extension LayoutConstraintable { } // Method to check if the view is pinned to its superview - public func isPinnedToSuperview() -> Bool { - isPinnedVerticallyToSuperview() && isPinnedHorizontallyToSuperview() + public func isPinnedEqual() -> Bool { + isPinnedEqualVertically() && isPinnedEqualHorizontally() } - public func horizontalPinnedSize() -> CGSize? { + public func horizontalPinnedWidth() -> CGFloat? { guard let view = self as? UIView, let superview = view.superview else { return nil } let constraints = superview.constraints @@ -735,44 +735,106 @@ extension LayoutConstraintable { if let leadingView = leadingObject as? UIView, let trailingView = trailingObject as? UIView { let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width - return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) + return trailingPosition - leadingPosition } else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingGuide = trailingObject as? UILayoutGuide { let leadingPosition = leadingGuide.layoutFrame.minX let trailingPosition = trailingGuide.layoutFrame.maxX - return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) + return trailingPosition - leadingPosition } else if let leadingView = leadingObject as? UIView, let trailingGuide = trailingObject as? UILayoutGuide { let leadingPosition = leadingView.convert(leadingView.bounds.origin, to: superview).x let trailingPosition = trailingGuide.layoutFrame.maxX - return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) + return trailingPosition - leadingPosition } else if let leadingGuide = leadingObject as? UILayoutGuide, let trailingView = trailingObject as? UIView { let leadingPosition = leadingGuide.layoutFrame.minX let trailingPosition = trailingView.convert(trailingView.bounds.origin, to: superview).x + trailingView.bounds.width - return CGSize(width: trailingPosition - leadingPosition, height: view.bounds.size.height) + return trailingPosition - leadingPosition } } else if let pinnedObject = leadingPinnedObject { if let view = pinnedObject as? UIView { - return view.bounds.size + return view.bounds.size.width } else if let layoutGuide = pinnedObject as? UILayoutGuide { - return layoutGuide.layoutFrame.size + return layoutGuide.layoutFrame.size.width } } else if let pinnedObject = trailingPinnedObject { if let view = pinnedObject as? UIView { - return view.bounds.size + return view.bounds.size.width } else if let layoutGuide = pinnedObject as? UILayoutGuide { - return layoutGuide.layoutFrame.size + return layoutGuide.layoutFrame.size.width } } return nil } + + public func verticalPinnedHeight() -> CGFloat? { + guard let view = self as? UIView, let superview = view.superview else { return nil } + let constraints = superview.constraints + + var topPinnedObject: AnyObject? + var bottomPinnedObject: AnyObject? + + for constraint in constraints { + if (constraint.firstItem === view && (constraint.firstAttribute == .top || constraint.firstAttribute == .topMargin)) { + topPinnedObject = constraint.secondItem as AnyObject? + } else if (constraint.secondItem === view && (constraint.secondAttribute == .top || constraint.secondAttribute == .topMargin)) { + topPinnedObject = constraint.firstItem as AnyObject? + } else if (constraint.firstItem === view && (constraint.firstAttribute == .bottom || constraint.firstAttribute == .bottomMargin)) { + bottomPinnedObject = constraint.secondItem as AnyObject? + } else if (constraint.secondItem === view && (constraint.secondAttribute == .bottom || constraint.secondAttribute == .bottomMargin)) { + bottomPinnedObject = constraint.firstItem as AnyObject? + } + } + + // Ensure both top and bottom pinned objects are identified + if let topObject = topPinnedObject, let bottomObject = bottomPinnedObject { + + // Calculate the size based on the pinned objects + if let topView = topObject as? UIView, let bottomView = bottomObject as? UIView { + let topPosition = topView.convert(topView.bounds.origin, to: superview).y + let bottomPosition = bottomView.convert(bottomView.bounds.origin, to: superview).y + bottomView.bounds.height + return bottomPosition - topPosition + + } else if let topGuide = topObject as? UILayoutGuide, let bottomGuide = bottomObject as? UILayoutGuide { + let topPosition = topGuide.layoutFrame.minY + let bottomPosition = bottomGuide.layoutFrame.maxY + return bottomPosition - topPosition + + } else if let topView = topObject as? UIView, let bottomGuide = bottomObject as? UILayoutGuide { + let topPosition = topView.convert(topView.bounds.origin, to: superview).y + let bottomPosition = bottomGuide.layoutFrame.maxY + return bottomPosition - topPosition + + } else if let topGuide = topObject as? UILayoutGuide, let bottomView = bottomObject as? UIView { + let topPosition = topGuide.layoutFrame.minY + let bottomPosition = bottomView.convert(bottomView.bounds.origin, to: superview).y + bottomView.bounds.height + return bottomPosition - topPosition + } + + } else if let pinnedObject = topPinnedObject { + if let view = pinnedObject as? UIView { + return view.bounds.size.height + } else if let layoutGuide = pinnedObject as? UILayoutGuide { + return layoutGuide.layoutFrame.size.height + } + + } else if let pinnedObject = bottomPinnedObject { + if let view = pinnedObject as? UIView { + return view.bounds.size.height + } else if let layoutGuide = pinnedObject as? UILayoutGuide { + return layoutGuide.layoutFrame.size.height + } + } + + return nil + } - public func isPinnedHorizontallyToSuperview() -> Bool { + public func isPinnedEqualHorizontally() -> Bool { guard let view = self as? UIView, let superview = view.superview else { return false } let constraints = superview.constraints var leadingPinned = false @@ -796,7 +858,7 @@ extension LayoutConstraintable { return leadingPinned && trailingPinned } - public func isPinnedVerticallyToSuperview() -> Bool { + public func isPinnedEqualVertically() -> Bool { guard let view = self as? UIView, let superview = view.superview else { return false } let constraints = superview.constraints var topPinned = false From b91017068c65bc6865039490d7111fe3821475c6 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 18 Jul 2024 15:34:03 -0500 Subject: [PATCH 104/117] Missed a few "fill" types and refactored to use a multiplier Signed-off-by: Matt Bruce --- .../TileContainer/TileContainer.swift | 248 ++++++++++-------- 1 file changed, 141 insertions(+), 107 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 6eeaf316..131a62f5 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -74,7 +74,7 @@ open class TileContainerBase: Control where Padding case custom(UIColor) private var reflectedValue: String { String(reflecting: self) } - + public static func == (lhs: Self, rhs: Self) -> Bool { lhs.reflectedValue == rhs.reflectedValue } @@ -86,7 +86,7 @@ open class TileContainerBase: Control where Padding case gradient(UIColor, UIColor) case none } - + /// Enum used to describe the aspect ratios used for this component. public enum AspectRatio: String, CaseIterable { case ratio1x1 = "1:1" @@ -109,7 +109,7 @@ open class TileContainerBase: Control where Padding $0.contentMode = .scaleAspectFill $0.clipsToBounds = true } - + open var containerView = View().with { $0.setContentHuggingPriority(.defaultLow, for: .horizontal) $0.setContentHuggingPriority(.defaultLow, for: .vertical) @@ -125,27 +125,27 @@ open class TileContainerBase: Control where Padding /// This is the container in which views will be pinned. open var contentView = View() - + /// This is the view used to show the high light color for a onClick. open var highlightView = View().with { $0.isUserInteractionEnabled = false } - + /// This controls the aspect ratio for the component. open var aspectRatio: AspectRatio = .ratio1x1 { didSet { setNeedsUpdate() } } - + /// Sets the background color for the component. open var color: BackgroundColor? { didSet { setNeedsUpdate() } } /// Sets the background effect for the component. open var backgroundEffect: BackgroundEffect = .none { didSet { setNeedsUpdate() } } - + /// Sets the inside padding for the component open var padding: PaddingType = PaddingType.defaultValue { didSet { setNeedsUpdate() } } /// Applies a background color if backgroundImage prop fails or has trouble loading. open var imageFallbackColor: Surface = .light { didSet { setNeedsUpdate() } } - + private var _width: CGFloat? /// Sets the width for the component. Accepts a pixel value. open var width: CGFloat? { @@ -159,7 +159,7 @@ open class TileContainerBase: Control where Padding setNeedsUpdate() } } - + private var _height: CGFloat? /// Sets the height for the component. Accepts a pixel value. open var height: CGFloat? { @@ -179,13 +179,14 @@ open class TileContainerBase: Control where Padding /// Determines if there is a drop shadow or not. open var showDropShadow: Bool = false { didSet { setNeedsUpdate() } } - + //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- internal var widthConstraint: NSLayoutConstraint? internal var heightConstraint: NSLayoutConstraint? - + internal var aspectRatioConstraint: NSLayoutConstraint? + //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- @@ -228,13 +229,13 @@ open class TileContainerBase: Control where Padding containerView.addSubview(backgroundImageView) backgroundImageView.pinToSuperView() - + containerView.addSubview(contentView) contentView.pinToSuperView() - + containerView.addSubview(highlightView) highlightView.pinToSuperView() - + widthConstraint = widthAnchor.constraint(equalToConstant: 0).deactivate() heightConstraint = heightAnchor.constraint(equalToConstant: 0).deactivate() @@ -266,7 +267,7 @@ open class TileContainerBase: Control where Padding setNeedsUpdate() } }.store(in: &subscribers) - + } /// Overriden to take the hit if there is an onClickSubscriber and the view is not a UIControl @@ -291,7 +292,7 @@ open class TileContainerBase: Control where Padding shouldUpdateView = true setNeedsUpdate() } - + /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() @@ -301,13 +302,14 @@ open class TileContainerBase: Control where Padding containerView.layer.borderColor = borderColorConfiguration.getColor(self).cgColor containerView.layer.borderWidth = showBorder ? VDSFormControls.borderWidth : 0 - + contentView.removeConstraints() contentView.pinToSuperView(.uniform(padding.value)) updateContainerView() + } - + open override var accessibilityElements: [Any]? { get { var items = [Any]() @@ -328,7 +330,7 @@ open class TileContainerBase: Control where Padding //append all children that are accessible items.append(contentsOf: elements) - + return items } set {} @@ -337,7 +339,7 @@ open class TileContainerBase: Control where Padding //-------------------------------------------------- // MARK: - Public Methods //-------------------------------------------------- - + /// This will place a view within the contentView of this component. public func addContentView(_ view: UIView, shouldPin: Bool = true) { view.removeFromSuperview() @@ -346,7 +348,7 @@ open class TileContainerBase: Control where Padding view.pinToSuperView() } } - + //-------------------------------------------------- // MARK: - Private Methods //-------------------------------------------------- @@ -379,55 +381,10 @@ open class TileContainerBase: Control where Padding containerView.backgroundColor = color.withAlphaComponent(alphaConfiguration) } } - - private func ratioSize(for width: CGFloat) -> CGSize { - var height: CGFloat = width - switch aspectRatio { - case .ratio1x1: - break; - case .ratio3x4: - height = (4 / 3) * width - case .ratio4x3: - height = (3 / 4) * width - case .ratio2x3: - height = (3 / 2) * width - case .ratio3x2: - height = (2 / 3) * width - case .ratio9x16: - height = (16 / 9) * width - case .ratio16x9: - height = (9 / 16) * width - case .ratio1x2: - height = (2 / 1) * width - case .ratio2x1: - height = (1 / 2) * width - - default: - break - } - - return CGSize(width: width, height: height) - } - - private func sizeContainerView(width: CGFloat? = nil, height: CGFloat? = nil) { - if let width, width > 0 { - widthConstraint?.constant = width - widthConstraint?.activate() - } - - if let height, height > 0 { - heightConstraint?.constant = height - heightConstraint?.activate() - } - } - private func updateContainerView() { applyBackgroundEffects() - - widthConstraint?.deactivate() - heightConstraint?.deactivate() - + if showDropShadow, surface == .light { containerView.addDropShadow(dropShadowConfiguration) } else { @@ -436,50 +393,100 @@ open class TileContainerBase: Control where Padding containerView.dropShadowLayers?.forEach { $0.frame = containerView.bounds } containerView.gradientLayers?.forEach { $0.frame = containerView.bounds } + + //sizing the container with constraints + + //Set local vars + var containerViewWidth: CGFloat? = width + let containerViewHeight: CGFloat? = height + let multiplier = aspectRatio.multiplier - if width != nil || height != nil { - var containerViewWidth: CGFloat? - var containerViewHeight: CGFloat? - //run logic to determine which to activate - if let width, aspectRatio == .none && height == nil{ - containerViewWidth = width - - } else if let height, aspectRatio == .none && width == nil{ - containerViewHeight = height - - } else if let height, let width { - containerViewWidth = width - containerViewHeight = height - - } else if let width { - let size = ratioSize(for: width) - containerViewWidth = size.width - containerViewHeight = size.height + //turn off the constraints + aspectRatioConstraint?.deactivate() + widthConstraint?.deactivate() + heightConstraint?.deactivate() - } else if let height { - let size = ratioSize(for: height) - containerViewWidth = size.width - containerViewHeight = size.height - } - sizeContainerView(width: containerViewWidth, height: containerViewHeight) - } else { - if let parentSize = horizontalPinnedSize() { - - var containerViewWidth: CGFloat? - var containerViewHeight: CGFloat? - - let size = ratioSize(for: parentSize.width) - if aspectRatio == .none { - containerViewWidth = size.width - } else { - containerViewWidth = size.width - containerViewHeight = size.height - } - - sizeContainerView(width: containerViewWidth, height: containerViewHeight) - } + //------------------------------------------------------------------------- + //Overriding Nil Width Rules + //------------------------------------------------------------------------- + //Rule 1: + //In the scenario where we only have a height but the multiplie is nil, we + //want to set the width with the parent's width which will more or less "fill" + //the container horizontally + //- height is set + //- width is not set + //- aspectRatio is not set + if let superviewWidth, superviewWidth > 0, + containerViewHeight != nil, + containerViewWidth == nil, + multiplier == nil { + containerViewWidth = superviewWidth + } + + //Rule 2: + //In the scenario where no width and height is set, want to set the width with the + //parent's width which will more or less "fill" the container horizontally + //- height is not set + //- width is not set + else if let superviewWidth, superviewWidth > 0, + containerViewWidth == nil, + containerViewHeight == nil { + containerViewWidth = superviewWidth + } + //------------------------------------------------------------------------- + + + //------------------------------------------------------------------------- + //Width + AspectRatio Constraint - Will exit out if set + //------------------------------------------------------------------------- + if let containerViewWidth, + let multiplier, + containerViewWidth > 0, + containerViewHeight == nil { + widthConstraint?.constant = containerViewWidth + widthConstraint?.activate() + aspectRatioConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: multiplier) + aspectRatioConstraint?.activate() + return + } + //------------------------------------------------------------------------- + //Height + AspectRatio Constraint - Will exit out if set + //------------------------------------------------------------------------- + else if let containerViewHeight, + let multiplier, + containerViewHeight > 0, + containerViewWidth == nil { + heightConstraint?.constant = containerViewHeight + heightConstraint?.activate() + aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: multiplier) + aspectRatioConstraint?.activate() + return + } + + //------------------------------------------------------------------------- + //Width Constraint + //------------------------------------------------------------------------- + if let containerViewWidth, + containerViewWidth > 0 { + widthConstraint?.constant = containerViewWidth + widthConstraint?.activate() + } + + //------------------------------------------------------------------------- + //Height Constraint + //------------------------------------------------------------------------- + if let containerViewHeight, + containerViewHeight > 0 { + heightConstraint?.constant = containerViewHeight + heightConstraint?.activate() } } + + /// This is the size of the superview's allowed space for this container first by constrained size which would include padding/inset values an + private var superviewWidth: CGFloat? { + horizontalPinnedWidth() ?? superview?.frame.size.width + } + } extension TileContainerBase { @@ -519,3 +526,30 @@ extension TileContainerBase { } } } + +extension TileContainerBase.AspectRatio { + var multiplier: CGFloat? { + switch self { + case .ratio1x1: + return 1 + case .ratio3x4: + return 4 / 3 + case .ratio4x3: + return 3 / 4 + case .ratio2x3: + return 3 / 2 + case .ratio3x2: + return 2 / 3 + case .ratio9x16: + return 16 / 9 + case .ratio16x9: + return 9 / 16 + case .ratio1x2: + return 2 / 1 + case .ratio2x1: + return 1 / 2 + case .none: + return nil + } + } +} From f1b5fd18c962af74af62f63afd2e8e99894af552 Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Fri, 19 Jul 2024 11:17:54 +0530 Subject: [PATCH 105/117] Digital ACT-191 ONEAPP-7013 story: removed unused code, updated Gutter pattern, renamed selectedIndex to groupIndex --- VDS/Components/Carousel/Carousel.swift | 48 ++++++++++++-------------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 0a6e71f9..0cc6d738 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -39,7 +39,9 @@ open class Carousel: View { } /// Enum used to describe the peek for this component. - /// This is how much a tile is partially visible. It is measured by the distance between the edge of the tile and the edge of the viewport or carousel container. A peek can appear on the left and/or right edge of the carousel container or viewport, depending on the carousel’s scroll position. + /// This is how much a tile is partially visible. It is measured by the distance between the edge of + /// the tile and the edge of the viewport or carousel container. A peek can appear on the left and/or + /// right edge of the carousel container or viewport, depending on the carousel’s scroll position. public enum Peek: String, CaseIterable { case standard, minimum, none } @@ -53,23 +55,19 @@ open class Carousel: View { public enum Horizontal: String, CaseIterable { case left, center, right } - - /// Enum used to describe the width of a fixed value or percentage of parent's width. - public enum Width { - case percentage(CGFloat) - case value(CGFloat) - } - + /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. - public enum Gutter: String, CaseIterable { - case twelvePX = "12px" - case twentyFourPX = "24px" + public enum Gutter: String, CaseIterable , DefaultValuing { + case gutter3X = "3X" + case gutter6X = "6X" - var value: CGFloat { + public static var defaultValue: Self { UIDevice.isIPad ? .gutter6X : .gutter3X } + + public var value: CGFloat { switch self { - case .twelvePX: + case .gutter3X: VDSLayout.space3X - case .twentyFourPX: + case .gutter6X: VDSLayout.space6X } } @@ -82,9 +80,10 @@ open class Carousel: View { open var views: [UIView] = [] { didSet { setNeedsUpdate() } } /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. - open var gutter: Gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX { didSet { setNeedsUpdate() } } + open var gutter: Gutter = Gutter.defaultValue { didSet { setNeedsUpdate() } } - /// The amount of slides visible in the carousel container at one time. The default value will be 3UP in tablet and 1UP in mobile. + /// The amount of slides visible in the carousel container at one time. + /// The default value will be 3UP in tablet and 1UP in mobile. open var layout: CarouselScrollbar.Layout = UIDevice.isIPad ? .threeUP : .oneUP { didSet { carouselScrollBar.position = 0 @@ -116,11 +115,12 @@ open class Carousel: View { /// The default value will be 12px in tablet and 8px in mobile. These values are the default in order to avoid overlapping content within the carousel. open var paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X { didSet { updatePaginationInset() } } - /// Options for user to configure the partially-visible tile in group. Setting peek to 'none' will display arrow navigation icons on mobile devices. + /// Options for user to configure the partially-visible tile in group. + /// Setting peek to 'none' will display arrow navigation icons on mobile devices. open var peek: Peek = .standard { didSet { setNeedsUpdate() } } /// The initial visible slide's index in the carousel. - open var selectedIndex: Int? { didSet { setNeedsUpdate() } } + open var groupIndex: Int? { didSet { setNeedsUpdate() } } /// If provided, will set the alignment for slot content when the slots has different heights. open var slotAlignment: CarouselSlotAlignmentModel? = .init(vertical: .top, horizontal: .left) { didSet { setNeedsUpdate() } } @@ -184,15 +184,13 @@ open class Carousel: View { private var containerViewHeightConstraint: NSLayoutConstraint? private var prevButtonLeadingConstraint: NSLayoutConstraint? private var nextButtonTrailingConstraint: NSLayoutConstraint? - private var containerLeadingConstraint: NSLayoutConstraint? // The scrollbar has top 5X space. So the expected top space is adjusted for tablet and mobile. let scrollbarTopSpace = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space1X - + var slotDefaultHeight = 50.0 var peekMinimum = 24.0 var minimumSlotWidth = 0.0 - var carouselScrollbarMinWidth = 96.0 //-------------------------------------------------- // MARK: - Lifecycle @@ -280,10 +278,10 @@ open class Carousel: View { updatePaginationControls() addCarouselSlots() - // If selectedIndex is received, the carousel should update its position. - if let selectedIndex { + // If groupIndex is received, the carousel should update its position. + if let groupIndex { let totalPos = totalPositions() - carouselScrollBar.position = selectedIndex >= totalPos ? totalPos : selectedIndex+1 + carouselScrollBar.position = groupIndex >= totalPos ? totalPos : groupIndex+1 } } @@ -295,7 +293,7 @@ open class Carousel: View { pagination = .init(kind: .lowContrast, floating: true) paginationDisplay = .none paginationInset = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X - gutter = UIDevice.isIPad ? .twentyFourPX : .twelvePX + gutter = UIDevice.isIPad ? .gutter6X : .gutter3X peek = .standard } From 938364535b4a7dcde81cbd74a9343ec44102457a Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Fri, 19 Jul 2024 14:42:00 +0530 Subject: [PATCH 106/117] Digital ACT-191 ONEAPP-7013 story: removed redundant code which causes scrolling issue --- VDS/Components/Carousel/Carousel.swift | 6 ------ 1 file changed, 6 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 0cc6d738..b7c0477a 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -304,12 +304,6 @@ open class Carousel: View { nextButton.onClick = { _ in self.nextButtonClick() } previousButton.onClick = { _ in self.previousButtonClick() } - /// Will be called when the scrubber position changes. - carouselScrollBar.onScrubberDrag = { [weak self] scrubberId in - guard let self else { return } - updateScrollPosition(position: scrubberId, callbackText:"onThumbPositionChange") - } - /// Will be called when the scrollbar thumb move forward. carouselScrollBar.onMoveForward = { [weak self] scrubberId in guard let self else { return } From 99fe7e22ac6462621e0bdaf4fd02f2689cca96f1 Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Fri, 19 Jul 2024 16:41:40 +0530 Subject: [PATCH 107/117] Digital ACT-191 ONEAPP-7013 story: fixed observed issue on content scroll while changing peek, gutter and layout. --- VDS/Components/Carousel/Carousel.swift | 44 +++++++++----------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index b7c0477a..3a754ae8 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -120,7 +120,7 @@ open class Carousel: View { open var peek: Peek = .standard { didSet { setNeedsUpdate() } } /// The initial visible slide's index in the carousel. - open var groupIndex: Int? { didSet { setNeedsUpdate() } } + open var groupIndex: Int = 0 { didSet { setNeedsUpdate() } } /// If provided, will set the alignment for slot content when the slots has different heights. open var slotAlignment: CarouselSlotAlignmentModel? = .init(vertical: .top, horizontal: .left) { didSet { setNeedsUpdate() } } @@ -277,12 +277,6 @@ open class Carousel: View { updatePaginationControls() addCarouselSlots() - - // If groupIndex is received, the carousel should update its position. - if let groupIndex { - let totalPos = totalPositions() - carouselScrollBar.position = groupIndex >= totalPos ? totalPos : groupIndex+1 - } } /// Resets to default settings. @@ -304,6 +298,12 @@ open class Carousel: View { nextButton.onClick = { _ in self.nextButtonClick() } previousButton.onClick = { _ in self.previousButtonClick() } + /// Will be called when the scrubber position changes. + carouselScrollBar.onScrubberDrag = { [weak self] scrubberId in + guard let self else { return } + updateScrollPosition(position: scrubberId, callbackText:"onThumbPositionChange") + } + /// Will be called when the scrollbar thumb move forward. carouselScrollBar.onMoveForward = { [weak self] scrubberId in guard let self else { return } @@ -484,7 +484,7 @@ open class Carousel: View { break } } - minimumSlotWidth = minimumSlotWidth / CGFloat(layout.value) + minimumSlotWidth = ceil(minimumSlotWidth / CGFloat(layout.value)) } private func nextButtonClick() { @@ -533,27 +533,18 @@ open class Carousel: View { let isScrollbarSuppressed = views.count > 0 && layout.value == views.count let isPeekMinimumOnTablet = UIDevice.isIPad && peek == .minimum if !isScrollbarSuppressed { - let subpart = minimumSlotWidth + gutter.value - let xPosition = CGFloat( Float(position-1) * Float(layout.value) * Float(subpart)) - switch peek { - case .standard: - if UIDevice.isIPad { - xPos = xPosition - (minimumSlotWidth/(CGFloat(layout.value) + 3))/2 - } else if layout == .oneUP { - xPos = xPosition - gutter.value - (minimumSlotWidth/4)/2 - } - case .minimum: - xPos = isPeekMinimumOnTablet ? xPosition : xPosition - peekMinimum - case .none: - xPos = xPosition - } + let slotWidthWithGutter = minimumSlotWidth + gutter.value + let xPosition = CGFloat( Float(position-1) * Float(layout.value) * Float(slotWidthWithGutter)) + let peekWidth = (containerView.frame.size.width - gutter.value - (Double(layout.value) * (minimumSlotWidth + gutter.value)))/2 + xPos = (peek == .none || isPeekMinimumOnTablet) ? xPosition : xPosition - gutter.value - peekWidth } } carouselScrollBar.scrubberId = position+1 let yPos = scrollView.contentOffset.y - scrollView.setContentOffset(CGPoint(x: xPos, y: yPos), animated: true) + scrollView.setContentOffset(CGPoint(x: xPos, y: yPos), animated: false) showPaginationControls() - onChangePublisher.send(position-1) + groupIndex = position-1 + onChangePublisher.send(groupIndex) } } @@ -571,9 +562,4 @@ extension Carousel: UIScrollViewDelegate { updateScrollbarPosition(targetContentOffsetXPos: targetContentOffset.pointee.x) } - public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { - var visibleRect = CGRect() - visibleRect.origin = scrollView.contentOffset - updateScrollbarPosition(targetContentOffsetXPos: visibleRect.origin.x) - } } From 7c431974ac02324bce273bb965b351a52e12497f Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Fri, 19 Jul 2024 18:04:50 +0530 Subject: [PATCH 108/117] minor changes --- VDS/Components/Carousel/Carousel.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 3a754ae8..e05b94ea 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -56,7 +56,7 @@ open class Carousel: View { case left, center, right } - /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. + /// Space between each tile. The default value will be 6X in tablet and 3X in mobile. public enum Gutter: String, CaseIterable , DefaultValuing { case gutter3X = "3X" case gutter6X = "6X" @@ -79,7 +79,7 @@ open class Carousel: View { /// views used to render view in the carousel slots. open var views: [UIView] = [] { didSet { setNeedsUpdate() } } - /// Space between each tile. The default value will be 24px (6X) in tablet and 12px (3X) in mobile. + /// Space between each tile. The default value will be 6X in tablet and 3X in mobile. open var gutter: Gutter = Gutter.defaultValue { didSet { setNeedsUpdate() } } /// The amount of slides visible in the carousel container at one time. @@ -112,7 +112,7 @@ open class Carousel: View { open var paginationDisplay: PaginationDisplay = .none { didSet {setNeedsUpdate() } } /// If provided, will apply margin to pagination arrows. Can be set to either positive or negative values. - /// The default value will be 12px in tablet and 8px in mobile. These values are the default in order to avoid overlapping content within the carousel. + /// The default value will be 3X in tablet and 2X in mobile. These values are the default in order to avoid overlapping content within the carousel. open var paginationInset: CGFloat = UIDevice.isIPad ? VDSLayout.space3X : VDSLayout.space2X { didSet { updatePaginationInset() } } /// Options for user to configure the partially-visible tile in group. From afc4d71f38104afaf89985db11240830ded45f5a Mon Sep 17 00:00:00 2001 From: Vasavi Kanamarlapudi Date: Fri, 19 Jul 2024 20:54:03 +0530 Subject: [PATCH 109/117] updated scroll animation to true --- VDS/Components/Carousel/Carousel.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index e05b94ea..3e8aed2d 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -541,7 +541,7 @@ open class Carousel: View { } carouselScrollBar.scrubberId = position+1 let yPos = scrollView.contentOffset.y - scrollView.setContentOffset(CGPoint(x: xPos, y: yPos), animated: false) + scrollView.setContentOffset(CGPoint(x: xPos, y: yPos), animated: true) showPaginationControls() groupIndex = position-1 onChangePublisher.send(groupIndex) From e02ea9712e2ddd8b855876a9d02010f15935279b Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 19 Jul 2024 12:28:14 -0500 Subject: [PATCH 110/117] put last part in else Signed-off-by: Matt Bruce --- .../TileContainer/TileContainer.swift | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 131a62f5..0852bca6 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -447,7 +447,7 @@ open class TileContainerBase: Control where Padding widthConstraint?.activate() aspectRatioConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: multiplier) aspectRatioConstraint?.activate() - return + } //------------------------------------------------------------------------- //Height + AspectRatio Constraint - Will exit out if set @@ -460,25 +460,26 @@ open class TileContainerBase: Control where Padding heightConstraint?.activate() aspectRatioConstraint = widthAnchor.constraint(equalTo: heightAnchor, multiplier: multiplier) aspectRatioConstraint?.activate() - return - } - //------------------------------------------------------------------------- - //Width Constraint - //------------------------------------------------------------------------- - if let containerViewWidth, - containerViewWidth > 0 { - widthConstraint?.constant = containerViewWidth - widthConstraint?.activate() - } - - //------------------------------------------------------------------------- - //Height Constraint - //------------------------------------------------------------------------- - if let containerViewHeight, - containerViewHeight > 0 { - heightConstraint?.constant = containerViewHeight - heightConstraint?.activate() + } else { + + //------------------------------------------------------------------------- + //Width Constraint + //------------------------------------------------------------------------- + if let containerViewWidth, + containerViewWidth > 0 { + widthConstraint?.constant = containerViewWidth + widthConstraint?.activate() + } + + //------------------------------------------------------------------------- + //Height Constraint + //------------------------------------------------------------------------- + if let containerViewHeight, + containerViewHeight > 0 { + heightConstraint?.constant = containerViewHeight + heightConstraint?.activate() + } } } From 00f73a503c03ed40c3684410dd232b23ce15015c Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 19 Jul 2024 13:13:21 -0500 Subject: [PATCH 111/117] CXTDT-581800 - Date Picker - Selected Error state icon Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 18 +++++++++++++++++- VDS/SupportingFiles/ReleaseNotes.txt | 3 +++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 83450403..3a41780e 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -27,6 +27,19 @@ open class DatePicker: EntryFieldBase { /// A callback when the selected option changes. Passes parameters (option). open var onDateSelected: ((Date, DatePicker) -> Void)? + /// Override UIControl state to add the .error state if showError is true. + open override var state: UIControl.State { + get { + var state = super.state + if isEnabled { + if isCalendarShowing { + state.insert(.focused) + } + } + return state + } + } + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- @@ -35,8 +48,9 @@ open class DatePicker: EntryFieldBase { true } } - + internal override var responder: UIResponder? { hiddenView } + internal var isCalendarShowing: Bool = false { didSet { setNeedsUpdate() } } internal var hiddenView = Responder().with { $0.width(0) } internal var minWidthDefault = 186.0 internal var bottomStackView: UIStackView = { @@ -315,6 +329,7 @@ extension DatePicker { } } + isCalendarShowing = true } private func hidePopoverView() { @@ -346,6 +361,7 @@ extension DatePicker { UIAccessibility.post(notification: .layoutChanged, argument: containerView) } } + isCalendarShowing = false } private func calculatePopoverPosition(relativeTo sourceView: UIView, in parentView: UIView, size: CGSize, with spacing: CGFloat) -> CGPoint? { diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 973e8ed8..bc182364 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,7 +1,10 @@ 1.0.71 ---------------- +- CXTDT-581800 - DatePicker - Selected Error state icon - CXTDT-581803 - DatePicker - Calendar does not switch to Dark Mode - CXTDT-584278 – InputField - Accessibility +- CXTDT-586375 - Table - Issue With Stripe +- CXTDT-577463 - InputField - Accessibility - #7 1.0.70 ---------------- From 54a2a63ba41633e753c9bc7d9f93c1301b9edbd8 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 19 Jul 2024 13:17:49 -0500 Subject: [PATCH 112/117] CXTDT-581801 - DatePicker - border disappears for on dark focus state Signed-off-by: Matt Bruce --- VDS/Components/TextFields/EntryFieldBase.swift | 4 ++-- VDS/SupportingFiles/ReleaseNotes.txt | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 78c1077a..fbae1563 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -137,8 +137,8 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { internal var borderColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSFormControlsColor.borderOnlight, VDSFormControlsColor.borderOndark, forState: .normal) - $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forState: .focused) - $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOnlight, forState: [.focused, .error]) + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .focused) + $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: [.focused, .error]) $0.setSurfaceColors(VDSColor.interactiveDisabledOnlight, VDSColor.interactiveDisabledOndark, forState: .disabled) $0.setSurfaceColors(VDSColor.feedbackErrorOnlight, VDSColor.feedbackErrorOndark, forState: .error) $0.setSurfaceColors(VDSFormControlsColor.borderReadonlyOnlight, VDSFormControlsColor.borderReadonlyOndark, forState: .readonly) diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index bc182364..6450add8 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -1,6 +1,7 @@ 1.0.71 ---------------- - CXTDT-581800 - DatePicker - Selected Error state icon +- CXTDT-581801 - DatePicker - border disappears for on dark focus state - CXTDT-581803 - DatePicker - Calendar does not switch to Dark Mode - CXTDT-584278 – InputField - Accessibility - CXTDT-586375 - Table - Issue With Stripe From a253e2efc86655ae2ef5e636b484f9e41e6c7a77 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 19 Jul 2024 13:28:08 -0500 Subject: [PATCH 113/117] =?UTF-8?q?CXTDT-565796=20-=20DropdownSelect=20?= =?UTF-8?q?=E2=80=93=20Removed=20the=20"Type"=20from=20the=20VoiceOver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Matt Bruce --- VDS/Components/TextFields/EntryFieldBase.swift | 4 +--- VDS/SupportingFiles/ReleaseNotes.txt | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index fbae1563..571783bc 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -331,9 +331,7 @@ open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { if let errorText, showError { accessibilityLabels.append("error, \(errorText)") } - - accessibilityLabels.append("\(Self.self)") - + return accessibilityLabels.joined(separator: ", ") } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 6450add8..69b82096 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -6,6 +6,7 @@ - CXTDT-584278 – InputField - Accessibility - CXTDT-586375 - Table - Issue With Stripe - CXTDT-577463 - InputField - Accessibility - #7 +- CXTDT-565796 - DropdownSelect – Removed the "Type" from the VoiceOver 1.0.70 ---------------- From 7cabfdb78cb9cf8260a2796d23c339bcf0b2f8e3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 19 Jul 2024 13:29:14 -0500 Subject: [PATCH 114/117] updated version to 71 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 59600512..230ce227 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -1557,7 +1557,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 70; + CURRENT_PROJECT_VERSION = 71; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1595,7 +1595,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 70; + CURRENT_PROJECT_VERSION = 71; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; From 2a1b9380c82d192ef3278c79e69be0e4042c4f73 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Fri, 19 Jul 2024 13:42:57 -0500 Subject: [PATCH 115/117] updated comments Signed-off-by: Matt Bruce --- VDS/Components/TileContainer/TileContainer.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 0852bca6..cbe68e3a 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -437,7 +437,7 @@ open class TileContainerBase: Control where Padding //------------------------------------------------------------------------- - //Width + AspectRatio Constraint - Will exit out if set + //Width + AspectRatio Constraint //------------------------------------------------------------------------- if let containerViewWidth, let multiplier, @@ -450,7 +450,7 @@ open class TileContainerBase: Control where Padding } //------------------------------------------------------------------------- - //Height + AspectRatio Constraint - Will exit out if set + //Height + AspectRatio Constraint //------------------------------------------------------------------------- else if let containerViewHeight, let multiplier, From 781df9ff1a6d7da8ed09dde37771038e3842e8c3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 25 Jul 2024 10:45:20 -0500 Subject: [PATCH 116/117] added objcMembers Signed-off-by: Matt Bruce --- VDS/BaseClasses/Control.swift | 1 + VDS/BaseClasses/Selector/SelectorBase.swift | 2 ++ VDS/BaseClasses/View.swift | 1 + VDS/Classes/AlertViewController.swift | 2 ++ VDS/Classes/ClearPopoverViewController.swift | 2 ++ 5 files changed, 8 insertions(+) diff --git a/VDS/BaseClasses/Control.swift b/VDS/BaseClasses/Control.swift index 7aa08717..472f0635 100644 --- a/VDS/BaseClasses/Control.swift +++ b/VDS/BaseClasses/Control.swift @@ -10,6 +10,7 @@ import UIKit import Combine /// Base Class use to build Controls. +@objcMembers @objc(VDSControl) open class Control: UIControl, ViewProtocol, UserInfoable, Clickable { //-------------------------------------------------- diff --git a/VDS/BaseClasses/Selector/SelectorBase.swift b/VDS/BaseClasses/Selector/SelectorBase.swift index fb8d771e..1e2f5fba 100644 --- a/VDS/BaseClasses/Selector/SelectorBase.swift +++ b/VDS/BaseClasses/Selector/SelectorBase.swift @@ -28,6 +28,8 @@ public protocol SelectorControlable: Control, Changeable { } /// Base Class used to build out a Selector control. +@objcMembers +@objc(VDSSelectorBase) open class SelectorBase: Control, SelectorControlable { //-------------------------------------------------- // MARK: - Initializers diff --git a/VDS/BaseClasses/View.swift b/VDS/BaseClasses/View.swift index 7e88df8e..88996ba6 100644 --- a/VDS/BaseClasses/View.swift +++ b/VDS/BaseClasses/View.swift @@ -10,6 +10,7 @@ import UIKit import Combine /// Base Class used to build Views. +@objcMembers @objc(VDSView) open class View: UIView, ViewProtocol, UserInfoable { diff --git a/VDS/Classes/AlertViewController.swift b/VDS/Classes/AlertViewController.swift index 30a5d5c6..5472fab8 100644 --- a/VDS/Classes/AlertViewController.swift +++ b/VDS/Classes/AlertViewController.swift @@ -10,6 +10,8 @@ import UIKit import Combine import VDSCoreTokens +@objcMembers +@objc(VDSAlertViewController) open class AlertViewController: UIViewController, Surfaceable { /// Set of Subscribers for any Publishers for this Control. diff --git a/VDS/Classes/ClearPopoverViewController.swift b/VDS/Classes/ClearPopoverViewController.swift index 6f9bcb67..d2137b62 100644 --- a/VDS/Classes/ClearPopoverViewController.swift +++ b/VDS/Classes/ClearPopoverViewController.swift @@ -8,6 +8,8 @@ import Foundation import UIKit +@objcMembers +@objc(VDSClearPopoverViewController) open class ClearPopoverViewController: UIViewController, UIPopoverPresentationControllerDelegate { /// The view to be inserted inside the popover From 6bf3e19ebe23275f4207282833d12b4ee23de3d7 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 25 Jul 2024 10:53:26 -0500 Subject: [PATCH 117/117] added @objcMembers to all classes Signed-off-by: Matt Bruce --- VDS/Components/Badge/Badge.swift | 1 + .../BadgeIndicator/BadgeIndicator.swift | 1 + .../Breadcrumbs/BreadcrumbItem.swift | 1 + VDS/Components/Breadcrumbs/Breadcrumbs.swift | 1 + VDS/Components/Buttons/Button/Button.swift | 1 + VDS/Components/Buttons/ButtonBase.swift | 1 + .../Buttons/ButtonGroup/ButtonGroup.swift | 1 + .../Buttons/TextLink/TextLink.swift | 1 + .../Buttons/TextLinkCaret/TextLinkCaret.swift | 1 + VDS/Components/Calendar/Calendar.swift | 1 + VDS/Components/Carousel/Carousel.swift | 1 + .../CarouselScrollbar/CarouselScrollbar.swift | 1 + VDS/Components/Checkbox/Checkbox.swift | 1 + VDS/Components/Checkbox/CheckboxGroup.swift | 1 + VDS/Components/Checkbox/CheckboxItem.swift | 29 +++++++++++++++++++ VDS/Components/DatePicker/DatePicker.swift | 1 + .../DropdownSelect/DropdownSelect.swift | 1 + VDS/Components/Icon/Icon.swift | 1 + VDS/Components/Label/Label.swift | 1 + VDS/Components/Line/Line.swift | 1 + VDS/Components/Loader/Loader.swift | 1 + .../Loader/LoaderViewController.swift | 2 ++ .../Notification/Notification.swift | 1 + VDS/Components/Pagination/Pagination.swift | 1 + .../Pagination/PaginationButton.swift | 1 + VDS/Components/RadioBox/RadioBoxGroup.swift | 1 + VDS/Components/RadioBox/RadioBoxItem.swift | 1 + VDS/Components/RadioButton/RadioButton.swift | 1 + .../RadioButton/RadioButtonGroup.swift | 1 + .../RadioButton/RadioButtonItem.swift | 1 + VDS/Components/Table/Table.swift | 1 + VDS/Components/Tabs/Tab.swift | 2 +- VDS/Components/Tabs/Tabs.swift | 1 + VDS/Components/Tabs/TabsContainer.swift | 1 + .../TextFields/EntryFieldBase.swift | 1 + .../TextFields/InputField/InputField.swift | 19 ++++++++++++ .../TextFields/InputField/TextField.swift | 1 + .../TextFields/TextArea/TextArea.swift | 1 + .../TextFields/TextArea/TextView.swift | 1 + .../TileContainer/TileContainer.swift | 1 + VDS/Components/Tilelet/Tilelet.swift | 1 + VDS/Components/TitleLockup/TitleLockup.swift | 1 + VDS/Components/Toggle/Toggle.swift | 1 + VDS/Components/Toggle/ToggleView.swift | 1 + VDS/Components/Tooltip/Tooltip.swift | 1 + .../Tooltip/TooltipAlertViewController.swift | 2 ++ VDS/Components/Tooltip/TooltipDialog.swift | 2 ++ 47 files changed, 96 insertions(+), 1 deletion(-) diff --git a/VDS/Components/Badge/Badge.swift b/VDS/Components/Badge/Badge.swift index 5753428e..07477f5f 100644 --- a/VDS/Components/Badge/Badge.swift +++ b/VDS/Components/Badge/Badge.swift @@ -15,6 +15,7 @@ import Combine /// If you are using AutoLayoutConstraints you have a combination of Leading/Left and Trailing/Right NSLayoutConstraints, /// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges /// to its parent this object will stretch to the parent's width. +@objcMembers @objc(VDSBadge) open class Badge: View { diff --git a/VDS/Components/BadgeIndicator/BadgeIndicator.swift b/VDS/Components/BadgeIndicator/BadgeIndicator.swift index 60025390..13a6cb21 100644 --- a/VDS/Components/BadgeIndicator/BadgeIndicator.swift +++ b/VDS/Components/BadgeIndicator/BadgeIndicator.swift @@ -11,6 +11,7 @@ import VDSCoreTokens import Combine /// A badge indicator is a visual label used to convey status or highlight supplemental information. +@objcMembers @objc(VDSBadgeIndicator) open class BadgeIndicator: View { diff --git a/VDS/Components/Breadcrumbs/BreadcrumbItem.swift b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift index bf6d4ed1..66f62989 100644 --- a/VDS/Components/Breadcrumbs/BreadcrumbItem.swift +++ b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift @@ -13,6 +13,7 @@ import Combine /// A Breadcrumb Item contains href(link) and selected flag. /// Breadcrumb links to its respective page if it is not disabled. /// Breadcrumb contains text with a separator by default, highlights text in bold without a separator if selected. +@objcMembers @objc (VDSBreadcrumbItem) open class BreadcrumbItem: ButtonBase { diff --git a/VDS/Components/Breadcrumbs/Breadcrumbs.swift b/VDS/Components/Breadcrumbs/Breadcrumbs.swift index b5256497..5a2de7d0 100644 --- a/VDS/Components/Breadcrumbs/Breadcrumbs.swift +++ b/VDS/Components/Breadcrumbs/Breadcrumbs.swift @@ -13,6 +13,7 @@ import Combine /// A Breadcrumbs contains BreadcrumbItems. /// It contains Breadcrumb Item Default, Breadcrumb Item Selected, Separator. /// Breadcrumbs are secondary navigation that use a hierarchy of internal links to tell customers where they are in an experience. Each breadcrumb links to its respective page, except for that of current page. +@objcMembers @objc(VDSBreadcrumbs) open class Breadcrumbs: View { diff --git a/VDS/Components/Buttons/Button/Button.swift b/VDS/Components/Buttons/Button/Button.swift index f4147f54..0b9d13f1 100644 --- a/VDS/Components/Buttons/Button/Button.swift +++ b/VDS/Components/Buttons/Button/Button.swift @@ -15,6 +15,7 @@ import Combine /// If you are using AutoLayoutConstraints you have a combination of Leading/Left and Trailing/Right NSLayoutConstraints, /// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges /// to its parent this object will stretch to the parent's width. +@objcMembers @objc(VDSButton) open class Button: ButtonBase, Useable { diff --git a/VDS/Components/Buttons/ButtonBase.swift b/VDS/Components/Buttons/ButtonBase.swift index eebf3a68..c764b89b 100644 --- a/VDS/Components/Buttons/ButtonBase.swift +++ b/VDS/Components/Buttons/ButtonBase.swift @@ -11,6 +11,7 @@ import VDSCoreTokens import Combine /// Base class used for UIButton type classes. +@objcMembers @objc(VDSButtonBase) open class ButtonBase: UIButton, ViewProtocol, UserInfoable, Clickable { diff --git a/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift b/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift index 124a7fba..8b662070 100644 --- a/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift +++ b/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift @@ -11,6 +11,7 @@ import VDSCoreTokens import Combine /// A button group contains combinations of related CTAs including ``Button``, ``TextLink``, and ``TextLinkCaret``. This group component controls a combination's orientation, spacing, size and allowable size pairings. +@objcMembers @objc(VDSButtonGroup) open class ButtonGroup: View { diff --git a/VDS/Components/Buttons/TextLink/TextLink.swift b/VDS/Components/Buttons/TextLink/TextLink.swift index f77dfa79..70f2840b 100644 --- a/VDS/Components/Buttons/TextLink/TextLink.swift +++ b/VDS/Components/Buttons/TextLink/TextLink.swift @@ -16,6 +16,7 @@ import Combine /// If you are using AutoLayoutConstraints you have a combination of Leading/Left and Trailing/Right NSLayoutConstraints, /// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges /// to its parent this object will stretch to the parent's width. +@objcMembers @objc(VDSTextLink) open class TextLink: ButtonBase { //-------------------------------------------------- diff --git a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift index d1522d02..f8c86a44 100644 --- a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift +++ b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift @@ -16,6 +16,7 @@ import Combine /// If you are using AutoLayoutConstraints you have a combination of Leading/Left and Trailing/Right NSLayoutConstraints, /// you need to ensure that one of these Horizontal Contraints is not constraint of "equatTo". If you are to pin the left/right edges /// to its parent this object will stretch to the parent's width. +@objcMembers @objc(VDSTextLinkCaret) open class TextLinkCaret: ButtonBase { //-------------------------------------------------- diff --git a/VDS/Components/Calendar/Calendar.swift b/VDS/Components/Calendar/Calendar.swift index cd1c783b..bed772d8 100644 --- a/VDS/Components/Calendar/Calendar.swift +++ b/VDS/Components/Calendar/Calendar.swift @@ -11,6 +11,7 @@ import VDSCoreTokens import Combine /// A calendar is a monthly view that lets customers select a single date. +@objcMembers @objc(VDSCalendar) open class CalendarBase: Control, Changeable { diff --git a/VDS/Components/Carousel/Carousel.swift b/VDS/Components/Carousel/Carousel.swift index 3e8aed2d..d1624253 100644 --- a/VDS/Components/Carousel/Carousel.swift +++ b/VDS/Components/Carousel/Carousel.swift @@ -12,6 +12,7 @@ import Combine /// A carousel is a collection of related content in a row that a customer can navigate through horizontally. /// Use this component to show content that is supplementary, not essential for task completion. +@objcMembers @objc(VDSCarousel) open class Carousel: View { diff --git a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift index 1e05d875..f171c395 100644 --- a/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift +++ b/VDS/Components/CarouselScrollbar/CarouselScrollbar.swift @@ -12,6 +12,7 @@ import Combine /// A carousel scrollbar is a control that allows to navigate between items in a carousel. /// It's also a status indicator that conveys the relative amount of content in a carousel and a location within it. +@objcMembers @objc(VDSCarouselScrollbar) open class CarouselScrollbar: View { diff --git a/VDS/Components/Checkbox/Checkbox.swift b/VDS/Components/Checkbox/Checkbox.swift index 3369ebcb..5b2afdc5 100644 --- a/VDS/Components/Checkbox/Checkbox.swift +++ b/VDS/Components/Checkbox/Checkbox.swift @@ -12,6 +12,7 @@ import VDSCoreTokens /// Checkboxes are a multi-select component through which a customer indicates a choice. This is also used within /// ``CheckboxItem`` and ``CheckboxGroup`` +@objcMembers @objc(VDSCheckbox) open class Checkbox: SelectorBase { diff --git a/VDS/Components/Checkbox/CheckboxGroup.swift b/VDS/Components/Checkbox/CheckboxGroup.swift index b450d4d5..1431d9dc 100644 --- a/VDS/Components/Checkbox/CheckboxGroup.swift +++ b/VDS/Components/Checkbox/CheckboxGroup.swift @@ -12,6 +12,7 @@ import VDSCoreTokens /// When the choice has multiple options, use a checkbox group. For example, use a checkbox group when /// asking a customer which attributes they would like to filter their search by. This uses ``CheckboxItem`` /// to allow user selection. +@objcMembers @objc(VDSCheckboxGroup) open class CheckboxGroup: SelectorGroupBase, SelectorGroupMultiSelect { diff --git a/VDS/Components/Checkbox/CheckboxItem.swift b/VDS/Components/Checkbox/CheckboxItem.swift index 21943636..c115fe79 100644 --- a/VDS/Components/Checkbox/CheckboxItem.swift +++ b/VDS/Components/Checkbox/CheckboxItem.swift @@ -9,6 +9,7 @@ import Foundation import UIKit /// Checkboxes are a multi-select component through which a customer indicates a choice. If a binary choice, the component is a checkbox. If the choice has multiple options, the component is a ``CheckboxGroup``. +@objcMembers @objc(VDSCheckboxItem) open class CheckboxItem: SelectorItemBase { @@ -47,6 +48,13 @@ open class CheckboxItem: SelectorItemBase { isSelected.toggle() sendActions(for: .valueChanged) } + + open override func setup() { + super.setup() + let foo = ConcreteClass(customView: Checkbox()) + + print(foo.customView.isAnimated) + } /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { @@ -54,3 +62,24 @@ open class CheckboxItem: SelectorItemBase { super.updateView() } } + + +@objcMembers +open class GenericClass: NSObject { + public var customView: T + + public init(customView: T = T()) { + self.customView = customView + } +} + +@objcMembers +@objc(VDSConcreteClass) +open class ConcreteClass: GenericClass { + +} + +@objcMembers +@objc(VDSConcreteCheckboxClass) +open class ConcreteCheckboxClass: ConcreteClass {} + diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index 3a41780e..df7cc7c9 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -4,6 +4,7 @@ import VDSCoreTokens import Combine /// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection. +@objcMembers @objc(VDSDatePicker) open class DatePicker: EntryFieldBase { //-------------------------------------------------- diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index 832349d2..947210f4 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -11,6 +11,7 @@ import VDSCoreTokens import Combine /// A dropdown select is an expandable menu of predefined options that allows a customer to make a single selection. +@objcMembers @objc(VDSDropdownSelect) open class DropdownSelect: EntryFieldBase { //-------------------------------------------------- diff --git a/VDS/Components/Icon/Icon.swift b/VDS/Components/Icon/Icon.swift index 140f7b87..fd5c5fb2 100644 --- a/VDS/Components/Icon/Icon.swift +++ b/VDS/Components/Icon/Icon.swift @@ -13,6 +13,7 @@ import Combine /// An icon is a graphical element that conveys information at a glance. It helps orient /// a customer, explain functionality and draw attention to interactive elements. Icons /// should have a functional purpose and should never be used for decoration. +@objcMembers @objc(VDSIcon) open class Icon: View { diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index e408953b..f420be7d 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -12,6 +12,7 @@ import Combine /// Label is a standard view used to draw text with applying Typography through ``TextStyle`` as well /// as other attributes using any implemetation of ``LabelAttributeModel``. +@objcMembers @objc(VDSLabel) open class Label: UILabel, ViewProtocol, UserInfoable { diff --git a/VDS/Components/Line/Line.swift b/VDS/Components/Line/Line.swift index e6edae70..627210f9 100644 --- a/VDS/Components/Line/Line.swift +++ b/VDS/Components/Line/Line.swift @@ -10,6 +10,7 @@ import UIKit import VDSCoreTokens /// A line visually separates content sections or elements in lists, tables and layouts to indicate content hierarchy. +@objcMembers @objc(VDSLine) open class Line: View { diff --git a/VDS/Components/Loader/Loader.swift b/VDS/Components/Loader/Loader.swift index 13ceeb43..5b7f1b59 100644 --- a/VDS/Components/Loader/Loader.swift +++ b/VDS/Components/Loader/Loader.swift @@ -11,6 +11,7 @@ import VDSCoreTokens /// A loader is an indicator that uses animation to show customers that there is an indefinite amount of wait time while a task is ongoing, e.g. a page is loading, a form is being submitted. The component disappears when the task is complete. +@objcMembers @objc(VDSLoader) open class Loader: View { diff --git a/VDS/Components/Loader/LoaderViewController.swift b/VDS/Components/Loader/LoaderViewController.swift index 65639c18..408422fb 100644 --- a/VDS/Components/Loader/LoaderViewController.swift +++ b/VDS/Components/Loader/LoaderViewController.swift @@ -10,6 +10,8 @@ import UIKit import VDSCoreTokens /// ViewController to show the Loader, this will be presented using the LoaderLaunchable Protocl. +@objcMembers +@objc(VDSLoaderViewController) open class LoaderViewController: UIViewController, Surfaceable { //-------------------------------------------------- // MARK: - Private Properties diff --git a/VDS/Components/Notification/Notification.swift b/VDS/Components/Notification/Notification.swift index 66be33c4..6df7da68 100644 --- a/VDS/Components/Notification/Notification.swift +++ b/VDS/Components/Notification/Notification.swift @@ -14,6 +14,7 @@ import Combine /// in context. There are four types: information, success, warning and error; each /// with different color and content. They may be screen-specific, flow-specific or /// experience-wide. +@objcMembers @objc(VDSNotification) open class Notification: View { diff --git a/VDS/Components/Pagination/Pagination.swift b/VDS/Components/Pagination/Pagination.swift index 8988aaa4..02c1e85e 100644 --- a/VDS/Components/Pagination/Pagination.swift +++ b/VDS/Components/Pagination/Pagination.swift @@ -11,6 +11,7 @@ import VDSCoreTokens import Combine ///Pagination is a control that enables customers to navigate multiple pages of content by selecting either a specific page or the next or previous set of four pages. +@objcMembers @objc(VDSPagination) open class Pagination: View { diff --git a/VDS/Components/Pagination/PaginationButton.swift b/VDS/Components/Pagination/PaginationButton.swift index d7aaf8e2..d5de6e3f 100644 --- a/VDS/Components/Pagination/PaginationButton.swift +++ b/VDS/Components/Pagination/PaginationButton.swift @@ -9,6 +9,7 @@ import UIKit import VDSCoreTokens ///This is customised button for Pagination view +@objcMembers @objc(PaginationButton) open class PaginationButton: ButtonBase { //-------------------------------------------------- diff --git a/VDS/Components/RadioBox/RadioBoxGroup.swift b/VDS/Components/RadioBox/RadioBoxGroup.swift index c58802c0..b5531e33 100644 --- a/VDS/Components/RadioBox/RadioBoxGroup.swift +++ b/VDS/Components/RadioBox/RadioBoxGroup.swift @@ -11,6 +11,7 @@ import UIKit /// Radio boxes are single-select components through which a customer indicates a choice. /// They're stylized ``RadioButtons`` that must always be paired with one or more ``RadioBoxItem`` /// in a radio box group. Use radio boxes to display choices like device storage. +@objcMembers @objc(VDSRadioBoxGroup) open class RadioBoxGroup: SelectorGroupBase, SelectorGroupSingleSelect { diff --git a/VDS/Components/RadioBox/RadioBoxItem.swift b/VDS/Components/RadioBox/RadioBoxItem.swift index 872a4d05..1da80c0a 100644 --- a/VDS/Components/RadioBox/RadioBoxItem.swift +++ b/VDS/Components/RadioBox/RadioBoxItem.swift @@ -12,6 +12,7 @@ import VDSCoreTokens /// Radio boxes are single-select components through which a customer indicates a choice /// that are used within a ``RadioBoxGroup``. +@objcMembers @objc(VDSRadioBoxItem) open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { diff --git a/VDS/Components/RadioButton/RadioButton.swift b/VDS/Components/RadioButton/RadioButton.swift index f19e7b2f..7241fddf 100644 --- a/VDS/Components/RadioButton/RadioButton.swift +++ b/VDS/Components/RadioButton/RadioButton.swift @@ -13,6 +13,7 @@ import VDSCoreTokens /// Radio buttons are single-select components through which a customer indicates a choice. /// They must always be paired with one or more ``RadioButtonItem`` within a ``RadioButtonGroup``. /// Use radio buttons to display choices like delivery method. +@objcMembers @objc(VDSRadioButton) open class RadioButton: SelectorBase { diff --git a/VDS/Components/RadioButton/RadioButtonGroup.swift b/VDS/Components/RadioButton/RadioButtonGroup.swift index f44f5587..13adf080 100644 --- a/VDS/Components/RadioButton/RadioButtonGroup.swift +++ b/VDS/Components/RadioButton/RadioButtonGroup.swift @@ -11,6 +11,7 @@ import UIKit /// Radio buttons items are single-select components through which a customer indicates a choice. /// They must always be paired with one or more other ``RadioButtonItem`` within a radio button group. /// Use radio buttons to display choices like delivery method. +@objcMembers @objc(VDSRadioButtonGroup) open class RadioButtonGroup: SelectorGroupBase, SelectorGroupSingleSelect { diff --git a/VDS/Components/RadioButton/RadioButtonItem.swift b/VDS/Components/RadioButton/RadioButtonItem.swift index bc15531d..af84ac89 100644 --- a/VDS/Components/RadioButton/RadioButtonItem.swift +++ b/VDS/Components/RadioButton/RadioButtonItem.swift @@ -11,6 +11,7 @@ import UIKit /// Radio buttons items are single-select components through which a customer indicates a choice. /// They must always be paired with one or more other radio button items within a ``RadioButtonGroup``. /// Use radio buttons to display choices like delivery method. +@objcMembers @objc(VDSRadioButtonItem) open class RadioButtonItem: SelectorItemBase { diff --git a/VDS/Components/Table/Table.swift b/VDS/Components/Table/Table.swift index 39cc6698..bb599545 100644 --- a/VDS/Components/Table/Table.swift +++ b/VDS/Components/Table/Table.swift @@ -10,6 +10,7 @@ import UIKit import VDSCoreTokens ///Table is view composed of rows and columns, which takes any view into each cell and resizes based on the highest cell height. +@objcMembers @objc(VDSTable) open class Table: View { diff --git a/VDS/Components/Tabs/Tab.swift b/VDS/Components/Tabs/Tab.swift index 4c0b55cd..edc8af55 100644 --- a/VDS/Components/Tabs/Tab.swift +++ b/VDS/Components/Tabs/Tab.swift @@ -11,7 +11,7 @@ import VDSCoreTokens import Combine extension Tabs { - + @objcMembers @objc(VDSTab) open class Tab: Control, Groupable { diff --git a/VDS/Components/Tabs/Tabs.swift b/VDS/Components/Tabs/Tabs.swift index 88ae02e7..e909d43b 100644 --- a/VDS/Components/Tabs/Tabs.swift +++ b/VDS/Components/Tabs/Tabs.swift @@ -10,6 +10,7 @@ import UIKit import VDSCoreTokens /// Tabs are organizational components that group content and allow customers to navigate its display. Use them to separate content when the content is related but doesn’t need to be compared. +@objcMembers @objc(VDSTabs) open class Tabs: View { diff --git a/VDS/Components/Tabs/TabsContainer.swift b/VDS/Components/Tabs/TabsContainer.swift index d44b877f..945ecbaf 100644 --- a/VDS/Components/Tabs/TabsContainer.swift +++ b/VDS/Components/Tabs/TabsContainer.swift @@ -8,6 +8,7 @@ import Foundation import UIKit +@objcMembers @objc(VDSTabsContainer) open class TabsContainer: View { diff --git a/VDS/Components/TextFields/EntryFieldBase.swift b/VDS/Components/TextFields/EntryFieldBase.swift index 571783bc..481318b6 100644 --- a/VDS/Components/TextFields/EntryFieldBase.swift +++ b/VDS/Components/TextFields/EntryFieldBase.swift @@ -11,6 +11,7 @@ import VDSCoreTokens import Combine /// Base Class used to build out a Input controls. +@objcMembers @objc(VDSEntryField) open class EntryFieldBase: Control, Changeable, FormFieldInternalValidatable { diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index fd36ecac..a65df78e 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -13,6 +13,7 @@ import Combine /// An input field is an input wherein a customer enters information. They typically appear in forms. /// Specialized input fields capture credit card numbers, inline actions, passwords, phone numbers, /// dates and security codes in their correct formats. +@objcMembers @objc(VDSInputField) open class InputField: EntryFieldBase { @@ -311,6 +312,24 @@ open class InputField: EntryFieldBase { } } + open var widthPercentage: CGFloat? { didSet { setNeedsUpdate() } } + + internal override func updateContainerWidth() { + widthConstraint?.deactivate() + trailingLessThanEqualsConstraint?.deactivate() + trailingEqualsConstraint?.deactivate() + + //see if there is a widthPercentage and follow the same pattern as done for "width" + let currentWidth = (horizontalPinnedWidth() ?? 0) * (widthPercentage ?? 0) + if currentWidth >= minWidth, currentWidth <= maxWidth { + widthConstraint?.constant = currentWidth + widthConstraint?.activate() + trailingLessThanEqualsConstraint?.activate() + } else { + super.updateContainerWidth() + } + } + override func updateRules() { super.updateRules() fieldType.handler().appendRules(self) diff --git a/VDS/Components/TextFields/InputField/TextField.swift b/VDS/Components/TextFields/InputField/TextField.swift index b56f3423..3fdf308f 100644 --- a/VDS/Components/TextFields/InputField/TextField.swift +++ b/VDS/Components/TextFields/InputField/TextField.swift @@ -10,6 +10,7 @@ import UIKit import Combine import VDSCoreTokens +@objcMembers @objc(VDSTextField) open class TextField: UITextField, ViewProtocol, Errorable { diff --git a/VDS/Components/TextFields/TextArea/TextArea.swift b/VDS/Components/TextFields/TextArea/TextArea.swift index 8768352e..07c79ca6 100644 --- a/VDS/Components/TextFields/TextArea/TextArea.swift +++ b/VDS/Components/TextFields/TextArea/TextArea.swift @@ -12,6 +12,7 @@ import Combine /// A text area is an input wherein a customer enters long-form information. /// Use a text area when you want customers to enter text that’s longer than a single line. +@objcMembers @objc(VDSTextArea) open class TextArea: EntryFieldBase { //-------------------------------------------------- diff --git a/VDS/Components/TextFields/TextArea/TextView.swift b/VDS/Components/TextFields/TextArea/TextView.swift index 3c5dc5ee..63356635 100644 --- a/VDS/Components/TextFields/TextArea/TextView.swift +++ b/VDS/Components/TextFields/TextArea/TextView.swift @@ -10,6 +10,7 @@ import UIKit import Combine import VDSCoreTokens +@objcMembers @objc(VDSTextView) open class TextView: UITextView, ViewProtocol, Errorable { diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index cbe68e3a..f35d2614 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -10,6 +10,7 @@ import VDSCoreTokens import UIKit import Combine +@objcMembers @objc(VDSTileContainer) open class TileContainer: TileContainerBase { diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index e52332ff..4391bfb3 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -15,6 +15,7 @@ import Combine /// support quick scanning and engagement. A Tilelet is fully clickable and /// while it can include an arrow CTA, it does not require one in order to /// function. +@objcMembers @objc(VDSTilelet) open class Tilelet: TileContainerBase { diff --git a/VDS/Components/TitleLockup/TitleLockup.swift b/VDS/Components/TitleLockup/TitleLockup.swift index bc5c4c3a..47ae5421 100644 --- a/VDS/Components/TitleLockup/TitleLockup.swift +++ b/VDS/Components/TitleLockup/TitleLockup.swift @@ -12,6 +12,7 @@ import Combine /// Title Lockup ensures the readability of words on the screen /// with approved built in text size configurations. +@objcMembers @objc(VDSTitleLockup) open class TitleLockup: View { diff --git a/VDS/Components/Toggle/Toggle.swift b/VDS/Components/Toggle/Toggle.swift index aaa411de..7cb4ef71 100644 --- a/VDS/Components/Toggle/Toggle.swift +++ b/VDS/Components/Toggle/Toggle.swift @@ -12,6 +12,7 @@ import Combine /// A toggle is a control that lets customers instantly turn on /// or turn off a single option, setting or function. +@objcMembers @objc(VDSToggle) open class Toggle: Control, Changeable, FormFieldable { diff --git a/VDS/Components/Toggle/ToggleView.swift b/VDS/Components/Toggle/ToggleView.swift index dc5e9570..9274be1a 100644 --- a/VDS/Components/Toggle/ToggleView.swift +++ b/VDS/Components/Toggle/ToggleView.swift @@ -12,6 +12,7 @@ import Combine /// A toggle is a control that lets customers instantly turn on /// or turn off a single option, setting or function. +@objcMembers @objc(VDSToggleView) open class ToggleView: Control, Changeable, FormFieldable { diff --git a/VDS/Components/Tooltip/Tooltip.swift b/VDS/Components/Tooltip/Tooltip.swift index f07fb1be..6eaf7ef4 100644 --- a/VDS/Components/Tooltip/Tooltip.swift +++ b/VDS/Components/Tooltip/Tooltip.swift @@ -13,6 +13,7 @@ import Combine /// A tooltip is an overlay that clarifies another component or content /// element. It is triggered when a customer hovers, clicks or taps /// the tooltip icon. +@objcMembers @objc(VDSTooltip) open class Tooltip: Control, TooltipLaunchable { diff --git a/VDS/Components/Tooltip/TooltipAlertViewController.swift b/VDS/Components/Tooltip/TooltipAlertViewController.swift index 043f07f4..0fb3d69c 100644 --- a/VDS/Components/Tooltip/TooltipAlertViewController.swift +++ b/VDS/Components/Tooltip/TooltipAlertViewController.swift @@ -10,6 +10,8 @@ import UIKit import Combine import VDSCoreTokens +@objcMembers +@objc(VDSTooltipAlertViewController) open class TooltipAlertViewController: UIViewController, Surfaceable { /// Set of Subscribers for any Publishers for this Control. diff --git a/VDS/Components/Tooltip/TooltipDialog.swift b/VDS/Components/Tooltip/TooltipDialog.swift index 0650a808..bb6b5653 100644 --- a/VDS/Components/Tooltip/TooltipDialog.swift +++ b/VDS/Components/Tooltip/TooltipDialog.swift @@ -9,6 +9,8 @@ import Foundation import UIKit import VDSCoreTokens +@objcMembers +@objc(VDSTooltipDialog) open class TooltipDialog: View, UIScrollViewDelegate { //--------------------------------------------------