diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 8f6878a0..39a21260 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -1513,7 +1513,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 64; + CURRENT_PROJECT_VERSION = 65; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1550,7 +1550,7 @@ BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 64; + CURRENT_PROJECT_VERSION = 65; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/VDS/BaseClasses/Selector/SelectorItemBase.swift b/VDS/BaseClasses/Selector/SelectorItemBase.swift index 75f832ed..66772ce0 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -161,7 +161,8 @@ open class SelectorItemBase: Control, Errorable, /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() - + + selectorView.isAccessibilityElement = false isAccessibilityElement = true accessibilityTraits = .button addSubview(mainStackView) diff --git a/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift b/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift index 424ebcf3..f9dc98fb 100644 --- a/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift +++ b/VDS/Components/Breadcrumbs/BreadcrumbCellItem.swift @@ -13,78 +13,14 @@ final class BreadcrumbCellItem: UICollectionViewCell { ///Identifier for the BreadcrumbCellItem static let identifier: String = String(describing: BreadcrumbCellItem.self) - - //-------------------------------------------------- - // MARK: - Private Properties - //-------------------------------------------------- - internal var stackView: UIStackView = { - return UIStackView().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.axis = .horizontal - $0.distribution = .fill - $0.alignment = .fill - $0.spacing = VDSLayout.space1X - $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) - $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) - } - }() - - internal var breadCrumbItem: BreadcrumbItem? - - ///separator label - private var separator: Label = Label().with { - $0.translatesAutoresizingMaskIntoConstraints = false - $0.textAlignment = .left - $0.numberOfLines = 1 - $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) - $0.setContentHuggingPriority(.defaultHigh, for: .horizontal) - $0.text = "/" - } - - private let textColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) - - //-------------------------------------------------- - // MARK: - Initializers - //-------------------------------------------------- - override init(frame: CGRect) { - super.init(frame: frame) - setUp() - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - setUp() - } - - ///Configuring the cell with default setup - private func setUp() { - separator.textColorConfiguration = textColorConfiguration.eraseToAnyColorable() - contentView.addSubview(stackView) - stackView.pinToSuperView() - separator.backgroundColor = .clear - } - + ///Updating the breadCrumbItem and UI based on the selected flag along with the surface - func update(surface: Surface, hideSlash: Bool, breadCrumbItem: BreadcrumbItem) { - //update surface - separator.surface = surface - breadCrumbItem.surface = surface + func update(breadCrumbItem: BreadcrumbItem) { + contentView.subviews.forEach{$0.removeFromSuperview()} + contentView.addSubview(breadCrumbItem) + breadCrumbItem.pinToSuperView() breadCrumbItem.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) breadCrumbItem.setContentHuggingPriority(.defaultLow, for: .horizontal) - - //remove previous views - stackView.arrangedSubviews.forEach { $0.removeFromSuperview() } - - //add to stack - stackView.addArrangedSubview(separator) - stackView.addArrangedSubview(breadCrumbItem) - stackView.setCustomSpacing(VDSLayout.space1X, after: separator) - - //update separator - separator.textColor = textColorConfiguration.getColor(surface) - separator.isHidden = hideSlash - self.breadCrumbItem = breadCrumbItem - setNeedsLayout() } } diff --git a/VDS/Components/Breadcrumbs/BreadcrumbItem.swift b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift index f12d9646..263a4cba 100644 --- a/VDS/Components/Breadcrumbs/BreadcrumbItem.swift +++ b/VDS/Components/Breadcrumbs/BreadcrumbItem.swift @@ -42,23 +42,28 @@ open class BreadcrumbItem: ButtonBase { textColorConfiguration.getColor(self) } - /// The natural size for the receiving view, considering only properties of the view itself. - open override var intrinsicContentSize: CGSize { - guard let titleLabel else { return super.intrinsicContentSize } - // Calculate the titleLabel's intrinsic content size - let labelSize = titleLabel.sizeThatFits(CGSize(width: self.frame.width, height: CGFloat.greatestFiniteMagnitude)) - // Adjust the size if needed (add any additional padding if your design requires) - let adjustedSize = CGSize(width: labelSize.width + contentEdgeInsets.left + contentEdgeInsets.right, - height: labelSize.height + contentEdgeInsets.top + contentEdgeInsets.bottom) - return adjustedSize + /// Determines if a slash is predended or not. + open var hideSlash: Bool = false { didSet { setNeedsUpdate() } } + + private var slashText = "/ " + + open override var textAttributes: [any LabelAttributeModel]? { + hideSlash + ? nil + : [ColorLabelAttribute(location: 0, + length: 1, + color: surface == .light ? VDSColor.elementsPrimaryOnlight : VDSColor.elementsPrimaryOndark), + TextStyleLabelAttribute(location: 0, + length: 1, + textStyle: .bodySmall) + ] } - + //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var textColorConfiguration = ControlColorConfiguration().with { $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .normal) - $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .disabled) $0.setSurfaceColors(VDSColor.interactiveActiveOnlight, VDSColor.interactiveActiveOndark, forState: .highlighted) $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forState: .selected) } @@ -69,15 +74,51 @@ open class BreadcrumbItem: ButtonBase { /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() + + titleLabel?.numberOfLines = 0 + titleLabel?.lineBreakMode = .byWordWrapping + contentHorizontalAlignment = .left + isAccessibilityElement = true accessibilityTraits = .link - contentHorizontalAlignment = .leading + } /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { - //always call last so the label is rendered - super.updateView() + + //clear the arrays holding actions + accessibilityCustomActions = [] + if let text, !text.isEmpty { + var updatedText = text + if updatedText.hasPrefix(slashText) && hideSlash { + updatedText = String(updatedText.dropFirst(slashText.count)) + } else if !hideSlash, !updatedText.hasPrefix(slashText) { + updatedText = slashText + updatedText + } + + //create the primary string + let mutableText = NSMutableAttributedString.mutableText(for: updatedText, + textStyle: textStyle, + useScaledFont: useScaledFont, + textColor: textColor, + alignment: titleLabel?.textAlignment ?? .center, + lineBreakMode: titleLabel?.lineBreakMode ?? .byTruncatingTail) + + //apply any attributes + if let attributes = textAttributes { + mutableText.apply(attributes: attributes) + } + + //set the attributed text + setAttributedTitle(mutableText, for: .normal) + setAttributedTitle(mutableText, for: .highlighted) + invalidateIntrinsicContentSize() + } else { + setAttributedTitle(nil, for: .normal) + setAttributedTitle(nil, for: .highlighted) + titleLabel?.text = nil + } } diff --git a/VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift b/VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift index 75c97c86..2c73f24f 100644 --- a/VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift +++ b/VDS/Components/Breadcrumbs/BreadcrumbItemModel.swift @@ -12,9 +12,6 @@ extension Breadcrumbs { ///Text that goes in the breadcrumb item public var text: String - - /// Whether the Item can be clicked. - public var enabled: Bool /// The Breadcrumb link to links to its respective page. public var selected: Bool @@ -22,9 +19,8 @@ extension Breadcrumbs { ///Click event when you click on a breadcrumb item public var onClick: ((BreadcrumbItem) -> Void)? - public init(text: String, enabeled: Bool = true, selected: Bool = false, onClick: ((BreadcrumbItem) -> Void)? = nil) { + public init(text: String, selected: Bool = false, onClick: ((BreadcrumbItem) -> Void)? = nil) { self.text = text - self.enabled = enabeled self.selected = selected self.onClick = onClick } diff --git a/VDS/Components/Breadcrumbs/Breadcrumbs.swift b/VDS/Components/Breadcrumbs/Breadcrumbs.swift index 7c88e18a..41940a52 100644 --- a/VDS/Components/Breadcrumbs/Breadcrumbs.swift +++ b/VDS/Components/Breadcrumbs/Breadcrumbs.swift @@ -32,13 +32,6 @@ open class Breadcrumbs: View { } } - /// Current Surface and this is used to pass down to child objects that implement Surfacable - override open var surface: Surface { - didSet { - breadcrumbs.forEach { $0.surface = surface } - } - } - open override var accessibilityElements: [Any]? { get { return [containerView, breadcrumbs] @@ -99,7 +92,6 @@ open class Breadcrumbs: View { breadcrumbs = breadcrumbModels.compactMap({ model in let breadcrumbItem = BreadcrumbItem() breadcrumbItem.text = model.text - breadcrumbItem.isEnabled = model.enabled breadcrumbItem.isSelected = model.selected breadcrumbItem.onClick = { [weak self] breadcrumb in guard let self, breadcrumb.isEnabled else { return } @@ -166,17 +158,22 @@ extension Breadcrumbs: UICollectionViewDelegate, UICollectionViewDataSource, But public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: BreadcrumbCellItem.identifier, for: indexPath) as? BreadcrumbCellItem else { return UICollectionViewCell() } - let hideSlash = indexPath.row == 0 - cell.update(surface: surface, hideSlash: hideSlash, breadCrumbItem: breadcrumbs[indexPath.row]) + let breadcrumb = breadcrumbs[indexPath.row] + breadcrumb.hideSlash = breadcrumb == breadcrumbs.first + breadcrumb.surface = surface + cell.update(breadCrumbItem: breadcrumb) return cell } public func collectionView(_ collectionView: UICollectionView, sizeForItemAtIndexPath indexPath: IndexPath) -> CGSize { + let breadcrumb = breadcrumbs[indexPath.row] - let intrinsicSize = breadcrumb.intrinsicContentSize - let separatorFullWidth: CGFloat = indexPath.row == 0 ? 0 : VDSLayout.space1X + separatorWidth - let cellwidth = intrinsicSize.width + separatorFullWidth - return .init(width: min(cellwidth, collectionView.frame.width), height: intrinsicSize.height) + breadcrumb.hideSlash = breadcrumb == breadcrumbs.first + + let maxWidth = frame.width + let intrinsicSize = breadcrumb.titleLabel!.sizeThatFits(.init(width: maxWidth, height: CGFloat.greatestFiniteMagnitude)) + let cellwidth = min(maxWidth, intrinsicSize.width) + return .init(width: cellwidth, height: intrinsicSize.height) } public func collectionView(_ collectionView: UICollectionView, buttonBaseAtIndexPath indexPath: IndexPath) -> ButtonBase { diff --git a/VDS/Components/RadioBox/RadioBoxItem.swift b/VDS/Components/RadioBox/RadioBoxItem.swift index f102b7c2..15f7c55a 100644 --- a/VDS/Components/RadioBox/RadioBoxItem.swift +++ b/VDS/Components/RadioBox/RadioBoxItem.swift @@ -243,6 +243,11 @@ open class RadioBoxItem: Control, Changeable, FormFieldable, Groupable { open override func updateAccessibility() { super.updateAccessibility() setAccessibilityLabel(for: [textLabel, subTextLabel, subTextRightLabel]) + if let currentAccessibilityLabel = accessibilityLabel { + accessibilityLabel = "Radiobox, \(currentAccessibilityLabel)" + } else { + accessibilityLabel = "Radiobox" + } if let accessibilityValueText { accessibilityValue = strikethrough ? "\(strikethroughAccessibilityText), \(accessibilityValueText)" diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 7abac288..a2f20ffe 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -108,7 +108,7 @@ open class TileContainerBase: Control where Padding $0.clipsToBounds = true } - private var containerView = View() + internal var containerView = View() //-------------------------------------------------- // MARK: - Public Properties @@ -342,9 +342,6 @@ open class TileContainerBase: Control where Padding containerView.isAccessibilityElement = onClickSubscriber != nil containerView.accessibilityHint = "Double tap to open." containerView.accessibilityLabel = nil - if let views = accessibilityElements?.compactMap({ $0 as? UIView }), !views.isEmpty { - containerView.setAccessibilityLabel(for: views) - } } open override var accessibilityElements: [Any]? { @@ -358,10 +355,18 @@ open class TileContainerBase: Control where Padding } items.append(containerView) } - items.append(contentsOf: contentView.subviews.filter({ $0.isAccessibilityElement == true })) + + let elements = gatherAccessibilityElements(from: contentView) + let views = elements.compactMap({ $0 as? UIView }) + + //update accessibilityLabel + containerView.setAccessibilityLabel(for: views) + + //append all children that are accessible + items.append(contentsOf: elements) + return items } - set {} } diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index e905560c..9c4f33da 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -415,29 +415,37 @@ open class Tilelet: TileContainerBase { /// Used to update any Accessibility properties. open override var accessibilityElements: [Any]? { - get { - var elements = [Any]() - if let superElements = super.accessibilityElements { - elements.append(contentsOf: superElements) - } + var views = [UIView]() + + // grab the available views in order if badgeModel != nil { - elements.append(badge) + views.append(badge) } + if titleModel != nil || subTitleModel != nil || eyebrowModel != nil { - elements.append(titleLockup) + views.append(titleLockup) } - if descriptiveIconModel != nil { - elements.append(descriptiveIcon) + + containerView.setAccessibilityLabel(for: views) + + // get the views to return + var items = [Any]() + if containerView.isAccessibilityElement { + if !accessibilityTraits.contains(.button) && !accessibilityTraits.contains(.link) { + containerView.accessibilityTraits = .button + } else { + containerView.accessibilityTraits = accessibilityTraits + } + items.append(containerView) } - if directionalIconModel != nil { - elements.append(directionalIcon) - } - return elements + + //append all other accessible views to traverse + items.append(contentsOf: views) + + return items } - set {} - } //-------------------------------------------------- diff --git a/VDS/Extensions/UIView+Accessibility.swift b/VDS/Extensions/UIView+Accessibility.swift index e85b3f74..8710032f 100644 --- a/VDS/Extensions/UIView+Accessibility.swift +++ b/VDS/Extensions/UIView+Accessibility.swift @@ -50,4 +50,16 @@ extension UIView { return isIntersecting } + public func gatherAccessibilityElements(from view: UIView) -> [Any] { + var elements: [Any] = [] + + for subview in view.subviews { + if subview.isAccessibilityElement && subview.isVisibleOnScreen { + elements.append(subview) + } + elements.append(contentsOf: gatherAccessibilityElements(from: subview)) + } + + return elements + } } diff --git a/VDS/SupportingFiles/ReleaseNotes.txt b/VDS/SupportingFiles/ReleaseNotes.txt index 265c65ba..165ab44b 100644 --- a/VDS/SupportingFiles/ReleaseNotes.txt +++ b/VDS/SupportingFiles/ReleaseNotes.txt @@ -7,6 +7,7 @@ - CXTDT-563189 - Dropdown Select Readonly Border color - CXTDT-555854 - Dropdown Select - spacing issues - CXTDT-563194 - Dropdown Select - missing transparentBackground option +- CXTDT-552834 – TileContainer – Voice over is not rendering the information. 1.0.64 ----------------