diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 6f061205..2cd47732 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -93,6 +93,7 @@ 0A7EF86523D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86423D8AFFF00B2AAD1 /* ItemDropdownEntryFieldModel.swift */; }; 0A7EF86723D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */; }; 0A849EFE246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A849EFD246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift */; }; + 0A98D1722485842200FAD895 /* AccessibilityProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A98D1712485842200FAD895 /* AccessibilityProtocol.swift */; }; 0A9D091D2433796500D2E6C0 /* BarsCarouselIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D09172433796500D2E6C0 /* BarsCarouselIndicatorModel.swift */; }; 0A9D091E2433796500D2E6C0 /* NumericCarouselIndicatorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D09182433796500D2E6C0 /* NumericCarouselIndicatorModel.swift */; }; 0A9D091F2433796500D2E6C0 /* NumericIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A9D09192433796500D2E6C0 /* NumericIndicatorView.swift */; }; @@ -525,6 +526,7 @@ 0A7EF86623D8B0AE00B2AAD1 /* DateDropdownEntryFieldModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateDropdownEntryFieldModel.swift; sourceTree = ""; }; 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; 0A849EFD246F1775009F277F /* RuleEqualsIgnoreCaseModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuleEqualsIgnoreCaseModel.swift; sourceTree = ""; }; + 0A98D1712485842200FAD895 /* AccessibilityProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityProtocol.swift; sourceTree = ""; }; 0A9D09172433796500D2E6C0 /* BarsCarouselIndicatorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarsCarouselIndicatorModel.swift; sourceTree = ""; }; 0A9D09182433796500D2E6C0 /* NumericCarouselIndicatorModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumericCarouselIndicatorModel.swift; sourceTree = ""; }; 0A9D09192433796500D2E6C0 /* NumericIndicatorView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NumericIndicatorView.swift; sourceTree = ""; }; @@ -947,6 +949,7 @@ children = ( D21B7F72243BAC6800051ABF /* CollectionItemModelProtocol.swift */, 0A5D59C123AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift */, + 0A98D1712485842200FAD895 /* AccessibilityProtocol.swift */, ); path = Protocols; sourceTree = ""; @@ -2362,6 +2365,7 @@ 012A88C2238D7BCA00FE3DA1 /* CarouselItemModel.swift in Sources */, 0AB764D324460FA400E7FE72 /* UIPickerView+Extension.swift in Sources */, D29DF29E21E7AE3B003B2FB9 /* MFStyler.m in Sources */, + 0A98D1722485842200FAD895 /* AccessibilityProtocol.swift in Sources */, 94C661D923CCF4B400D9FE5B /* LeftRightLabelModel.swift in Sources */, AA26850C244840AE00CE34CC /* HeadersH2TinyButton.swift in Sources */, 011D95AB2405C553000E3791 /* FormItemProtocol.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift index 448d76e1..e5da20c5 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinks.swift @@ -8,15 +8,23 @@ import Foundation + @objcMembers open class ListLeftVariableCheckboxAllTextAndLinks: TableViewCell { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + public let checkbox = Checkbox() + public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink() + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public let checkbox = Checkbox() - public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink(frame: .zero) public var stack: Stack + private var observation: NSKeyValueObservation? = nil + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -31,7 +39,7 @@ import Foundation public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + //-------------------------------------------------- // MARK: - Life Cycle //-------------------------------------------------- @@ -40,6 +48,16 @@ import Foundation super.setupView() addMolecule(stack) stack.restack() + + isAccessibilityElement = true + checkbox.isAccessibilityElement = false + accessibilityTraits = checkbox.accessibilityTraits + accessibilityHint = checkbox.accessibilityHint + // Update accessibility label on radio button state change. + observation = observe(\.checkbox.isSelected, options: [.new]) { [weak self] _, _ in + self?.updateAccessibilityLabel() + } + updateAccessibilityLabel() } //-------------------------------------------------- @@ -53,9 +71,58 @@ import Foundation checkbox.set(with: model.checkbox, delegateObject, additionalData) eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + updateAccessibilityLabel() } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 140 } + + func updateAccessibilityLabel() { + + var message = "" + + checkbox.updateAccessibilityLabel() + + if let checkboxLabel = checkbox.accessibilityLabel { + message += checkboxLabel + ", " + } + + if let eyebrowLabel = eyebrowHeadlineBodyLink.eyebrow.text { + message += eyebrowLabel + ", " + } + + if let headlineLabel = eyebrowHeadlineBodyLink.headline.text { + message += headlineLabel + ", " + } + + if let bodyLabel = eyebrowHeadlineBodyLink.body.text { + message += bodyLabel + } + + let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + + isAccessibilityElement = true//!linkShowing + checkbox.isAccessibilityElement = linkShowing + eyebrowHeadlineBodyLink.link.isAccessibilityElement = linkShowing + + if !linkShowing { + // Make whole cell focusable if no link. + accessibilityLabel = message + } else { + // Allow only radio button and link to be focused on. + checkbox.accessibilityLabel = message + var elements: [UIView] = [] + + if message.count > 0 { + elements.append(checkbox) + } + + if eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 { + elements.append(eyebrowHeadlineBodyLink.link) + } + + accessibilityElements = elements + } + } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinksModel.swift index d3d3aab4..8c2e9761 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxAllTextAndLinksModel.swift @@ -8,6 +8,7 @@ import Foundation + open class ListLeftVariableCheckboxAllTextAndLinksModel: ListItemModel, MoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyText.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyText.swift index e1f823ce..92e31c68 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyText.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyText.swift @@ -8,17 +8,27 @@ import Foundation + @objcMembers open class ListLeftVariableCheckboxBodyText: TableViewCell { //----------------------------------------------------- // MARK: - Outlets //----------------------------------------------------- - public let checkbox = Checkbox(frame: .zero) + + public let checkbox = Checkbox() public var headlineBody = HeadlineBody() + + //----------------------------------------------------- + // MARK: - Properties + //----------------------------------------------------- + public var stack: Stack + private var observation: NSKeyValueObservation? = nil + //----------------------------------------------------- // MARK: - Initializers //----------------------------------------------------- + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { stack = Stack.createStack(with: [(view: checkbox, model: StackItemModel(horizontalAlignment: .fill)), (view: headlineBody, model: StackItemModel(horizontalAlignment: .leading))], @@ -31,25 +41,63 @@ import Foundation } //----------------------------------------------------- - // MARK: - View Lifecycle + // MARK: - Lifecycle //----------------------------------------------------- + override open func setupView() { super.setupView() addMolecule(stack) stack.restack() + isAccessibilityElement = true + checkbox.isAccessibilityElement = false + accessibilityTraits = checkbox.accessibilityTraits + accessibilityHint = checkbox.accessibilityHint + // Update accessibility label on radio button state change. + observation = observe(\.checkbox.isSelected, options: [.new]) { [weak self] _, _ in + self?.updateAccessibilityLabel() + } + updateAccessibilityLabel() } //---------------------------------------------------- // MARK: - Molecule //---------------------------------------------------- - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListLeftVariableCheckboxBodyTextModel else { return } + checkbox.set(with: model.checkbox, delegateObject, additionalData) headlineBody.set(with: model.headlineBody, delegateObject, additionalData) + updateAccessibilityLabel() } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 90 } + + public override func didSelectCell(at index: IndexPath, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + checkbox.isSelected.toggle() + } + + func updateAccessibilityLabel() { + + var message = "" + + checkbox.updateAccessibilityLabel() + + if let checkboxLabel = checkbox.accessibilityLabel { + message += checkboxLabel + ", " + } + + if let headlineLabel = headlineBody.headlineLabel.text { + message += headlineLabel + ", " + } + + if let messageLabel = headlineBody.messageLabel.text { + message += messageLabel + } + + accessibilityLabel = message + } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift index f6fb19fc..2d7301b3 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableCheckboxBodyTextModel.swift @@ -7,6 +7,8 @@ // import Foundation + + open class ListLeftVariableCheckboxBodyTextModel: ListItemModel, MoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties @@ -36,6 +38,7 @@ open class ListLeftVariableCheckboxBodyTextModel: ListItemModel, MoleculeModelPr //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { case moleculeName case headlineBody @@ -45,6 +48,7 @@ open class ListLeftVariableCheckboxBodyTextModel: ListItemModel, MoleculeModelPr //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) headlineBody = try typeContainer.decode(HeadlineBodyModel.self, forKey: .headlineBody) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift index f53134a1..c5ec5d63 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinks.swift @@ -7,17 +7,21 @@ // import Foundation + + @objcMembers open class ListLeftVariableIconAllTextLinks: TableViewCell { - //----------------------------------------------------- + //-------------------------------------------------- // MARK: - Outlets - //----------------------------------------------------- + //-------------------------------------------------- + public let leftImage = LoadImageView() - public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink(frame: .zero) + public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink() public var stack: Stack //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { stack = Stack.createStack(with: [(view: leftImage, model: StackItemModel(horizontalAlignment: .fill)), (view: eyebrowHeadlineBodyLink, model: StackItemModel(horizontalAlignment: .leading))], axis: .horizontal) super.init(style: style, reuseIdentifier: reuseIdentifier) @@ -30,25 +34,77 @@ import Foundation //-------------------------------------------------- // MARK: - Life Cycle //-------------------------------------------------- + override open func setupView() { super.setupView() + leftImage.addSizeConstraintsForAspectRatio = true leftImage.imageView.contentMode = .scaleAspectFit addMolecule(stack) stack.restack() + isAccessibilityElement = true + updateAccessibilityLabel() } - //------------------------------------------------------ + //-------------------------------------------------- // MARK: - Molecule - //------------------------------------------------------ + //-------------------------------------------------- + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListLeftVariableIconAllTextLinksModel else { return } + leftImage.set(with: model.image, delegateObject, additionalData) eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + updateAccessibilityLabel() } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 140 } + + func updateAccessibilityLabel() { + + var message = "" + + if let leftImageLabel = leftImage.accessibilityLabel { + message += leftImageLabel + ", " + } + + if let eyebrowLabel = eyebrowHeadlineBodyLink.eyebrow.text { + message += eyebrowLabel + ", " + } + + if let headlineLabel = eyebrowHeadlineBodyLink.headline.text { + message += headlineLabel + ", " + } + + if let bodyLabel = eyebrowHeadlineBodyLink.body.text { + message += bodyLabel + } + + let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + isAccessibilityElement = !linkShowing + eyebrowHeadlineBodyLink.link.isAccessibilityElement = linkShowing + + if !linkShowing { + // Make whole cell focusable if no link. + accessibilityLabel = message + } else { + // Allow only radio button and link to be focused on. + accessibilityLabel = message + var elements: [UIView] = [] + + if message.count > 0 { + elements.append(self) + } + + if linkShowing { + elements.append(eyebrowHeadlineBodyLink.link) + } + + accessibilityElements = elements + } + } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift index 8e35984a..0c3c8f5b 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconAllTextLinksModel.swift @@ -7,11 +7,21 @@ // import Foundation + + public class ListLeftVariableIconAllTextLinksModel: ListItemModel, MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "listLVImgAll" public var image: ImageViewModel public var eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel + //-------------------------------------------------- + // MARK: - Method + //-------------------------------------------------- + override public func setDefaults() { super.setDefaults() if image.width == nil, image.height == nil { @@ -20,18 +30,30 @@ public class ListLeftVariableIconAllTextLinksModel: ListItemModel, MoleculeModel } } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + public init(image: ImageViewModel, eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel) { self.image = image self.eyebrowHeadlineBodyLink = eyebrowHeadlineBodyLink super.init() } + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { case moleculeName case image case eyebrowHeadlineBodyLink } + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) image = try typeContainer.decode(ImageViewModel.self, forKey: .image) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaret.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaret.swift index db7decbd..16175687 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaret.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaret.swift @@ -8,19 +8,21 @@ import UIKit + @objcMembers open class ListLeftVariableIconWithRightCaret: TableViewCell { - - //----------------------------------------------------- + //-------------------------------------------------- // MARK: - Outlets - //------------------------------------------------------- + //-------------------------------------------------- + let leftImage = LoadImageView(pinnedEdges: .all) let leftLabel = Label.createLabelRegularBodySmall(true) let rightLabel = Label.createLabelRegularBodySmall(true) var stack: Stack - //----------------------------------------------------- + //-------------------------------------------------- // MARK: - Initializers - //----------------------------------------------------- + //-------------------------------------------------- + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { stack = Stack.createStack(with: [(view: leftImage, model: StackItemModel(horizontalAlignment: .fill)), (view: leftLabel, model: StackItemModel(horizontalAlignment: .fill)), @@ -33,28 +35,36 @@ import UIKit fatalError("init(coder:) has not been implemented") } - //----------------------------------------------------- + //-------------------------------------------------- // MARK: - View Lifecycle - //------------------------------------------------------- + //-------------------------------------------------- + override open func setupView() { super.setupView() + leftImage.addSizeConstraintsForAspectRatio = true leftImage.contentMode = .scaleAspectFit leftLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 901), for: .horizontal) rightLabel.setContentHuggingPriority(UILayoutPriority(rawValue: 902), for: .horizontal) addMolecule(stack) stack.restack() + isAccessibilityElement = true + updateAccessibilityLabel() } - //---------------------------------------------------- + //-------------------------------------------------- // MARK: - Molecule - //------------------------------------------------------ - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + //-------------------------------------------------- + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListLeftVariableIconWithRightCaretModel else { return} + leftImage.set(with: model.image, delegateObject, additionalData) leftLabel.set(with: model.leftLabel, delegateObject, additionalData) rightLabel.set(with: model.rightLabel, delegateObject, additionalData) + updateAccessibilityLabel() } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { @@ -66,4 +76,23 @@ import UIKit leftLabel.styleRegularBodySmall(true) rightLabel.styleRegularBodySmall(true) } + + func updateAccessibilityLabel() { + + var message = "" + + if let leftImageLabel = leftImage.accessibilityLabel { + message += leftImageLabel + ", " + } + + if let leftLabel = leftLabel.text { + message += leftLabel + ", " + } + + if let rightLabel = rightLabel.text { + message += rightLabel + } + + accessibilityLabel = message + } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyText.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyText.swift index d05fea10..8603381e 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyText.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyText.swift @@ -7,18 +7,21 @@ // import Foundation + + @objcMembers open class ListLeftVariableIconWithRightCaretBodyText: TableViewCell { - //----------------------------------------------------- + //-------------------------------------------------- // MARK: - Outlets - //------------------------------------------------------- + //-------------------------------------------------- + public let leftImage = LoadImageView() public let headlineBody = HeadlineBody() public let rightLabel = Label.createLabelRegularBodySmall(true) public var stack: Stack - //----------------------------------------------------- + //-------------------------------------------------- // MARK: - Initializers - //----------------------------------------------------- + //-------------------------------------------------- public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { stack = Stack.createStack(with: [(view: leftImage, model: StackItemModel(horizontalAlignment: .fill)), (view: headlineBody, model: StackItemModel(horizontalAlignment: .leading)), @@ -30,11 +33,13 @@ import Foundation fatalError("init(coder:) has not been implemented") } - //----------------------------------------------------- + //-------------------------------------------------- // MARK: - View Lifecycle - //------------------------------------------------------- + //-------------------------------------------------- + override open func setupView() { super.setupView() + leftImage.addSizeConstraintsForAspectRatio = true leftImage.contentMode = .scaleAspectFit rightLabel.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 900), for: .horizontal) @@ -42,17 +47,22 @@ import Foundation rightLabel.numberOfLines = 1 addMolecule(stack) stack.restack() + updateAccessibilityLabel() } - //---------------------------------------------------- + //-------------------------------------------------- // MARK: - Molecule - //------------------------------------------------------ - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + //--------------------------------------------------- + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListLeftVariableIconWithRightCaretBodyTextModel else { return } + leftImage.set(with: model.image, delegateObject, additionalData) headlineBody.set(with: model.headlineBody, delegateObject, additionalData) rightLabel.set(with: model.rightLabel, delegateObject, additionalData) + updateAccessibilityLabel() } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { @@ -63,4 +73,27 @@ import Foundation super.reset() rightLabel.styleRegularBodySmall(true) } + + func updateAccessibilityLabel() { + + var message = "" + + if let leftImageLabel = leftImage.accessibilityLabel { + message += leftImageLabel + ", " + } + + if let headlineLabel = headlineBody.headlineLabel.text { + message += headlineLabel + ", " + } + + if let messageLabel = headlineBody.messageLabel.text { + message += messageLabel + ", " + } + + if let rightLabel = rightLabel.text { + message += rightLabel + } + + accessibilityLabel = message + } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift index e2bd5d90..81632b05 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretBodyTextModel.swift @@ -7,12 +7,22 @@ // import Foundation + + public class ListLeftVariableIconWithRightCaretBodyTextModel: ListItemModel, MoleculeModelProtocol { + //----------------------------------------------------- + // MARK: - Properties + //----------------------------------------------------- + public static var identifier: String = "listLVImgBdy" public var image: ImageViewModel public var headlineBody: HeadlineBodyModel public var rightLabel: LabelModel + //----------------------------------------------------- + // MARK: - Methods + //----------------------------------------------------- + override public func setDefaults() { super.setDefaults() if image.width == nil, image.height == nil { @@ -23,6 +33,10 @@ public class ListLeftVariableIconWithRightCaretBodyTextModel: ListItemModel, Mol headlineBody.headline?.hero = 0 } + //----------------------------------------------------- + // MARK: - Initializers + //----------------------------------------------------- + public init(image: ImageViewModel, headlineBody: HeadlineBodyModel, rightLabel: LabelModel) { self.image = image self.headlineBody = headlineBody @@ -30,6 +44,10 @@ public class ListLeftVariableIconWithRightCaretBodyTextModel: ListItemModel, Mol super.init() } + //----------------------------------------------------- + // MARK: - Keys + //----------------------------------------------------- + private enum CodingKeys: String, CodingKey { case moleculeName case image @@ -37,6 +55,10 @@ public class ListLeftVariableIconWithRightCaretBodyTextModel: ListItemModel, Mol case rightLabel } + //----------------------------------------------------- + // MARK: - Codec + //----------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) image = try typeContainer.decode(ImageViewModel.self, forKey: .image) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift index 4b426909..d53faa5e 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableIconWithRightCaretModel.swift @@ -8,12 +8,21 @@ import Foundation + public class ListLeftVariableIconWithRightCaretModel: ListItemModel, MoleculeModelProtocol { + //----------------------------------------------------- + // MARK: - Properties + //----------------------------------------------------- + public static var identifier: String = "listLVImg" public var image: ImageViewModel public var leftLabel: LabelModel public var rightLabel: LabelModel + //----------------------------------------------------- + // MARK: - Methods + //----------------------------------------------------- + override public func setDefaults() { super.setDefaults() if image.height == nil { @@ -22,6 +31,10 @@ public class ListLeftVariableIconWithRightCaretModel: ListItemModel, MoleculeMod rightLabel.hero = 0 } + //----------------------------------------------------- + // MARK: - Initializer + //----------------------------------------------------- + public init(image: ImageViewModel, leftLabel: LabelModel, rightLabel: LabelModel) { self.image = image self.leftLabel = leftLabel @@ -29,6 +42,10 @@ public class ListLeftVariableIconWithRightCaretModel: ListItemModel, MoleculeMod super.init() } + //----------------------------------------------------- + // MARK: - Keys + //----------------------------------------------------- + private enum CodingKeys: String, CodingKey { case moleculeName case leftLabel @@ -36,6 +53,10 @@ public class ListLeftVariableIconWithRightCaretModel: ListItemModel, MoleculeMod case image } + //----------------------------------------------------- + // MARK: - Codec + //----------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) leftLabel = try typeContainer.decode(LabelModel.self, forKey: .leftLabel) @@ -53,5 +74,3 @@ public class ListLeftVariableIconWithRightCaretModel: ListItemModel, MoleculeMod try container.encode(image, forKey: .image) } } - - diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift index d623311b..3bf3c2c1 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinks.swift @@ -8,17 +8,25 @@ import Foundation + @objcMembers open class ListLeftVariableNumberedListAllTextAndLinks: TableViewCell { - //----------------------------------------------------- + //-------------------------------------------------- // MARK: - Outlets - //----------------------------------------------------- + //-------------------------------------------------- + public let leftLabel = Label.createLabelTitle2XLarge(true) - public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink(frame: .zero) + public let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink() + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var stack: Stack //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { stack = Stack.createStack(with: [(view: leftLabel, model: StackItemModel(horizontalAlignment: .fill)), (view: eyebrowHeadlineBodyLink, model: StackItemModel(horizontalAlignment: .fill))], @@ -29,7 +37,7 @@ import Foundation public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + //-------------------------------------------------- // MARK: - Life Cycle //-------------------------------------------------- @@ -38,16 +46,22 @@ import Foundation leftLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) addMolecule(stack) stack.restack() + isAccessibilityElement = true + updateAccessibilityLabel() } - //------------------------------------------------------ + //--------------------------------------------------- // MARK: - Molecule - //------------------------------------------------------ + //--------------------------------------------------- + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListLeftVariableNumberedListAllTextAndLinksModel else { return } + leftLabel.text = String(model.number) eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) + updateAccessibilityLabel() } open override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { @@ -64,4 +78,49 @@ import Foundation // This fixes a defect body text where it doesn't layout correctly. eyebrowHeadlineBodyLink.body.preferredMaxLayoutWidth = eyebrowHeadlineBodyLink.frame.width } + + func updateAccessibilityLabel() { + + var message = "" + + if let leftLabel = leftLabel.text { + message += leftLabel + ", " + } + + if let eyebrowLabel = eyebrowHeadlineBodyLink.eyebrow.text { + message += eyebrowLabel + ", " + } + + if let headlineLabel = eyebrowHeadlineBodyLink.headline.text { + message += headlineLabel + ", " + } + + if let bodyLabel = eyebrowHeadlineBodyLink.body.text { + message += bodyLabel + } + + let linkShowing = eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 + isAccessibilityElement = !linkShowing + eyebrowHeadlineBodyLink.link.isAccessibilityElement = linkShowing + + if !linkShowing { + // Make whole cell focusable if no link. + accessibilityLabel = message + } else { + // Allow only radio button and link to be focused on. + accessibilityLabel = message + + var elements: [UIView] = [] + + if message.count > 0 { + elements.append(self) + } + + if linkShowing { + elements.append(eyebrowHeadlineBodyLink.link) + } + + accessibilityElements = elements + } + } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinksModel.swift index 78a85a8e..b2271f3c 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableNumberedListAllTextAndLinksModel.swift @@ -8,23 +8,40 @@ import Foundation + public class ListLeftVariableNumberedListAllTextAndLinksModel: ListItemModel, MoleculeModelProtocol { + //----------------------------------------------------- + // MARK: - Properties + //----------------------------------------------------- + public static var identifier: String = "listLVNAll" public var number: Int public var eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel + //----------------------------------------------------- + // MARK: - Initializer + //----------------------------------------------------- + public init(number: Int, eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel) { self.number = number self.eyebrowHeadlineBodyLink = eyebrowHeadlineBodyLink super.init() } + //----------------------------------------------------- + // MARK: - Keys + //----------------------------------------------------- + private enum CodingKeys: String, CodingKey { case moleculeName case number case eyebrowHeadlineBodyLink } + //----------------------------------------------------- + // MARK: - Codec + //----------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) number = try typeContainer.decode(Int.self, forKey: .number) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift index fd71bd6d..d4e2d382 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinks.swift @@ -7,10 +7,13 @@ // import Foundation + + @objcMembers open class ListLeftVariableRadioButtonAllTextAndLinks: TableViewCell { //----------------------------------------------------- // MARK: - Outlets //----------------------------------------------------- + let radioButton = RadioButton() let eyebrowHeadlineBodyLink = EyebrowHeadlineBodyLink() var stack: Stack @@ -20,6 +23,7 @@ import Foundation //----------------------------------------------------- // MARK: - Initializers //----------------------------------------------------- + public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { stack = Stack.createStack(with: [(view: radioButton, model: StackItemModel(horizontalAlignment: .fill)), (view: eyebrowHeadlineBodyLink, model: StackItemModel(horizontalAlignment: .leading))], @@ -34,6 +38,7 @@ import Foundation //----------------------------------------------------- // MARK: - View Lifecycle //----------------------------------------------------- + override open func setupView() { super.setupView() addMolecule(stack) @@ -51,6 +56,7 @@ import Foundation //---------------------------------------------------- // MARK: - Molecule //---------------------------------------------------- + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) guard let model = model as? ListLeftVariableRadioButtonAllTextAndLinksModel else { return} @@ -68,17 +74,23 @@ import Foundation } func updateAccessibilityLabel() { + var message = "" + radioButton.updateAccessibilityLabel() + if let radioButtonLabel = radioButton.accessibilityLabel { message += radioButtonLabel + ", " } + if let eyebrowLabel = eyebrowHeadlineBodyLink.eyebrow.text { message += eyebrowLabel + ", " } + if let headlineLabel = eyebrowHeadlineBodyLink.headline.text { message += headlineLabel + ", " } + if let bodyLabel = eyebrowHeadlineBodyLink.body.text { message += bodyLabel } @@ -96,12 +108,15 @@ import Foundation radioButton.accessibilityLabel = message var elements: [UIView] = [] + if message.count > 0 { elements.append(radioButton) } - if eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 { + + if linkShowing { elements.append(eyebrowHeadlineBodyLink.link) } + accessibilityElements = elements } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinksModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinksModel.swift index 1ac8e80d..f682037e 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinksModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAllTextAndLinksModel.swift @@ -7,10 +7,13 @@ // import Foundation + + open class ListLeftVariableRadioButtonAllTextAndLinksModel: ListItemModel, MoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + open class var identifier: String { return "listLVRBAll" } @@ -20,6 +23,7 @@ open class ListLeftVariableRadioButtonAllTextAndLinksModel: ListItemModel, Molec //-------------------------------------------------- // MARK: - Initializer //-------------------------------------------------- + public init(radioButton: RadioButtonModel, eyebrowHeadlineBodyLink: EyebrowHeadlineBodyLinkModel) { self.radioButton = radioButton self.eyebrowHeadlineBodyLink = eyebrowHeadlineBodyLink @@ -29,6 +33,7 @@ open class ListLeftVariableRadioButtonAllTextAndLinksModel: ListItemModel, Molec //-------------------------------------------------- // MARK: - Keys //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { case moleculeName case eyebrowHeadlineBodyLink @@ -38,6 +43,7 @@ open class ListLeftVariableRadioButtonAllTextAndLinksModel: ListItemModel, Molec //-------------------------------------------------- // MARK: - Codec //-------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) radioButton = try typeContainer.decode(RadioButtonModel.self, forKey: .radioButton) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift index 7ee0b619..4eaa3fd4 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethod.swift @@ -43,6 +43,7 @@ import UIKit override open func setupView() { super.setupView() + leftImage.addSizeConstraintsForAspectRatio = true addMolecule(stack) stack.restack() @@ -56,6 +57,7 @@ import UIKit observation = observe(\.radioButton.isSelected, options: [.new]) { [weak self] _, _ in self?.updateAccessibilityLabel() } + updateAccessibilityLabel() } open override func reset() { @@ -70,7 +72,9 @@ import UIKit open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + guard let model = model as? ListLeftVariableRadioButtonAndPaymentMethodModel else { return} + radioButton.set(with: model.radioButton, delegateObject, additionalData) leftImage.set(with: model.image, delegateObject, additionalData) eyebrowHeadlineBodyLink.set(with: model.eyebrowHeadlineBodyLink, delegateObject, additionalData) @@ -86,20 +90,27 @@ import UIKit } func updateAccessibilityLabel() { + var message = "" + radioButton.updateAccessibilityLabel() + if let radioButtonLabel = radioButton.accessibilityLabel { message += radioButtonLabel + ", " } + if let leftImageLabel = leftImage.accessibilityLabel { message += leftImageLabel + ", " } + if let eyebrowLabel = eyebrowHeadlineBodyLink.eyebrow.text { message += eyebrowLabel + ", " } + if let headlineLabel = eyebrowHeadlineBodyLink.headline.text { message += headlineLabel + ", " } + if let bodyLabel = eyebrowHeadlineBodyLink.body.text { message += bodyLabel } @@ -115,14 +126,16 @@ import UIKit } else { // Allow only radio button and link to be focused on. radioButton.accessibilityLabel = message - var elements: [UIView] = [] - if message.count > 0 { + + if !message.isEmpty { elements.append(radioButton) } - if eyebrowHeadlineBodyLink.link.titleLabel?.text?.count ?? 0 > 0 { + + if linkShowing { elements.append(eyebrowHeadlineBodyLink.link) } + accessibilityElements = elements } } diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethodModel.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethodModel.swift index f455882e..551ce585 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethodModel.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/LeftVariable/ListLeftVariableRadioButtonAndPaymentMethodModel.swift @@ -8,6 +8,7 @@ import Foundation + public class ListLeftVariableRadioButtonAndPaymentMethodModel: ListItemModel, MoleculeModelProtocol { //-------------------------------------------------- // MARK: - Properties diff --git a/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift b/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift new file mode 100644 index 00000000..158f9c11 --- /dev/null +++ b/MVMCoreUI/BaseClasses/Protocols/AccessibilityProtocol.swift @@ -0,0 +1,14 @@ +// +// AccessibilityProtocol.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 6/1/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + + +public protocol AccessibilityProtocol { + func updateAccessibilityLabel() +}