From 42929810a194ba24d5be8142c455ca55d5b70e7e Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 4 Sep 2020 15:00:55 -0400 Subject: [PATCH 01/16] accessibility and formatting update --- .../TextFields/EntryFieldModel.swift | 2 +- .../BGImageHeadlineBodyButton.swift | 110 +++++++++++++----- .../BGImageHeadlineBodyButtonModel.swift | 33 +++++- .../EyebrowHeadlineBodyLink.swift | 11 +- .../EyebrowHeadlineBodyLinkModel.swift | 1 - .../HeadLineBodyCaretLinkImage.swift | 63 +++++----- .../HeadlineBody.swift | 61 ++++++---- .../HeadlineBodyButton.swift | 2 - .../HeadlineBodyButtonModel.swift | 4 +- .../HeadlineBodyCaretLinkImageModel.swift | 30 ++++- .../HeadlineBodyLink.swift | 56 ++++++--- .../HeadlineBodyLinkModel.swift | 11 +- .../ThreeHeadlineBodyLink.swift | 2 - MVMCoreUI/Atomic/Organisms/Stack.swift | 7 +- 14 files changed, 277 insertions(+), 116 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift index dd2a3c62..d64f70ae 100644 --- a/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift +++ b/MVMCoreUI/Atomic/Atoms/FormFields/TextFields/EntryFieldModel.swift @@ -34,7 +34,7 @@ import Foundation public var baseValue: AnyHashable? public var wasInitiallySelected: Bool = false - public var isValid: Bool? { + public var isValid: Bool? = true { didSet { updateUI?() } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButton.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButton.swift index 946022ac..bac1b1c2 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButton.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButton.swift @@ -6,82 +6,136 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation @objcMembers public class BGImageHeadlineBodyButton: Container { - let headlineBody = HeadlineBody(frame: .zero) - let button = PillButton(frame: .zero) + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + let headlineBody = HeadlineBody() + let button = PillButton() let backgroundImageView = LoadImageView(pinnedEdges: .all) - let maxWidth: CGFloat = 350.0 - static let heightConstant: CGFloat = 320.0 + + //-------------------------------------------------- + // MARK: - Property + //-------------------------------------------------- + + let maxWidth: CGFloat = 350 + static let heightConstant: CGFloat = 320 + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + var heightConstraint: NSLayoutConstraint? - - // MARK: - MVMCoreViewProtocol - open override func updateView(_ size: CGFloat) { - super.updateView(size) - headlineBody.updateView(size) - button.updateView(size) - backgroundImageView.updateView(size) - backgroundImageView.pinEdges(.all) - } - + + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- + open override func setupView() { super.setupView() + heightConstraint = heightAnchor.constraint(equalToConstant: Self.heightConstant) heightConstraint?.isActive = true backgroundImageView.addSizeConstraintsForAspectRatio = true - + let container = MVMCoreUICommonViewsUtility.commonView() addAndContain(container) container.addSubview(headlineBody) container.addSubview(button) - //Headline view - headlineBody.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0).isActive = true - headlineBody.topAnchor.constraint(equalTo: container.topAnchor, constant: 0).isActive = true - + headlineBody.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true + headlineBody.topAnchor.constraint(equalTo: container.topAnchor).isActive = true + let headLineBodyWidth = headlineBody.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.67) headLineBodyWidth.priority = UILayoutPriority(rawValue: 999) headLineBodyWidth.isActive = true headlineBody.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth).isActive = true - //Caret view button.translatesAutoresizingMaskIntoConstraints = false - button.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0).isActive = true - container.bottomAnchor.constraint(greaterThanOrEqualTo: button.bottomAnchor, constant: 0).isActive = true - + button.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true + container.bottomAnchor.constraint(greaterThanOrEqualTo: button.bottomAnchor).isActive = true button.topAnchor.constraint(equalTo: headlineBody.bottomAnchor, constant: PaddingDefault).isActive = true - //Background image view backgroundImageView.translatesAutoresizingMaskIntoConstraints = false backgroundImageView.imageView.contentMode = .scaleAspectFill backgroundImageView.pinEdges(.all) addSubview(backgroundImageView) NSLayoutConstraint.constraintPinSubview(toSuperview: backgroundImageView) sendSubviewToBack(backgroundImageView) - } + isAccessibilityElement = true + accessibilityHint = button.accessibilityHint + accessibilityTraits = button.accessibilityTraits + } + + //-------------------------------------------------- // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineBody.updateView(size) + button.updateView(size) + backgroundImageView.updateView(size) + backgroundImageView.pinEdges(.all) + } + open override func reset() { super.reset() headlineBody.reset() backgroundImageView.reset() } - // MARK:- MoleculeViewProtocol public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 320 } - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + guard let model = model as? BGImageHeadlineBodyButtonModel else { return } + headlineBody.set(with: model.headlineBody, delegateObject, additionalData) button.setOptional(with: model.button, delegateObject, additionalData) button.isHidden = model.button == nil backgroundImageView.set(with: model.image, delegateObject, additionalData) backgroundImageView.pinEdges(.all) + updateAccessibilityLabel() + } + + //---------------------------------------------------- + // MARK: - Accessibility + //---------------------------------------------------- + + func updateAccessibilityLabel() { + + var message = "" + + if let headline = headlineBody.headlineLabel.text, !headline.isEmpty { + message += headline + ", " + } + + if let body = headlineBody.messageLabel.text, !body.isEmpty { + message += body + ", " + } + + if let backgroundImageText = backgroundImageView.accessibilityLabel, !backgroundImageText.isEmpty { + message += backgroundImageText + ", " + } + + if let buttonLabel = button.accessibilityLabel, !buttonLabel.isEmpty { + message += buttonLabel + } + + accessibilityLabel = message + } + + open override func accessibilityActivate() -> Bool { + + return button.accessibilityActivate() } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift index 040b368c..7c4215c2 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButtonModel.swift @@ -6,42 +6,62 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import UIKit public class BGImageHeadlineBodyButtonModel: ContainerModel, MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "bgImageHeadlineBodyButton" public var backgroundColor: Color? public var button: ButtonModel? public var headlineBody: HeadlineBodyModel public var image: ImageViewModel + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + init(headlineBody: HeadlineBodyModel, image: ImageViewModel) { self.headlineBody = headlineBody self.image = image super.init() } - /// Defaults to set + //-------------------------------------------------- + // MARK: - Defaults + //-------------------------------------------------- + public override func setDefaults() { + if useHorizontalMargins == nil { useHorizontalMargins = true } + if useVerticalMargins == nil { useVerticalMargins = true } + if topPadding == nil { topPadding = PaddingDefault } + if bottomPadding == nil { bottomPadding = PaddingDefault } + if image.height == nil { image.height = BGImageHeadlineBodyButton.heightConstant } + button?.size = .tiny button?.style = .secondary } + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { case moleculeName case backgroundColor @@ -49,7 +69,11 @@ public class BGImageHeadlineBodyButtonModel: ContainerModel, MoleculeModelProtoc case image case button } - + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) @@ -58,7 +82,7 @@ public class BGImageHeadlineBodyButtonModel: ContainerModel, MoleculeModelProtoc button = try typeContainer.decodeIfPresent(ButtonModel.self, forKey: .button) try super.init(from: decoder) } - + public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) @@ -69,4 +93,3 @@ public class BGImageHeadlineBodyButtonModel: ContainerModel, MoleculeModelProtoc try container.encodeIfPresent(button, forKey: .button) } } - diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift index 12b42e9f..ca76f0fb 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLink.swift @@ -6,7 +6,6 @@ // Copyright © 2019 Verizon Wireless. All rights reserved. // -import UIKit @objcMembers open class EyebrowHeadlineBodyLink: View { //-------------------------------------------------- @@ -76,8 +75,9 @@ import UIKit /// Returns the labels text in one message. func getAccessibilityMessage() -> String? { + var message = "" - + if let eyebrowLabel = eyebrow.text { message += eyebrowLabel + ", " } @@ -89,24 +89,31 @@ import UIKit if let bodyLabel = body.text { message += bodyLabel } + return message.count > 0 ? message : nil } /// Returns an array of the appropriate accessibility elements. func getAccessibilityElements() -> [Any]? { + var elements: [UIView] = [] + if eyebrow.hasText { elements.append(eyebrow) } + if headline.hasText { elements.append(headline) } + if body.hasText { elements.append(body) } + if link.titleLabel?.text?.count ?? 0 > 0 { elements.append(link) } + return elements.count > 0 ? elements : nil } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift index 0f158c2c..c9410775 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/EyebrowHeadlineBodyLinkModel.swift @@ -6,7 +6,6 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation public class EyebrowHeadlineBodyLinkModel: MoleculeModelProtocol { //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift index 114fd3d4..9afc2263 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadLineBodyCaretLinkImage.swift @@ -6,77 +6,88 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation + @objcMembers public class HeadLineBodyCaretLinkImage: Container { - let headlineBody = HeadlineBody(frame: .zero) - let caretButton = CaretLink(frame: .zero) + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + let headlineBody = HeadlineBody() + let caretButton = CaretLink() let backgroundImageView = LoadImageView(pinnedEdges: .all) - let maxWidth: CGFloat = 350.0 - static let heightConstant: CGFloat = 320.0 + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + let maxWidth: CGFloat = 350 + static let heightConstant: CGFloat = 320 var heightConstraint: NSLayoutConstraint? - - // MARK: - MVMCoreViewProtocol - open override func updateView(_ size: CGFloat) { - super.updateView(size) - headlineBody.updateView(size) - caretButton.updateView(size) - backgroundImageView.updateView(size) - backgroundImageView.pinEdges(.all) - } - + + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- + open override func setupView() { super.setupView() + heightConstraint = heightAnchor.constraint(equalToConstant: Self.heightConstant) heightConstraint?.isActive = true backgroundImageView.addSizeConstraintsForAspectRatio = true - + let container = MVMCoreUICommonViewsUtility.commonView() addAndContain(container) container.addSubview(headlineBody) container.addSubview(caretButton) - //Headline view headlineBody.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0).isActive = true headlineBody.topAnchor.constraint(equalTo: container.topAnchor, constant: 0).isActive = true - + headlineBody.widthAnchor.constraint(equalTo: container.widthAnchor, multiplier: 0.67).isActive = true let headLineBodyWidth = headlineBody.widthAnchor.constraint(lessThanOrEqualToConstant: maxWidth) headLineBodyWidth.priority = UILayoutPriority(250) headLineBodyWidth.isActive = true - //Caret view - caretButton.translatesAutoresizingMaskIntoConstraints = false caretButton.leadingAnchor.constraint(equalTo: container.leadingAnchor, constant: 0).isActive = true container.bottomAnchor.constraint(equalTo: caretButton.bottomAnchor, constant: 0).isActive = true - caretButton.topAnchor.constraint(greaterThanOrEqualTo: headlineBody.bottomAnchor, constant: PaddingTwo).isActive = true - //Background image view - backgroundImageView.translatesAutoresizingMaskIntoConstraints = false backgroundImageView.imageView.contentMode = .scaleAspectFill backgroundImageView.pinEdges(.all) addSubview(backgroundImageView) NSLayoutConstraint.constraintPinSubview(toSuperview: backgroundImageView) sendSubviewToBack(backgroundImageView) } - + + //-------------------------------------------------- // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineBody.updateView(size) + caretButton.updateView(size) + backgroundImageView.updateView(size) + backgroundImageView.pinEdges(.all) + } + open override func reset() { super.reset() headlineBody.reset() backgroundImageView.reset() } - // MARK:- MoleculeViewProtocol public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 320 } - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + guard let model = model as? HeadlineBodyCaretLinkImageModel else { return } + headlineBody.set(with: model.headlineBody, delegateObject, additionalData) caretButton.setOptional(with: model.caretLink, delegateObject, additionalData) caretButton.isHidden = model.caretLink == nil diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift index 93b6fa68..48d6c129 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift @@ -6,12 +6,19 @@ // Copyright © 2019 Verizon Wireless. All rights reserved. // -import UIKit open class HeadlineBody: View { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- let headlineLabel = Label(fontStyle: .BoldTitleLarge) let messageLabel = Label(fontStyle: .RegularBodySmall) + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + var spaceBetweenLabelsConstant = PaddingOne var spaceBetweenLabels: NSLayoutConstraint? var leftConstraintTitle: NSLayoutConstraint? @@ -19,21 +26,30 @@ open class HeadlineBody: View { var leftConstraintMessage: NSLayoutConstraint? var rightConstraintMessage: NSLayoutConstraint? + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + func hasText() -> Bool { return headlineLabel.hasText || messageLabel.hasText } // MARK: - Styling func style(with style: HeadlineBodyModel.Style?) { + switch style { case .landingHeader: styleLandingPageHeader() + case .header: stylePageHeader() + case .item: styleListItem() + case .itemHeader: styleListItemDivider() + default: break } } @@ -61,18 +77,14 @@ open class HeadlineBody: View { messageLabel.setFontStyle(.RegularBodySmall) spaceBetweenLabelsConstant = 0 } - - // MARK: - MVMCoreViewProtocol - open override func updateView(_ size: CGFloat) { - super.updateView(size) - headlineLabel.updateView(size) - messageLabel.updateView(size) - setSpacing() - } + + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- open override func setupView() { super.setupView() - + backgroundColor = .clear clipsToBounds = true @@ -86,35 +98,35 @@ open class HeadlineBody: View { view.addSubview(headlineLabel) view.addSubview(messageLabel) - + headlineLabel.setContentHuggingPriority(.required, for: .vertical) messageLabel.setContentHuggingPriority(.required, for: .vertical) view.setContentHuggingPriority(.required, for: .vertical) - - headlineLabel.topAnchor.constraint(equalTo: view.topAnchor, constant: 0).isActive = true - + + headlineLabel.topAnchor.constraint(equalTo: view.topAnchor).isActive = true + spaceBetweenLabels = messageLabel.topAnchor.constraint(equalTo: headlineLabel.bottomAnchor, constant: spaceBetweenLabelsConstant) spaceBetweenLabels?.isActive = true - + leftConstraintTitle = headlineLabel.leftAnchor.constraint(equalTo: view.leftAnchor) leftConstraintTitle?.isActive = true - + rightConstraintTitle = view.rightAnchor.constraint(equalTo: headlineLabel.rightAnchor) rightConstraintTitle?.isActive = true - + leftConstraintMessage = messageLabel.leftAnchor.constraint(equalTo: view.leftAnchor) leftConstraintMessage?.isActive = true rightConstraintMessage = view.rightAnchor.constraint(equalTo: messageLabel.rightAnchor) rightConstraintMessage?.isActive = true - view.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor, constant: 0).isActive = true + view.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor).isActive = true } //-------------------------------------------------- // MARK: - Constraining //-------------------------------------------------- - + public func setSpacing() { if headlineLabel.hasText && messageLabel.hasText { spaceBetweenLabels?.constant = spaceBetweenLabelsConstant @@ -122,11 +134,18 @@ open class HeadlineBody: View { spaceBetweenLabels?.constant = 0 } } - + //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineLabel.updateView(size) + messageLabel.updateView(size) + setSpacing() + } + public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 58 } @@ -135,7 +154,7 @@ open class HeadlineBody: View { super.set(with: model, delegateObject, additionalData) guard let headlineBodyModel = model as? HeadlineBodyModel else { return } - + style(with: headlineBodyModel.style) headlineLabel.setOptional(with: headlineBodyModel.headline, delegateObject, additionalData) diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift index 5170d8f2..8e22a1df 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButton.swift @@ -6,8 +6,6 @@ // Copyright © 2019 Verizon Wireless. All rights reserved. // -import UIKit - @objcMembers open class HeadlineBodyButton: View { //------------------------------------------------------ diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift index cd1912ca..cff8be23 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyButtonModel.swift @@ -6,8 +6,6 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation - public class HeadlineBodyButtonModel: MoleculeModelProtocol { //-------------------------------------------------- @@ -17,7 +15,7 @@ public class HeadlineBodyButtonModel: MoleculeModelProtocol { public static var identifier: String = "headlineBodyButton" public var moleculeName: String = HeadlineBodyButtonModel.identifier public var backgroundColor: Color? - + public var headlineBody: HeadlineBodyModel public var button: ButtonModel public var buttonHeadlinePadding: CGFloat diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift index 9fdd8612..2a3d844a 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyCaretLinkImageModel.swift @@ -6,40 +6,60 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import UIKit public class HeadlineBodyCaretLinkImageModel: ContainerModel, MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "headlineBodyCaretLinkImage" public var backgroundColor: Color? public var caretLink: CaretLinkModel? public var headlineBody: HeadlineBodyModel public var image: ImageViewModel + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + init(headlineBody: HeadlineBodyModel, image: ImageViewModel) { self.headlineBody = headlineBody self.image = image super.init() } + //-------------------------------------------------- + // MARK: - Defaults + //-------------------------------------------------- + /// Defaults to set public override func setDefaults() { + if useHorizontalMargins == nil { useHorizontalMargins = true } + if useVerticalMargins == nil { useVerticalMargins = true } + if topPadding == nil { topPadding = PaddingDefault } + if bottomPadding == nil { bottomPadding = PaddingDefault } + if image.height == nil { image.height = HeadLineBodyCaretLinkImage.heightConstant } } + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { case moleculeName case backgroundColor @@ -47,7 +67,11 @@ public class HeadlineBodyCaretLinkImageModel: ContainerModel, MoleculeModelProto case image case caretLink } - + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) @@ -56,7 +80,7 @@ public class HeadlineBodyCaretLinkImageModel: ContainerModel, MoleculeModelProto caretLink = try typeContainer.decodeIfPresent(CaretLinkModel.self, forKey: .caretLink) try super.init(from: decoder) } - + public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyLink.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyLink.swift index da5014a9..550434a8 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyLink.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyLink.swift @@ -6,33 +6,41 @@ // Copyright © 2019 Verizon Wireless. All rights reserved. // -import UIKit @objcMembers public class HeadlineBodyLink: View { - - let headlineBody = HeadlineBody(frame: .zero) + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + let headlineBody = HeadlineBody() let link = Link() - var spaceBetweenConstant: CGFloat = 0.0 + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + var spaceBetweenConstant: CGFloat = 0 + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + var spaceBetween: NSLayoutConstraint? - // MARK: - MVMCoreViewProtocol - open override func updateView(_ size: CGFloat) { - super.updateView(size) - headlineBody.updateView(size) - link.updateView(size) - setSpacing() - } + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- open override func setupView() { super.setupView() - guard subviews.count == 0 else { - return - } + + guard subviews.isEmpty else { return } + addSubview(headlineBody) addSubview(link) headlineBody.styleListItem() - headlineBody.topAnchor.constraint(equalTo: topAnchor, constant: 0).isActive = true + headlineBody.topAnchor.constraint(equalTo: topAnchor).isActive = true headlineBody.leftAnchor.constraint(equalTo: leftAnchor).isActive = true var constraint = rightAnchor.constraint(equalTo: headlineBody.rightAnchor) constraint.priority = .defaultHigh @@ -49,7 +57,10 @@ import UIKit constraint.isActive = true } - // MARK: - Constraining + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + public func setSpacing() { if headlineBody.hasText() && (link.titleLabel?.text?.count ?? 0) > 0 { spaceBetween?.constant = spaceBetweenConstant @@ -58,7 +69,17 @@ import UIKit } } - // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + // MARK: - MVMCoreViewProtocol + //-------------------------------------------------- + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineBody.updateView(size) + link.updateView(size) + setSpacing() + } + open override func reset() { super.reset() headlineBody.reset() @@ -66,7 +87,6 @@ import UIKit link.reset() } - // MARK:- MoleculeViewProtocol open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) guard let model = model as? HeadlineBodyLinkModel else { return } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyLinkModel.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyLinkModel.swift index 4ef4018b..f8a0d57e 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyLinkModel.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBodyLinkModel.swift @@ -8,13 +8,22 @@ import Foundation + public struct HeadlineBodyLinkModel: MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "headlineBodyLink" public var moleculeName: String = HeadlineBodyLinkModel.identifier public var headlineBody: HeadlineBodyModel public var link: LinkModel public var backgroundColor: Color? - + + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + public init(headlineBody: HeadlineBodyModel, link: LinkModel) { self.headlineBody = headlineBody self.link = link diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/ThreeHeadlineBodyLink.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/ThreeHeadlineBodyLink.swift index 5926951f..a34107b9 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/ThreeHeadlineBodyLink.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/ThreeHeadlineBodyLink.swift @@ -6,8 +6,6 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import UIKit - open class ThreeHeadlineBodyLink: View { //------------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Organisms/Stack.swift b/MVMCoreUI/Atomic/Organisms/Stack.swift index ad961586..5957f4c9 100644 --- a/MVMCoreUI/Atomic/Organisms/Stack.swift +++ b/MVMCoreUI/Atomic/Organisms/Stack.swift @@ -66,11 +66,12 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto open func updateContainedMolecules(with models: [MoleculeModelProtocol?], _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { guard var stackModel = stackModel else { return } var needsRestack = false + for (index, item) in stackItems.enumerated() { guard let container = item as? UIView & ContainerProtocol, - let contained = container.view as? MoleculeViewProtocol else { - continue - } + let contained = container.view as? MoleculeViewProtocol + else { continue } + if let model = models[index] { contained.set(with: model, delegateObject, additionalData) if stackModel.molecules[index].gone { From 5326a3afd6192ad1c862e579a22014449554ad99 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 8 Sep 2020 09:22:25 -0400 Subject: [PATCH 02/16] changes for accessibility --- MVMCoreUI/Atomic/Organisms/Stack.swift | 4 +--- MVMCoreUI/Containers/Views/Container.swift | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/MVMCoreUI/Atomic/Organisms/Stack.swift b/MVMCoreUI/Atomic/Organisms/Stack.swift index 5957f4c9..194eff48 100644 --- a/MVMCoreUI/Atomic/Organisms/Stack.swift +++ b/MVMCoreUI/Atomic/Organisms/Stack.swift @@ -57,9 +57,7 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto /// Removes all stack items views from the view. open func removeAllItemViews() { - for item in stackItems { - item.removeFromSuperview() - } + stackItems.forEach { $0.removeFromSuperview() } } /// A convenience function for when the stackItems are containers and we want to update them based on the contained molecules models. If model is nil, stackItem is set to gone. Restacks if necessary. diff --git a/MVMCoreUI/Containers/Views/Container.swift b/MVMCoreUI/Containers/Views/Container.swift index 8c95f110..6cd72e95 100644 --- a/MVMCoreUI/Containers/Views/Container.swift +++ b/MVMCoreUI/Containers/Views/Container.swift @@ -54,6 +54,7 @@ public extension Container { /// Will be called only once. override func setupView() { super.setupView() + isAccessibilityElement = false backgroundColor = .clear } From 5aee74282bf213b58744f4d0380871cae842ca90 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 9 Sep 2020 15:41:28 -0400 Subject: [PATCH 03/16] changes for accessibility. formatting and spacing --- .../Atomic/Atoms/Buttons/Link/Link.swift | 1 + .../Atomic/Atoms/Views/ImageViewModel.swift | 13 +++- .../Atomic/Atoms/Views/Label/Label.swift | 1 + .../Device/ListDeviceComplexButtonSmall.swift | 7 ++- .../TwoButtonView.swift | 8 +++ .../Items/MoleculeStackItemModel.swift | 21 ++++++- .../Atomic/Molecules/Items/StackItem.swift | 1 - .../Atomic/Molecules/MoleculeHeaderView.swift | 14 ++++- .../OtherContainers/MoleculeContainer.swift | 22 ++++--- .../StringAndMoleculeStack.swift | 6 +- .../StringAndMoleculeView.swift | 12 ++-- .../Atomic/Organisms/MoleculeStackView.swift | 16 +++-- MVMCoreUI/Atomic/Organisms/Stack.swift | 59 +++++++++++++++---- MVMCoreUI/Atomic/Organisms/StackModel.swift | 26 ++++++-- .../Templates/MoleculeListTemplate.swift | 6 +- .../Templates/MoleculeStackTemplate.swift | 37 ++++++++---- MVMCoreUI/BaseClasses/Button.swift | 7 ++- MVMCoreUI/BaseClasses/TableViewCell.swift | 3 +- .../ThreeLayerTableViewController.swift | 3 +- .../BaseControllers/ViewController.swift | 4 +- MVMCoreUI/Containers/Views/Container.swift | 19 +++++- 21 files changed, 217 insertions(+), 69 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift index 7413fcbb..d484840d 100644 --- a/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift +++ b/MVMCoreUI/Atomic/Atoms/Buttons/Link/Link.swift @@ -51,6 +51,7 @@ import UIKit guard let model = model as? LinkModel else { return } setTitle(model.title, for: .normal) + accessibilityLabel = model.title setTitleColor((model.inverted ? model.enabledColor_inverted : model.enabledColor).uiColor, for: .normal) setTitleColor((model.inverted ? model.disabledColor_inverted : model.disabledColor).uiColor, for: .disabled) isEnabled = model.enabled diff --git a/MVMCoreUI/Atomic/Atoms/Views/ImageViewModel.swift b/MVMCoreUI/Atomic/Atoms/Views/ImageViewModel.swift index 57a82052..86add716 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/ImageViewModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/ImageViewModel.swift @@ -6,9 +6,12 @@ // Copyright © 2019 Verizon Wireless. All rights reserved. // -import Foundation @objcMembers public class ImageViewModel: MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "image" public var backgroundColor: Color? public var moleculeName: String = ImageViewModel.identifier @@ -22,6 +25,10 @@ import Foundation public var localBundle: Bundle? public var cornerRadius: CGFloat? + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + public init(image: String, imageFormat: String? = nil, width: CGFloat? = nil, height: CGFloat? = nil) { self.image = image self.imageFormat = imageFormat @@ -29,6 +36,10 @@ import Foundation self.height = height } + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { case moleculeName case backgroundColor diff --git a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift index 73de7760..21dcff80 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Label/Label.swift @@ -249,6 +249,7 @@ public typealias ActionBlock = () -> () attributedText = nil originalAttributedString = nil + text = nil text = labelModel.text hero = labelModel.hero Label.setLabel(self, withHTML: labelModel.html) diff --git a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmall.swift b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmall.swift index cd43215b..e8d4df30 100644 --- a/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmall.swift +++ b/MVMCoreUI/Atomic/Molecules/DesignedComponents/List/Device/ListDeviceComplexButtonSmall.swift @@ -6,8 +6,6 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation - @objcMembers open class ListDeviceComplexButtonSmall: TableViewCell { //-------------------------------------------------- @@ -19,7 +17,7 @@ import Foundation public let headline = Label(fontStyle: .BoldTitleMedium) public let body = Label(fontStyle: .RegularBodySmall) public let body2 = Label(fontStyle: .RegularBodySmall) - public let button = PillButton(frame: .zero) + public let button = PillButton() public let rightImageView = LoadImageView() public var stack: Stack @@ -99,6 +97,7 @@ import Foundation //-------------------------------------------------- func getAccessibilityMessage() -> String? { + var message = "" if let eyebrowText = eyebrow.text, !eyebrowText.isEmpty { message += eyebrowText + ", " @@ -119,10 +118,12 @@ import Foundation if let rightImageViewText = rightImageView.imageView.accessibilityLabel, !rightImageViewText.isEmpty { message += rightImageViewText } + return message.count > 0 ? message : nil } func updateAccessibilityLabel() { + if let accessoryView = accessoryView { // Both caret and button. Read all content on caret. isAccessibilityElement = false diff --git a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonView.swift b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonView.swift index 31420e56..7dbea868 100644 --- a/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonView.swift +++ b/MVMCoreUI/Atomic/Molecules/HorizontalCombinationViews/TwoButtonView.swift @@ -8,6 +8,7 @@ import UIKit + @objcMembers open class TwoButtonView: View, MVMCoreUIViewConstrainingProtocol { //-------------------------------------------------- // MARK: - Properties @@ -60,6 +61,7 @@ import UIKit super.setupView() stack.translatesAutoresizingMaskIntoConstraints = false + isAccessibilityElement = false addSubview(stack) stack.addArrangedSubview(secondaryButton) stack.addArrangedSubview(primaryButton) @@ -84,6 +86,8 @@ import UIKit if secondaryButton.superview != nil { equalWidthConstraint?.isActive = true } + + primaryButton.isAccessibilityElement = true } public func showSecondaryButton() { @@ -96,6 +100,8 @@ import UIKit if primaryButton.superview != nil { equalWidthConstraint?.isActive = true } + + secondaryButton.isAccessibilityElement = true } public func hidePrimaryButton() { @@ -105,6 +111,7 @@ import UIKit primaryButton.isHidden = true } + primaryButton.isAccessibilityElement = false equalWidthConstraint?.isActive = false } @@ -115,6 +122,7 @@ import UIKit secondaryButton.isHidden = true } + secondaryButton.isAccessibilityElement = false equalWidthConstraint?.isActive = false } diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift index 1f6e4d88..8464b520 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeStackItemModel.swift @@ -6,9 +6,12 @@ // Copyright © 2019 Suresh, Kamlesh. All rights reserved. // -import Foundation @objcMembers public class MoleculeStackItemModel: MoleculeContainerModel, StackItemModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public override class var identifier: String { return "stackItem" } @@ -16,16 +19,28 @@ import Foundation public var percent: Int? public var gone: Bool = false + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { case spacing case percent case gone } + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + public override init(with moleculeModel: MoleculeModelProtocol) { super.init(with: moleculeModel) } - + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) spacing = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .spacing) @@ -35,7 +50,7 @@ import Foundation } try super.init(from: decoder) } - + public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) diff --git a/MVMCoreUI/Atomic/Molecules/Items/StackItem.swift b/MVMCoreUI/Atomic/Molecules/Items/StackItem.swift index 07cae3d0..61d63067 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/StackItem.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/StackItem.swift @@ -6,7 +6,6 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation open class StackItem: Container { var stackItemModel: StackItemModel? { diff --git a/MVMCoreUI/Atomic/Molecules/MoleculeHeaderView.swift b/MVMCoreUI/Atomic/Molecules/MoleculeHeaderView.swift index bff5a14f..291540af 100644 --- a/MVMCoreUI/Atomic/Molecules/MoleculeHeaderView.swift +++ b/MVMCoreUI/Atomic/Molecules/MoleculeHeaderView.swift @@ -8,14 +8,22 @@ import UIKit + public class MoleculeHeaderView: MoleculeContainer { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + var line = Line() var headerModel: MoleculeHeaderModel? { get { return model as? MoleculeHeaderModel } } + //-------------------------------------------------- // MARK: - MVMCoreViewProtocol + //-------------------------------------------------- + open override func updateView(_ size: CGFloat) { super.updateView(size) line.updateView(size) @@ -30,16 +38,16 @@ public class MoleculeHeaderView: MoleculeContainer { NSLayoutConstraint.pinViewRight(toSuperview: line, useMargins: true, constant: 0).isActive = true } - // MARK: - MoleculeViewProtocol open override func reset() { super.reset() line.setStyle(.heavy) } - - // MARK: - MoleculeViewProtocol + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + guard let headerModel = headerModel else { return } + if let lineModel = headerModel.line { line.set(with: lineModel, delegateObject, additionalData) } diff --git a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainer.swift b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainer.swift index 4e2c6279..b5d78c85 100644 --- a/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainer.swift +++ b/MVMCoreUI/Atomic/Molecules/OtherContainers/MoleculeContainer.swift @@ -8,6 +8,7 @@ import UIKit + open class MoleculeContainer: Container { /// Can be overriden to change how the molecule is added to the hierarchy. @@ -16,6 +17,7 @@ open class MoleculeContainer: Container { } public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + if let casteModel = model as? MoleculeContainerModelProtocol { if view != nil { (view as? MoleculeViewProtocol)?.set(with: casteModel.molecule, delegateObject, additionalData) @@ -29,26 +31,32 @@ open class MoleculeContainer: Container { } public override static func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { + guard let containerModel = model as? MoleculeContainerModelProtocol, let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(containerModel.molecule), - let moleculeName = moleculeClass.nameForReuse(with: containerModel.molecule, delegateObject) else { - return "\(model.moleculeName)<>" - } + let moleculeName = moleculeClass.nameForReuse(with: containerModel.molecule, delegateObject) + else { return "\(model.moleculeName)<>" } + return "\(model.moleculeName)<\(moleculeName)>" } public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { + guard let containerModel = model as? MoleculeContainerModelProtocol else { return 0 } + guard let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(containerModel.molecule), - let moleculeHeight = moleculeClass.estimatedHeight(with: containerModel.molecule, delegateObject) else { - return (containerModel.topPadding ?? 0) + (containerModel.bottomPadding ?? 0) - } + let moleculeHeight = moleculeClass.estimatedHeight(with: containerModel.molecule, delegateObject) + else { return (containerModel.topPadding ?? 0) + (containerModel.bottomPadding ?? 0) } + return moleculeHeight + (containerModel.topPadding ?? 0) + (containerModel.bottomPadding ?? 0) } public override class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + guard let containerModel = model as? MoleculeContainerModelProtocol, - let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(containerModel.molecule) else { return nil } + let moleculeClass = MoleculeObjectMapping.shared()?.getMoleculeClass(containerModel.molecule) + else { return nil } + return moleculeClass.requiredModules(with: containerModel.molecule, delegateObject, error: error) } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift index 8630e607..51789688 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeStack.swift @@ -8,11 +8,15 @@ import UIKit + // This class is only temporarily necessary. Eventually we will have initWithModel instad of just init for moleculeviews, which will remove this need. open class StringAndMoleculeStack: MoleculeStackView { override open func createStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let model = model as? StackModelProtocol, - let molcules = model.molecules as? [MoleculeStackItemModel] else { return } + let molcules = model.molecules as? [MoleculeStackItemModel] + else { return } + for stackItemModel in molcules { guard let stringAndMoleculeModel = stackItemModel.molecule as? StringAndMoleculeModel, let molecule = MoleculeObjectMapping.shared()?.createMolecule(stringAndMoleculeModel.molecule, delegateObject: delegateObject diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift index 3c97e988..47084c72 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/Lists/StringAndMoleculeStack/StringAndMoleculeView.swift @@ -6,9 +6,9 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation open class StringAndMoleculeView: View { + var label = Label(fontStyle: .RegularBodySmall) var molecule: MoleculeViewProtocol @@ -38,11 +38,9 @@ open class StringAndMoleculeView: View { override public func setupView() { super.setupView() - guard subviews.count == 0 else { - return - } - translatesAutoresizingMaskIntoConstraints = false + guard subviews.count == 0 else { return } + addSubview(label) addSubview(molecule) @@ -75,7 +73,7 @@ open class StringAndMoleculeView: View { molecule.reset() } - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) guard let model = model as? StringAndMoleculeModel else { return } label.text = model.string @@ -85,7 +83,7 @@ open class StringAndMoleculeView: View { func updateLeftViewWidthConstraint(_ percent: CGFloat) { percentage = percent leftWidthConstraint?.isActive = false - leftWidthConstraint = label.widthAnchor.constraint(equalTo: widthAnchor, multiplier: CGFloat(percent/100), constant: 0) + leftWidthConstraint = label.widthAnchor.constraint(equalTo: widthAnchor, multiplier: CGFloat(percent / 100), constant: 0) leftWidthConstraint?.isActive = true } } diff --git a/MVMCoreUI/Atomic/Organisms/MoleculeStackView.swift b/MVMCoreUI/Atomic/Organisms/MoleculeStackView.swift index e0517d45..f47b2e7e 100644 --- a/MVMCoreUI/Atomic/Organisms/MoleculeStackView.swift +++ b/MVMCoreUI/Atomic/Organisms/MoleculeStackView.swift @@ -9,12 +9,16 @@ import UIKit + open class MoleculeStackView: Stack { + var previousModel: MoleculeModelProtocol? /// Convenience function, adds a molecule to a MoleculeStackItem to the MoleculeStack func setup(with views: [View], lastItem: Bool) { + var models: [MoleculeStackItemModel] = [] + for view in views { guard let model = view.model else { return } let stackItemModel = MoleculeStackItemModel(with: model) @@ -22,20 +26,22 @@ open class MoleculeStackView: Stack { stackItems.append(stackItem) models.append(stackItemModel) } + if let stackModel = stackModel { stackModel.molecules = models } else { model = StackModel(molecules: models) } + restack() } - - // MARK: - Adding to stack /// Can be subclassed to create views when we get stack item models and have no views yet - open func createStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { + open func createStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let stackItemModels = stackModel?.molecules else { return } + for model in stackItemModels { if let stackItem = MoleculeObjectMapping.shared()?.createMolecule(model, delegateObject: delegateObject, additionalData: additionalData) as? MoleculeStackItem { stackItems.append(stackItem) @@ -43,8 +49,8 @@ open class MoleculeStackView: Stack { } } - open override func setStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable : Any]?) { - // If the items in the stack are different, clear them, create new ones. + open override func setStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + // If the items in the stack are different, clear them, create new ones. if (previousModel == nil) || Self.nameForReuse(with: previousModel!, delegateObject) != Self.nameForReuse(with: model, delegateObject) { removeAllItemViews() stackItems = [] diff --git a/MVMCoreUI/Atomic/Organisms/Stack.swift b/MVMCoreUI/Atomic/Organisms/Stack.swift index 194eff48..e70485a7 100644 --- a/MVMCoreUI/Atomic/Organisms/Stack.swift +++ b/MVMCoreUI/Atomic/Organisms/Stack.swift @@ -6,8 +6,6 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation - open class Stack: Container where T: (StackModelProtocol & MoleculeModelProtocol) { //-------------------------------------------------- @@ -16,6 +14,7 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto open var contentView: UIView = MVMCoreUICommonViewsUtility.commonView() open var stackItems: [UIView] = [] + var didSetAcessibilityElements = false open var stackModel: T? { get { return model as? T } @@ -24,7 +23,7 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto //-------------------------------------------------- // MARK: - Helpers //-------------------------------------------------- - + open func pinView(_ view: UIView, toView: UIView, attribute: NSLayoutConstraint.Attribute, relation: NSLayoutConstraint.Relation, priority: UILayoutPriority, constant: CGFloat) { let constraint = NSLayoutConstraint(item: view, attribute: attribute, relatedBy: relation, toItem: toView, attribute: attribute, multiplier: 1.0, constant: constant) @@ -34,8 +33,11 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto /// Restacks the existing items. open func restack() { + removeAllItemViews() + guard let stackModel = stackModel else { return } + let stackItems = self.stackItems self.stackItems = [] let lastItemIndex = stackModel.molecules.lastIndex { !$0.gone } @@ -46,13 +48,24 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto addView(view, stackModel.molecules[index], totalSpacing: totalSpace, lastItem: lastItemIndex == index) } + // setAccessibilityElements() + } + + open func setAccessibilityElements() { + + guard !didSetAcessibilityElements, + let stackModel = stackModel + else { return } + isAccessibilityElement = false var accessibleViews: [Any] = [] + for (index, view) in stackItems.enumerated() where !stackModel.molecules[index].gone { accessibleViews.append(view) } accessibilityElements = accessibleViews + didSetAcessibilityElements = true } /// Removes all stack items views from the view. @@ -62,6 +75,7 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto /// A convenience function for when the stackItems are containers and we want to update them based on the contained molecules models. If model is nil, stackItem is set to gone. Restacks if necessary. open func updateContainedMolecules(with models: [MoleculeModelProtocol?], _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard var stackModel = stackModel else { return } var needsRestack = false @@ -90,7 +104,7 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- - + public override init(frame: CGRect) { super.init(frame: frame) } @@ -114,12 +128,15 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto /// Returns a Stack created with a StackModel and StackItems containing the passed in views. public static func createStack(with views: [UIView], axis: NSLayoutConstraint.Axis? = nil, spacing: CGFloat? = nil) -> Stack { + var items: [StackItem] = [] var models: [StackItemModel] = [] + for view in views { items.append(StackItem(andContain: view)) models.append(StackItemModel()) } + let model = StackModel(molecules: models, axis: axis, spacing: spacing) return Stack(with: model, stackItems: items) } @@ -128,10 +145,12 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto public static func createStack(with viewModels:[(view: UIView, model: StackItemModel)], axis: NSLayoutConstraint.Axis? = nil, spacing: CGFloat? = nil) -> Stack { var stackItems: [StackItem] = [] var models: [StackItemModel] = [] + for item in viewModels { stackItems.append(StackItem(andContain: item.view)) models.append(item.model) } + let model = StackModel(molecules: models, axis: axis, spacing: spacing) return Stack(with: model, stackItems: stackItems) } @@ -139,10 +158,12 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto //-------------------------------------------------- // MARK: - MFViewProtocol //-------------------------------------------------- - + open override func setupView() { super.setupView() + guard contentView.superview == nil else { return } + MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) translatesAutoresizingMaskIntoConstraints = false backgroundColor = .clear @@ -162,7 +183,7 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto //-------------------------------------------------- // MARK: - MoleculeViewProtocol //-------------------------------------------------- - + open override func reset() { super.reset() backgroundColor = .clear @@ -217,13 +238,16 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto } open override class func requiredModules(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, error: AutoreleasingUnsafeMutablePointer?) -> [String]? { + guard let model = model as? T else { return nil } var modules: [String] = [] + for case let item in model.molecules { if let modulesForMolecule = (MoleculeObjectMapping.shared()?.getMoleculeClass(item))?.requiredModules(with: item, delegateObject, error: error) { modules += modulesForMolecule } } + return modules.count > 0 ? modules : nil } @@ -233,7 +257,9 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto /// Can be subclassed to set stack items with model when we already have views open func setStackItemsFromModel(_ model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let models = stackModel?.molecules else { return } + for (index, element) in models.enumerated() { (stackItems[index] as? MoleculeViewProtocol)?.set(with: element, delegateObject, additionalData) } @@ -244,31 +270,39 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto //-------------------------------------------------- /// Sets the stack with StackItems containing the passed in views and creates a StackModel with StackItems. open func setAndCreateModel(with views: [UIView]) { + var stackItems: [StackItem] = [] var models: [StackItemModel] = [] + for view in views { stackItems.append(StackItem(andContain: view)) models.append(StackItemModel()) } + self.stackItems = stackItems model = StackModel(molecules: models) } /// Sets the stack with StackItems containing the passed in views and sets the StackModel with models. open func set(with viewModels:[(view: UIView, model: StackItemModel)]) { + guard var stackModel = self.stackModel else { return } + var stackItems: [StackItem] = [] var models: [StackItemModel] = [] + for item in viewModels { stackItems.append(StackItem(andContain: item.view)) models.append(item.model) } + stackModel.molecules = models self.stackItems = stackItems } - + /// Gets the percent modifier. This value is used to help properly calculate percent for stack items when spacing is involved. private func getTotalSpace() -> CGFloat { + guard let stackModel = stackModel else { return 0.0 } var totalSpace: CGFloat = 0.0 var firstMoleculeFound = false @@ -283,11 +317,13 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto totalSpace += (stackModel.useStackSpacingBeforeFirstItem ? spacing : stackItemModel.spacing ?? 0) } } + return totalSpace } /// Adds the stack item view private func addView(_ view: UIView,_ model: StackItemModelProtocol, totalSpacing: CGFloat, lastItem: Bool) { + guard let stackModel = self.stackModel else { return } guard !model.gone else { // Gone views do not show @@ -296,15 +332,15 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto } contentView.addSubview(view) view.translatesAutoresizingMaskIntoConstraints = false - + let spacing = model.spacing ?? stackModel.spacing if let container = view as? ContainerProtocol { let verticalAlignment = (model as? ContainerModelProtocol)?.verticalAlignment ?? (container.view as? MVMCoreUIViewConstrainingProtocol)?.verticalAlignment?() ?? (model.percent == nil && stackModel.axis == .vertical ? .fill : (stackModel.axis == .vertical ? .leading : .center)) let horizontalAlignment = (model as? ContainerModelProtocol)?.horizontalAlignment ?? (container.view as? MVMCoreUIViewConstrainingProtocol)?.horizontalAlignment?() ?? (stackModel.axis == .vertical || model.percent == nil ? .fill : .leading) - container.alignHorizontal(horizontalAlignment) - container.alignVertical(verticalAlignment) + container.alignHorizontal(horizontalAlignment) + container.alignVertical(verticalAlignment) } - + let first = contentView.subviews.count == 1 if stackModel.axis == .vertical { if first { @@ -344,6 +380,7 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto pinView(contentView, toView: view, attribute: .right, relation: .equal, priority: .required, constant: 0) } } + stackItems.append(view) } } diff --git a/MVMCoreUI/Atomic/Organisms/StackModel.swift b/MVMCoreUI/Atomic/Organisms/StackModel.swift index 3f13a580..d96da035 100644 --- a/MVMCoreUI/Atomic/Organisms/StackModel.swift +++ b/MVMCoreUI/Atomic/Organisms/StackModel.swift @@ -6,20 +6,28 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation @objcMembers public class StackModel: ContainerModel, StackModelProtocol, MoleculeModelProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + static let defaultSpacing: CGFloat = 16.0 - + public class var identifier: String { return "stack" } + public var backgroundColor: Color? public var molecules: [StackItemModelProtocol & MoleculeModelProtocol] public var axis: NSLayoutConstraint.Axis = .vertical public var spacing: CGFloat = StackModel.defaultSpacing public var useStackSpacingBeforeFirstItem = false + //-------------------------------------------------- + // MARK: - Initializer + //-------------------------------------------------- + public init(molecules: [StackItemModelProtocol & MoleculeModelProtocol], axis: NSLayoutConstraint.Axis? = nil, spacing: CGFloat? = nil) { self.molecules = molecules if let axis = axis { @@ -30,7 +38,11 @@ import Foundation } super.init() } - + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { case moleculeName case backgroundColor @@ -38,7 +50,11 @@ import Foundation case axis case spacing } - + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) molecules = try typeContainer.decodeModels(codingKey: .molecules) @@ -51,7 +67,7 @@ import Foundation backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) try super.init(from: decoder) } - + public override func encode(to encoder: Encoder) throws { try super.encode(to: encoder) var container = encoder.container(keyedBy: CodingKeys.self) diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 372ae778..9c7f3ba6 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -108,6 +108,10 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } } + open override func accessibilityElementCount() -> Int { + return moleculesInfo?.count ?? 0 + } + open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { guard let moleculeInfo = moleculesInfo?[indexPath.row], let estimatedHeight = (moleculeInfo.class as? MoleculeViewProtocol.Type)?.estimatedHeight(with: moleculeInfo.molecule, delegateObject() as? MVMCoreUIDelegateObject) @@ -115,7 +119,7 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol return estimatedHeight } - + open override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return moleculesInfo?.count ?? 0 } diff --git a/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift index 5154d9a6..2b555e18 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeStackTemplate.swift @@ -8,10 +8,19 @@ import UIKit + open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + var observer: NSKeyValueObservation? public var templateModel: StackPageTemplateModel? + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func handleNewData() { topViewOutsideOfScroll = templateModel?.anchorHeader ?? false bottomViewOutsideOfScroll = templateModel?.anchorFooter ?? false @@ -20,9 +29,9 @@ open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol { // For subclassing the model. open func decodeTemplate(using decoder: JSONDecoder, from data: Data) throws -> StackPageTemplateModel { - return try decoder.decode(StackPageTemplateModel.self, from: data) + return try decoder.decode(StackPageTemplateModel.self, from: data) } - + open override func parsePageJSON() throws { try parseTemplate(json: loadObject?.pageJSON) try super.parsePageJSON() @@ -48,21 +57,23 @@ open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol { open override func viewForTop() -> UIView? { guard let headerModel = templateModel?.header, - let molecule = MoleculeObjectMapping.shared()?.createMolecule(headerModel, delegateObject: delegateObjectIVar) else { - return nil - } + let molecule = MoleculeObjectMapping.shared()?.createMolecule(headerModel, delegateObject: delegateObjectIVar) + else { return nil } + return molecule } open override func viewForMiddle() -> UIView? { guard let moleculeStackModel = templateModel?.moleculeStack else { return nil } - + // By default: Stack template stack has vertical space before the first item, dynamic stack items have default horizontal padding. let stack = MoleculeStackView(frame: .zero) moleculeStackModel.useStackSpacingBeforeFirstItem = true for stackItem in moleculeStackModel.molecules { guard let stackItem = stackItem as? MoleculeStackItemModel, - stackItem.useHorizontalMargins == nil else { continue } + stackItem.useHorizontalMargins == nil + else { continue } + stackItem.useHorizontalMargins = true } stack.set(with: moleculeStackModel, delegateObject() as? MVMCoreUIDelegateObject, nil) @@ -71,14 +82,16 @@ open class MoleculeStackTemplate: ThreeLayerViewController, TemplateProtocol { override open func viewForBottom() -> UIView? { guard let footerModel = templateModel?.footer, - let molecule = MoleculeObjectMapping.shared()?.createMolecule(footerModel, delegateObject: delegateObjectIVar) else { - return nil - } + let molecule = MoleculeObjectMapping.shared()?.createMolecule(footerModel, delegateObject: delegateObjectIVar) + else { return nil } + return molecule } - // MARK: - cache handling - + //-------------------------------------------------- + // MARK: - Cache Handling + //-------------------------------------------------- + /// Adds modules from requiredModules() to the MVMCoreViewControllerMapping.requiredModules map. open func updateRequiredModules() { if let requiredModules = requiredModules(), let pageType = pageType { diff --git a/MVMCoreUI/BaseClasses/Button.swift b/MVMCoreUI/BaseClasses/Button.swift index 757855ed..6fb9460e 100644 --- a/MVMCoreUI/BaseClasses/Button.swift +++ b/MVMCoreUI/BaseClasses/Button.swift @@ -95,14 +95,15 @@ public typealias ButtonAction = (Button) -> () self.model = model if let backgroundColor = model.backgroundColor { - self.backgroundColor = backgroundColor.uiColor + self.backgroundColor = backgroundColor.uiColor } - + if let model = model as? EnableableModelProtocol { isEnabled = model.enabled } guard let model = model as? ButtonModelProtocol else { return } + set(with: model.action, delegateObject: delegateObject, additionalData: additionalData) } @@ -141,6 +142,8 @@ extension Button: MVMCoreViewProtocol { /// Will be called only once. open func setupView() { + isAccessibilityElement = true + accessibilityTraits = .button translatesAutoresizingMaskIntoConstraints = false insetsLayoutMarginsFromSafeArea = false titleLabel?.numberOfLines = 0 diff --git a/MVMCoreUI/BaseClasses/TableViewCell.swift b/MVMCoreUI/BaseClasses/TableViewCell.swift index b391162e..65c05e53 100644 --- a/MVMCoreUI/BaseClasses/TableViewCell.swift +++ b/MVMCoreUI/BaseClasses/TableViewCell.swift @@ -119,6 +119,7 @@ import UIKit selectionStyle = .none insetsLayoutMarginsFromSafeArea = false preservesSuperviewLayoutMargins = false + isAccessibilityElement = false contentView.insetsLayoutMarginsFromSafeArea = false contentView.preservesSuperviewLayoutMargins = false styleStandard() @@ -192,7 +193,7 @@ import UIKit caret.accessibilityTraits = .button caret.size = .small(.vertical) if let size = caret.size?.dimensions() { - caret.frame = CGRect(origin: CGPoint.zero, size: size) + caret.frame = CGRect(origin: .zero, size: size) caretViewWidthSizeObject = MFSizeObject(standardSize: size.width, standardiPadPortraitSize: 9) caretViewHeightSizeObject = MFSizeObject(standardSize: size.height, standardiPadPortraitSize: 16) } diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index 7962dcf5..7d3b7d6b 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -9,6 +9,7 @@ import UIKit import MVMAnimationFramework + open class ThreeLayerTableViewController: ProgrammaticTableViewController { // The three main views private var topView: UIView? @@ -40,7 +41,7 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { createViewForTableHeader() createViewForTableFooter() tableView?.reloadData() - accessibilityElements = [tableView as Any] +// accessibilityElements = [tableView as Any] } override open func viewDidLoad() { diff --git a/MVMCoreUI/BaseControllers/ViewController.swift b/MVMCoreUI/BaseControllers/ViewController.swift index 336d5da6..10c8d45f 100644 --- a/MVMCoreUI/BaseControllers/ViewController.swift +++ b/MVMCoreUI/BaseControllers/ViewController.swift @@ -506,8 +506,8 @@ import UIKit // Needed otherwise when subclassed, the extension gets called. open func moleculeLayoutUpdated(_ molecule: MoleculeViewProtocol) {} open func getIndexPath(for molecule: ListItemModelProtocol & MoleculeModelProtocol) -> IndexPath? { return nil } - open func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) {} - open func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) {} + open func addMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], indexPath: IndexPath, animation: UITableView.RowAnimation) { } + open func removeMolecules(_ molecules: [ListItemModelProtocol & MoleculeModelProtocol], animation: UITableView.RowAnimation) { } //-------------------------------------------------- // MARK: - MVMCoreUIDetailViewProtocol diff --git a/MVMCoreUI/Containers/Views/Container.swift b/MVMCoreUI/Containers/Views/Container.swift index 6cd72e95..7ad02f9d 100644 --- a/MVMCoreUI/Containers/Views/Container.swift +++ b/MVMCoreUI/Containers/Views/Container.swift @@ -8,7 +8,11 @@ import UIKit + open class Container: View, ContainerProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- public var view: UIView? let containerHelper = ContainerHelper() @@ -17,10 +21,15 @@ open class Container: View, ContainerProtocol { get { return model as? ContainerModelProtocol } } - // MARK:- MoleculeViewProtocol + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + override open func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) + guard let containerModel = model as? ContainerModelProtocol else { return } + containerHelper.set(with: containerModel, for: view as? MVMCoreUIViewConstrainingProtocol) } @@ -29,7 +38,10 @@ open class Container: View, ContainerProtocol { (view as? MoleculeViewProtocol)?.reset() } - // MARK:- ContainerProtocol + //-------------------------------------------------- + // MARK: - ContainerProtocol + //-------------------------------------------------- + open func alignHorizontal(_ alignment: UIStackView.Alignment) { containerHelper.alignHorizontal(alignment) } @@ -45,12 +57,13 @@ open class Container: View, ContainerProtocol { // MARK: - MVMCoreViewProtocol public extension Container { + override func updateView(_ size: CGFloat) { super.updateView(size) (view as? MVMCoreViewProtocol)?.updateView(size) containerHelper.updateViewMargins(self, model: containerModel, size: size) } - + /// Will be called only once. override func setupView() { super.setupView() From bd15836698fe8072182705b1ffc57c514536eb7f Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 11 Sep 2020 15:20:12 -0400 Subject: [PATCH 04/16] some accessibility. spacing and formatting --- MVMCoreUI/Atomic/Organisms/Stack.swift | 11 ----------- MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift | 4 ---- MVMCoreUI/BaseClasses/TableViewCell.swift | 1 - .../ThreeLayerTableViewController.swift | 2 +- 4 files changed, 1 insertion(+), 17 deletions(-) diff --git a/MVMCoreUI/Atomic/Organisms/Stack.swift b/MVMCoreUI/Atomic/Organisms/Stack.swift index e70485a7..9440ccc5 100644 --- a/MVMCoreUI/Atomic/Organisms/Stack.swift +++ b/MVMCoreUI/Atomic/Organisms/Stack.swift @@ -14,7 +14,6 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto open var contentView: UIView = MVMCoreUICommonViewsUtility.commonView() open var stackItems: [UIView] = [] - var didSetAcessibilityElements = false open var stackModel: T? { get { return model as? T } @@ -48,15 +47,6 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto addView(view, stackModel.molecules[index], totalSpacing: totalSpace, lastItem: lastItemIndex == index) } - // setAccessibilityElements() - } - - open func setAccessibilityElements() { - - guard !didSetAcessibilityElements, - let stackModel = stackModel - else { return } - isAccessibilityElement = false var accessibleViews: [Any] = [] @@ -65,7 +55,6 @@ open class Stack: Container where T: (StackModelProtocol & MoleculeModelProto } accessibilityElements = accessibleViews - didSetAcessibilityElements = true } /// Removes all stack items views from the view. diff --git a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift index 9c7f3ba6..12f9ce4d 100644 --- a/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift +++ b/MVMCoreUI/Atomic/Templates/MoleculeListTemplate.swift @@ -108,10 +108,6 @@ open class MoleculeListTemplate: ThreeLayerTableViewController, TemplateProtocol } } - open override func accessibilityElementCount() -> Int { - return moleculesInfo?.count ?? 0 - } - open func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { guard let moleculeInfo = moleculesInfo?[indexPath.row], let estimatedHeight = (moleculeInfo.class as? MoleculeViewProtocol.Type)?.estimatedHeight(with: moleculeInfo.molecule, delegateObject() as? MVMCoreUIDelegateObject) diff --git a/MVMCoreUI/BaseClasses/TableViewCell.swift b/MVMCoreUI/BaseClasses/TableViewCell.swift index 8f936020..c4c1f35c 100644 --- a/MVMCoreUI/BaseClasses/TableViewCell.swift +++ b/MVMCoreUI/BaseClasses/TableViewCell.swift @@ -126,7 +126,6 @@ import UIKit selectionStyle = .none insetsLayoutMarginsFromSafeArea = false preservesSuperviewLayoutMargins = false - isAccessibilityElement = false contentView.insetsLayoutMarginsFromSafeArea = false contentView.preservesSuperviewLayoutMargins = false styleStandard() diff --git a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift index 7d3b7d6b..494599dc 100644 --- a/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift +++ b/MVMCoreUI/BaseControllers/ThreeLayerTableViewController.swift @@ -41,7 +41,7 @@ open class ThreeLayerTableViewController: ProgrammaticTableViewController { createViewForTableHeader() createViewForTableFooter() tableView?.reloadData() -// accessibilityElements = [tableView as Any] + accessibilityElements = [tableView as Any] } override open func viewDidLoad() { From 0f655b534eeeb873dfe22f32d4db3a5480cf9053 Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Wed, 16 Sep 2020 18:46:26 +0530 Subject: [PATCH 05/16] Initial commit for MOBFIRST-22354 Star story. --- MVMCoreUI.xcodeproj/project.pbxproj | 8 ++ MVMCoreUI/Atomic/Atoms/Views/Star.swift | 111 +++++++++++++++++++ MVMCoreUI/Atomic/Atoms/Views/StarModel.swift | 65 +++++++++++ MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 2 +- 4 files changed, 185 insertions(+), 1 deletion(-) create mode 100644 MVMCoreUI/Atomic/Atoms/Views/Star.swift create mode 100644 MVMCoreUI/Atomic/Atoms/Views/StarModel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index b67f6235..14152a0b 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -199,6 +199,8 @@ 94CA227D24058534002D6750 /* VerizonNHGeDS-Regular.otf in Resources */ = {isa = PBXBuildFile; fileRef = 94CA227924058533002D6750 /* VerizonNHGeDS-Regular.otf */; }; 94CA227E24058534002D6750 /* VerizonNHGeDS-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = 94CA227A24058533002D6750 /* VerizonNHGeDS-Bold.otf */; }; 94F6516D2437954100631BF9 /* Tabs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 94F6516C2437954100631BF9 /* Tabs.swift */; }; + AA07EA912510A442009A2AE3 /* StarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA07EA902510A442009A2AE3 /* StarModel.swift */; }; + AA07EA932510A451009A2AE3 /* Star.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA07EA922510A451009A2AE3 /* Star.swift */; }; AA0A257824766C8A00862F64 /* ListLeftVariableIconWithRightCaretBodyTextModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0A257724766C8A00862F64 /* ListLeftVariableIconWithRightCaretBodyTextModel.swift */; }; AA0A257A24766CA200862F64 /* ListLeftVariableIconWithRightCaretBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA0A257924766CA200862F64 /* ListLeftVariableIconWithRightCaretBodyText.swift */; }; AA104AC724472DB0004D2810 /* HeadersH1Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA104AC624472DB0004D2810 /* HeadersH1Button.swift */; }; @@ -687,6 +689,8 @@ 94CA227A24058533002D6750 /* VerizonNHGeDS-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGeDS-Bold.otf"; sourceTree = ""; }; 94CA227B24058533002D6750 /* VerizonNHGeTX-Regular.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGeTX-Regular.otf"; sourceTree = ""; }; 94F6516C2437954100631BF9 /* Tabs.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Tabs.swift; sourceTree = ""; }; + AA07EA902510A442009A2AE3 /* StarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarModel.swift; sourceTree = ""; }; + AA07EA922510A451009A2AE3 /* Star.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Star.swift; sourceTree = ""; }; AA0A257724766C8A00862F64 /* ListLeftVariableIconWithRightCaretBodyTextModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableIconWithRightCaretBodyTextModel.swift; sourceTree = ""; }; AA0A257924766CA200862F64 /* ListLeftVariableIconWithRightCaretBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableIconWithRightCaretBodyText.swift; sourceTree = ""; }; AA104AC624472DB0004D2810 /* HeadersH1Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH1Button.swift; sourceTree = ""; }; @@ -1875,6 +1879,8 @@ D20492A524329CE200A5EED6 /* LoadImageView.swift */, 0A51F3E02475CB73002E08B6 /* LoadingSpinnerModel.swift */, 0A51F3E12475CB73002E08B6 /* LoadingSpinner.swift */, + AA07EA902510A442009A2AE3 /* StarModel.swift */, + AA07EA922510A451009A2AE3 /* Star.swift */, ); path = Views; sourceTree = ""; @@ -2306,6 +2312,7 @@ AA1EC59724373985003D6F50 /* ListThreeColumnSpeedTestDividerModel.swift in Sources */, BB1D17E0244EAA30001D2002 /* ListDeviceComplexButtonMediumModel.swift in Sources */, D2CAC7D3251105A700C75681 /* MVMCoreUITopAlertExpandableView+Extension.swift in Sources */, + AA07EA932510A451009A2AE3 /* Star.swift in Sources */, D29DF2CF21E7C104003B2FB9 /* MFLoadingViewController.m in Sources */, D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */, D28BA74D248589C800B75CB8 /* TabPageModelProtocol.swift in Sources */, @@ -2569,6 +2576,7 @@ BB2BF0EC2452A9D5001D0FC2 /* ListDeviceComplexButtonSmallModel.swift in Sources */, 943784F6236B77BB006A1E82 /* WheelAnimationHandler.swift in Sources */, 011D95A1240453D0000E3791 /* RuleEqualsModel.swift in Sources */, + AA07EA912510A442009A2AE3 /* StarModel.swift in Sources */, D29DF2AA21E7B2F9003B2FB9 /* MVMCoreUIConstants.m in Sources */, 011D95892404249B000E3791 /* FormHolderModelProtocol.swift in Sources */, BB54C5202434D92F0038326C /* ListRightVariableButtonAllTextAndLinks.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/Star.swift b/MVMCoreUI/Atomic/Atoms/Views/Star.swift new file mode 100644 index 00000000..c0cd6f56 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/Star.swift @@ -0,0 +1,111 @@ +// +// Star.swift +// MVMCoreUI +// +// Created by Lekshmi S on 15/09/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers open class Star: View { + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + private var starLayer: CAShapeLayer? + let maskLayer = CAShapeLayer() + + var starModel: StarModel? + var progressBar = ProgressBar(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + public var widthConstraint: NSLayoutConstraint? + public var heightConstraint: NSLayoutConstraint? + + //------------------------------------------------------ + // MARK: - State Handling + //------------------------------------------------------ + + open override func draw(_ rect: CGRect) { + //Draw the heart + starLayer?.removeFromSuperlayer() + let star = drawStar() + layer.addSublayer(star) + starLayer = star + + //Mask the star + maskLayer.removeFromSuperlayer() + maskLayer.path = star.path + self.layer.mask = maskLayer + } + + func drawStar() -> CAShapeLayer { + let shapeLayer = CAShapeLayer() + shapeLayer.frame = self.bounds + let starPath = UIBezierPath() + let center = shapeLayer.position + let theta = .pi / CGFloat(5.0) + let outerRadius = center.x * 1.039 + let excessRadius = outerRadius - center.x + let innerRadius = CGFloat(outerRadius * 0.382) + let leftEdgePointX = (center.x + cos(4.0 * theta) * outerRadius) + excessRadius + let horizontalOffset = leftEdgePointX / 2.0 + let offsetCenter = CGPoint(x: center.x - horizontalOffset, y: center.y) + for i in 0 ..< 10 { + let radius = i % 2 == 0 ? outerRadius : innerRadius + let pointX = offsetCenter.x + cos(CGFloat(i) * theta) * radius + let pointY = offsetCenter.y + sin(CGFloat(i) * theta) * radius + let point = CGPoint(x: pointX, y: pointY) + if i == 0 { + starPath.move(to: point) + } else { + starPath.addLine(to: point) + } + } + starPath.close() + // Rotate the path so the star points up as expected + var pathTransform = CGAffineTransform.identity + pathTransform = pathTransform.translatedBy(x: center.x, y: center.y) + pathTransform = pathTransform.rotated(by: CGFloat(-.pi / 2.0)) + pathTransform = pathTransform.translatedBy(x: -center.x, y: -center.y) + starPath.apply(pathTransform) + shapeLayer.path = starPath.cgPath + shapeLayer.fillColor = UIColor.clear.cgColor + shapeLayer.opacity = 1.0 + shapeLayer.lineWidth = 1 + shapeLayer.strokeColor = starModel?.borderColor.cgColor + return shapeLayer + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func setupView() { + super.setupView() + addSubview(progressBar) + NSLayoutConstraint.constraintPinSubview(toSuperview: progressBar) + } + + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? StarModel else { return } + self.starModel = model + progressBar.progress = Float((model.percent) / 100.0) + progressBar.progressTintColor = model.fillColor.uiColor + progressBar.trackTintColor = .mvmWhite + setSizeForProgressBar(size: model.size) + } + + func setSizeForProgressBar(size: CGFloat) { + progressBar.transform = progressBar.transform.scaledBy(x: 1, y: size/9) + progressBar.transform = progressBar.transform.translatedBy(x: 0, y: -3.5) + widthConstraint = widthAnchor.constraint(equalToConstant: size) + widthConstraint?.isActive = true + heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1) + heightConstraint?.isActive = true + setNeedsDisplay() + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift b/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift new file mode 100644 index 00000000..402ada9a --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift @@ -0,0 +1,65 @@ +// +// StarModel.swift +// MVMCoreUI +// +// Created by Lekshmi S on 15/09/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class StarModel: MoleculeModelProtocol { + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "star" + public var backgroundColor: Color? + @Percent public var percent: CGFloat = 0 + public var borderColor: Color = Color(uiColor: .mvmBlack) + public var fillColor: Color = Color(uiColor: .mvmBlack) + public var size: CGFloat = 30.0 + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case percent + case borderColor + case fillColor + case size + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + + if let percent = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .percent) { + self.percent = percent + } + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + if let borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) { + self.borderColor = borderColor + } + if let fillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .fillColor) { + self.fillColor = fillColor + } + if let size = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .size) { + self.size = size + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(percent, forKey: .percent) + try container.encodeIfPresent(borderColor, forKey: .borderColor) + try container.encodeIfPresent(fillColor, forKey: .fillColor) + try container.encodeIfPresent(size, forKey: .size) + } +} diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index a598b113..a2dc068f 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -87,7 +87,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: RadioSwatches.self, viewModelClass: RadioSwatchesModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Tags.self, viewModelClass: TagsModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Tag.self, viewModelClass: TagModel.self) - + MoleculeObjectMapping.shared()?.register(viewClass: Star.self, viewModelClass: StarModel.self) // MARK:- Other Atoms From 25b88d47583251523bfd7cf6bd77975733de0f19 Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Thu, 24 Sep 2020 21:45:31 +0530 Subject: [PATCH 06/16] StarsModel and molecule class commit. --- MVMCoreUI.xcodeproj/project.pbxproj | 12 ++ MVMCoreUI/Atomic/Atoms/Views/Star.swift | 16 +- .../Atoms/Views/StarCollectionViewCell.swift | 24 +++ MVMCoreUI/Atomic/Atoms/Views/Stars.swift | 155 ++++++++++++++++++ MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift | 72 ++++++++ MVMCoreUI/Atomic/MoleculeObjectMapping.swift | 1 + 6 files changed, 271 insertions(+), 9 deletions(-) create mode 100644 MVMCoreUI/Atomic/Atoms/Views/StarCollectionViewCell.swift create mode 100644 MVMCoreUI/Atomic/Atoms/Views/Stars.swift create mode 100644 MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 14152a0b..177a69d3 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -219,6 +219,8 @@ AA2AD118244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA2AD117244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift */; }; AA3561AC24C9684400452EB1 /* ListRightVariableRightCaretAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3561AB24C9684400452EB1 /* ListRightVariableRightCaretAllTextAndLinksModel.swift */; }; AA3561AE24C96B9000452EB1 /* ListRightVariableRightCaretAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA3561AD24C96B9000452EB1 /* ListRightVariableRightCaretAllTextAndLinks.swift */; }; + AA37CBD3251907200027344C /* StarsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA37CBD2251907200027344C /* StarsModel.swift */; }; + AA37CBD52519072F0027344C /* Stars.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA37CBD42519072F0027344C /* Stars.swift */; }; AA45AA0B24BF0263007A6EA7 /* LockUpsPlanNamesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA45AA0A24BF0263007A6EA7 /* LockUpsPlanNamesModel.swift */; }; AA45AA0D24BF0276007A6EA7 /* LockUpsPlanNames.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA45AA0C24BF0276007A6EA7 /* LockUpsPlanNames.swift */; }; AA56A20F243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA56A20E243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift */; }; @@ -233,6 +235,7 @@ AA71AD4024A32FE700ACA76F /* HeadersH2Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA71AD3F24A32FE700ACA76F /* HeadersH2Link.swift */; }; AA7F32AB246C0F7900C965BA /* ListLeftVariableRadioButtonAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7F32AA246C0F7900C965BA /* ListLeftVariableRadioButtonAllTextAndLinksModel.swift */; }; AA7F32AD246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7F32AC246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift */; }; + AA817FE6251C71B600EF0C6C /* StarCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA817FE5251C71B600EF0C6C /* StarCollectionViewCell.swift */; }; AA85236C244435A20059CC1E /* RadioSwatchCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */; }; AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA99724F2475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift */; }; AA997252247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA997251247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift */; }; @@ -709,6 +712,8 @@ AA2AD117244EE48C00BBFFE3 /* ListDeviceComplexLinkMediumModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListDeviceComplexLinkMediumModel.swift; sourceTree = ""; }; AA3561AB24C9684400452EB1 /* ListRightVariableRightCaretAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableRightCaretAllTextAndLinksModel.swift; sourceTree = ""; }; AA3561AD24C96B9000452EB1 /* ListRightVariableRightCaretAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListRightVariableRightCaretAllTextAndLinks.swift; sourceTree = ""; }; + AA37CBD2251907200027344C /* StarsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarsModel.swift; sourceTree = ""; }; + AA37CBD42519072F0027344C /* Stars.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stars.swift; sourceTree = ""; }; AA45AA0A24BF0263007A6EA7 /* LockUpsPlanNamesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockUpsPlanNamesModel.swift; sourceTree = ""; }; AA45AA0C24BF0276007A6EA7 /* LockUpsPlanNames.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LockUpsPlanNames.swift; sourceTree = ""; }; AA56A20E243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListTwoColumnSubsectionDividerModel.swift; sourceTree = ""; }; @@ -723,6 +728,7 @@ AA71AD3F24A32FE700ACA76F /* HeadersH2Link.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2Link.swift; sourceTree = ""; }; AA7F32AA246C0F7900C965BA /* ListLeftVariableRadioButtonAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAllTextAndLinksModel.swift; sourceTree = ""; }; AA7F32AC246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAllTextAndLinks.swift; sourceTree = ""; }; + AA817FE5251C71B600EF0C6C /* StarCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarCollectionViewCell.swift; sourceTree = ""; }; AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchCollectionViewCell.swift; sourceTree = ""; }; AA99724F2475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableIconAllTextLinksModel.swift; sourceTree = ""; }; AA997251247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableIconAllTextLinks.swift; sourceTree = ""; }; @@ -1879,8 +1885,11 @@ D20492A524329CE200A5EED6 /* LoadImageView.swift */, 0A51F3E02475CB73002E08B6 /* LoadingSpinnerModel.swift */, 0A51F3E12475CB73002E08B6 /* LoadingSpinner.swift */, + AA37CBD2251907200027344C /* StarsModel.swift */, + AA37CBD42519072F0027344C /* Stars.swift */, AA07EA902510A442009A2AE3 /* StarModel.swift */, AA07EA922510A451009A2AE3 /* Star.swift */, + AA817FE5251C71B600EF0C6C /* StarCollectionViewCell.swift */, ); path = Views; sourceTree = ""; @@ -2216,6 +2225,7 @@ AAC6F167243332E400F295C1 /* RadioSwatchesModel.swift in Sources */, 324FB6AA249366F3002552C7 /* ListLeftVariableNumberedListBodyTextModel.swift in Sources */, 5248BFED23F12E350059236A /* ListThreeColumnPlanDataDividerModel.swift in Sources */, + AA817FE6251C71B600EF0C6C /* StarCollectionViewCell.swift in Sources */, AA0A257824766C8A00862F64 /* ListLeftVariableIconWithRightCaretBodyTextModel.swift in Sources */, 0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */, 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */, @@ -2337,6 +2347,7 @@ 525239C02407BCFF00454969 /* ListTwoColumnPriceDetailsModel.swift in Sources */, D2E2A99A23D8D6B4000B42E6 /* HeadlineBodyButtonModel.swift in Sources */, D202AFE6242A6A9C00E5BEDF /* UICollectionViewScrollPosition+Extension.swift in Sources */, + AA37CBD3251907200027344C /* StarsModel.swift in Sources */, 8D084AD22410BF7600951227 /* ListOneColumnFullWidthTextBodyText.swift in Sources */, 94C0150C2421564A005811A9 /* ActionCollapseNotificationModel.swift in Sources */, D2CAC7CB251104E100C75681 /* NotificationXButtonModel.swift in Sources */, @@ -2432,6 +2443,7 @@ 01EB368F23609801006832FA /* LabelModel.swift in Sources */, 0A6682AC243531C300AD3CA1 /* Padding.swift in Sources */, AA1EC59924373994003D6F50 /* ListThreeColumnSpeedTestDivider.swift in Sources */, + AA37CBD52519072F0027344C /* Stars.swift in Sources */, 942C378E2412F5B60066E45E /* ModalMoleculeStackTemplate.swift in Sources */, 8D8067D32444473A00203BE8 /* ListRightVariablePriceChangeAllTextAndLinks.swift in Sources */, 8D4687E4242E2DF300802879 /* ListFourColumnDataUsageListItem.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/Star.swift b/MVMCoreUI/Atomic/Atoms/Views/Star.swift index c0cd6f56..50639dc3 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Star.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Star.swift @@ -14,10 +14,9 @@ import Foundation // MARK: - Properties //-------------------------------------------------- private var starLayer: CAShapeLayer? - let maskLayer = CAShapeLayer() - - var starModel: StarModel? - var progressBar = ProgressBar(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) + private let maskLayer = CAShapeLayer() + public var starModel: StarModel? + public var progressBar = UIProgressView(progressViewStyle: .bar) //-------------------------------------------------- // MARK: - Constraints @@ -28,7 +27,6 @@ import Foundation //------------------------------------------------------ // MARK: - State Handling //------------------------------------------------------ - open override func draw(_ rect: CGRect) { //Draw the heart starLayer?.removeFromSuperlayer() @@ -85,6 +83,7 @@ import Foundation //-------------------------------------------------- open override func setupView() { super.setupView() + progressBar.translatesAutoresizingMaskIntoConstraints = false addSubview(progressBar) NSLayoutConstraint.constraintPinSubview(toSuperview: progressBar) } @@ -96,12 +95,11 @@ import Foundation progressBar.progress = Float((model.percent) / 100.0) progressBar.progressTintColor = model.fillColor.uiColor progressBar.trackTintColor = .mvmWhite - setSizeForProgressBar(size: model.size) + setFrame(with: model.size) } - func setSizeForProgressBar(size: CGFloat) { - progressBar.transform = progressBar.transform.scaledBy(x: 1, y: size/9) - progressBar.transform = progressBar.transform.translatedBy(x: 0, y: -3.5) + func setFrame(with size: CGFloat) { + progressBar.frame = CGRect(x: 0, y: 0, width: size, height: size) widthConstraint = widthAnchor.constraint(equalToConstant: size) widthConstraint?.isActive = true heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1) diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Views/StarCollectionViewCell.swift new file mode 100644 index 00000000..08774d45 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/StarCollectionViewCell.swift @@ -0,0 +1,24 @@ +// +// StarCollectionViewCell.swift +// MVMCoreUI +// +// Created by Lekshmi S on 24/09/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class StarCollectionViewCell: CollectionViewCell { + public let star = Star() + + open override func setupView() { + super.setupView() + addMolecule(star) + MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let model = model as? StarModel else { return } + star.set(with: model, delegateObject, additionalData) + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift new file mode 100644 index 00000000..41f80bc0 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift @@ -0,0 +1,155 @@ +// +// Stars.swift +// MVMCoreUI +// +// Created by Lekshmi S on 21/09/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class Stars: View { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var collectionView: CollectionView! + public var stars: [StarModel]? + private var size: CGFloat? + private var delegateObject: MVMCoreUIDelegateObject? + private var starBackgroundColor: Color? + private var borderColor: Color? + private var fillColor: Color? + private var progress: CGFloat? + private var cellSize: CGFloat = 30.0 + + //------------------------------------------------------ + // MARK: - Constraints + //------------------------------------------------------ + public var collectionViewHeight: NSLayoutConstraint? + private let itemSpacing: CGFloat = 3.0 + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any collection size changes + setHeight() + DispatchQueue.main.async { + self.collectionView.collectionViewLayout.invalidateLayout() + } + } + + open override func setupView() { + super.setupView() + collectionView = createCollectionView() + addSubview(collectionView) + NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) + collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 30) + collectionViewHeight?.isActive = true + } + + @objc override open func updateView(_ size: CGFloat) { + super.updateView(size) + self.size = size + collectionView.updateView(size) + } + + open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + self.delegateObject = delegateObject + + guard let starsModel = model as? StarsModel else { return } + stars = starsModel.stars + cellSize = starsModel.size + starBackgroundColor = starsModel.starBackgroundColor ?? Color(uiColor: .clear) + borderColor = starsModel.borderColor + fillColor = starsModel.fillColor + progress = starsModel.percent + collectionView.reloadData() + } + + //------------------------------------------------------ + // MARK: - Methods + //------------------------------------------------------ + /// Creates the collection view. + open func createCollectionView() -> CollectionView { + let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) + collection.dataSource = self + collection.delegate = self + collection.register(StarCollectionViewCell.self, forCellWithReuseIdentifier: "StarCollectionViewCell") + return collection + } + + /// Creates the layout for the collection. + open func createCollectionViewLayout() -> UICollectionViewLayout { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = itemSpacing + layout.minimumInteritemSpacing = itemSpacing + return layout + } + + open func setHeight() { + guard let stars = stars, stars.count > 0 else { + collectionViewHeight?.constant = 0 + return + } + // Calculate the height + let starsInRow = floor(CGFloat(collectionView.bounds.width/(cellSize + itemSpacing))) + let numberOfRows = ceil(CGFloat(stars.count)/starsInRow) + let height = (numberOfRows * cellSize) + (itemSpacing * (numberOfRows-1)) + + if let oldHeight = collectionViewHeight?.constant, + height != oldHeight { + // Notify delegate of height change, called async to avoid various race conditions caused while happening while laying out initially. + DispatchQueue.main.async { + self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) + } + } + collectionViewHeight?.constant = CGFloat(height) + } +} + +//------------------------------------------------------ +// MARK: - Delegate methods +//------------------------------------------------------ +extension Stars: UICollectionViewDelegateFlowLayout { + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + return CGSize(width: cellSize, height: cellSize) + } +} + +extension Stars: UICollectionViewDataSource { + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return stars?.count ?? 0 + } + + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let molecule = stars?[indexPath.row], let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "StarCollectionViewCell", for: indexPath) as? StarCollectionViewCell else { + fatalError() + } + cell.reset() + + //Fill the stars based on percentage. Ex: if there were 4 stars, 75 percent is 3 full stars + let percentRequiredToFillStarFully = CGFloat(100/(stars?.count ?? 1)) + let numberOfFilledStars = Int((progress ?? 0)/percentRequiredToFillStarFully) + if indexPath.row < numberOfFilledStars { + molecule.percent = 100 + } else if indexPath.row == numberOfFilledStars { + let remainingProgress = (progress ?? 0).truncatingRemainder(dividingBy: percentRequiredToFillStarFully) + let fillPercent = (remainingProgress/percentRequiredToFillStarFully) * 100 + molecule.percent = fillPercent + } else { + molecule.percent = 0 + } + molecule.backgroundColor = starBackgroundColor + molecule.borderColor = borderColor ?? Color(uiColor: .mvmBlack) + molecule.fillColor = fillColor ?? Color(uiColor: .mvmBlack) + molecule.size = cellSize + cell.set(with: molecule, delegateObject, nil) + cell.updateView(size ?? collectionView.bounds.width) + cell.layoutIfNeeded() + return cell + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift b/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift new file mode 100644 index 00000000..ef94c4b8 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift @@ -0,0 +1,72 @@ +// +// StarsModel.swift +// MVMCoreUI +// +// Created by Lekshmi S on 21/09/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +@objcMembers public class StarsModel: MoleculeModelProtocol { + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public static var identifier: String = "stars" + public var backgroundColor: Color? + public var starBackgroundColor: Color? + public var stars: [StarModel] + @Percent public var percent: CGFloat = 0 + public var borderColor: Color = Color(uiColor: .mvmBlack) + public var fillColor: Color = Color(uiColor: .mvmBlack) + public var size: CGFloat = 30.0 + + //-------------------------------------------------- + // MARK: - Keys + //-------------------------------------------------- + private enum CodingKeys: String, CodingKey { + case moleculeName + case backgroundColor + case starBackgroundColor + case stars + case percent + case borderColor + case fillColor + case size + } + + //-------------------------------------------------- + // MARK: - Codec + //-------------------------------------------------- + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + stars = try typeContainer.decode([StarModel].self, forKey: .stars) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + starBackgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .starBackgroundColor) + if let percent = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .percent) { + self.percent = percent + } + if let borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) { + self.borderColor = borderColor + } + if let fillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .fillColor) { + self.fillColor = fillColor + } + if let size = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .size) { + self.size = size + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(stars, forKey: .stars) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(starBackgroundColor, forKey: .starBackgroundColor) + try container.encodeIfPresent(percent, forKey: .percent) + try container.encodeIfPresent(borderColor, forKey: .borderColor) + try container.encodeIfPresent(fillColor, forKey: .fillColor) + try container.encodeIfPresent(size, forKey: .size) + } +} diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index a2dc068f..e27182db 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -87,6 +87,7 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: RadioSwatches.self, viewModelClass: RadioSwatchesModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Tags.self, viewModelClass: TagsModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Tag.self, viewModelClass: TagModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: Stars.self, viewModelClass: StarsModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Star.self, viewModelClass: StarModel.self) From 95ed215d34fdf05abd1f65459cdcfa51f02dcc3c Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Wed, 30 Sep 2020 19:38:53 +0530 Subject: [PATCH 07/16] Using stackview instead of colletionview for stars. --- MVMCoreUI.xcodeproj/project.pbxproj | 4 - MVMCoreUI/Atomic/Atoms/Views/Star.swift | 18 +- .../Atoms/Views/StarCollectionViewCell.swift | 24 --- MVMCoreUI/Atomic/Atoms/Views/StarModel.swift | 8 +- MVMCoreUI/Atomic/Atoms/Views/Stars.swift | 157 +++++------------- MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift | 8 +- 6 files changed, 60 insertions(+), 159 deletions(-) delete mode 100644 MVMCoreUI/Atomic/Atoms/Views/StarCollectionViewCell.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 2cee3c22..07b1b263 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -235,7 +235,6 @@ AA71AD4024A32FE700ACA76F /* HeadersH2Link.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA71AD3F24A32FE700ACA76F /* HeadersH2Link.swift */; }; AA7F32AB246C0F7900C965BA /* ListLeftVariableRadioButtonAllTextAndLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7F32AA246C0F7900C965BA /* ListLeftVariableRadioButtonAllTextAndLinksModel.swift */; }; AA7F32AD246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA7F32AC246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift */; }; - AA817FE6251C71B600EF0C6C /* StarCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA817FE5251C71B600EF0C6C /* StarCollectionViewCell.swift */; }; AA85236C244435A20059CC1E /* RadioSwatchCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */; }; AA9972502475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA99724F2475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift */; }; AA997252247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift in Sources */ = {isa = PBXBuildFile; fileRef = AA997251247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift */; }; @@ -733,7 +732,6 @@ AA71AD3F24A32FE700ACA76F /* HeadersH2Link.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadersH2Link.swift; sourceTree = ""; }; AA7F32AA246C0F7900C965BA /* ListLeftVariableRadioButtonAllTextAndLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAllTextAndLinksModel.swift; sourceTree = ""; }; AA7F32AC246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableRadioButtonAllTextAndLinks.swift; sourceTree = ""; }; - AA817FE5251C71B600EF0C6C /* StarCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StarCollectionViewCell.swift; sourceTree = ""; }; AA85236B244435A20059CC1E /* RadioSwatchCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioSwatchCollectionViewCell.swift; sourceTree = ""; }; AA99724F2475309F00FC7472 /* ListLeftVariableIconAllTextLinksModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableIconAllTextLinksModel.swift; sourceTree = ""; }; AA997251247530B100FC7472 /* ListLeftVariableIconAllTextLinks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListLeftVariableIconAllTextLinks.swift; sourceTree = ""; }; @@ -1900,7 +1898,6 @@ AA37CBD42519072F0027344C /* Stars.swift */, AA07EA902510A442009A2AE3 /* StarModel.swift */, AA07EA922510A451009A2AE3 /* Star.swift */, - AA817FE5251C71B600EF0C6C /* StarCollectionViewCell.swift */, ); path = Views; sourceTree = ""; @@ -2240,7 +2237,6 @@ AAC6F167243332E400F295C1 /* RadioSwatchesModel.swift in Sources */, 324FB6AA249366F3002552C7 /* ListLeftVariableNumberedListBodyTextModel.swift in Sources */, 5248BFED23F12E350059236A /* ListThreeColumnPlanDataDividerModel.swift in Sources */, - AA817FE6251C71B600EF0C6C /* StarCollectionViewCell.swift in Sources */, AA0A257824766C8A00862F64 /* ListLeftVariableIconWithRightCaretBodyTextModel.swift in Sources */, 0A5D59C223AD2F5700EFD9E9 /* AppleGuidelinesProtocol.swift in Sources */, 8D070BB0241B56530099AC56 /* ListRightVariableTotalDataModel.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Atoms/Views/Star.swift b/MVMCoreUI/Atomic/Atoms/Views/Star.swift index 50639dc3..2c532f46 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Star.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Star.swift @@ -15,7 +15,9 @@ import Foundation //-------------------------------------------------- private var starLayer: CAShapeLayer? private let maskLayer = CAShapeLayer() - public var starModel: StarModel? + public var starModel: StarModel? { + return model as? StarModel + } public var progressBar = UIProgressView(progressViewStyle: .bar) //-------------------------------------------------- @@ -37,12 +39,12 @@ import Foundation //Mask the star maskLayer.removeFromSuperlayer() maskLayer.path = star.path - self.layer.mask = maskLayer + layer.mask = maskLayer } func drawStar() -> CAShapeLayer { let shapeLayer = CAShapeLayer() - shapeLayer.frame = self.bounds + shapeLayer.frame = bounds let starPath = UIBezierPath() let center = shapeLayer.position let theta = .pi / CGFloat(5.0) @@ -86,12 +88,15 @@ import Foundation progressBar.translatesAutoresizingMaskIntoConstraints = false addSubview(progressBar) NSLayoutConstraint.constraintPinSubview(toSuperview: progressBar) + widthConstraint = widthAnchor.constraint(equalToConstant: 30) + widthConstraint?.isActive = true + heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1) + heightConstraint?.isActive = true } public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) guard let model = model as? StarModel else { return } - self.starModel = model progressBar.progress = Float((model.percent) / 100.0) progressBar.progressTintColor = model.fillColor.uiColor progressBar.trackTintColor = .mvmWhite @@ -100,10 +105,7 @@ import Foundation func setFrame(with size: CGFloat) { progressBar.frame = CGRect(x: 0, y: 0, width: size, height: size) - widthConstraint = widthAnchor.constraint(equalToConstant: size) - widthConstraint?.isActive = true - heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1) - heightConstraint?.isActive = true + widthConstraint?.constant = size setNeedsDisplay() } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Views/StarCollectionViewCell.swift deleted file mode 100644 index 08774d45..00000000 --- a/MVMCoreUI/Atomic/Atoms/Views/StarCollectionViewCell.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// StarCollectionViewCell.swift -// MVMCoreUI -// -// Created by Lekshmi S on 24/09/20. -// Copyright © 2020 Verizon Wireless. All rights reserved. -// - -import Foundation - -open class StarCollectionViewCell: CollectionViewCell { - public let star = Star() - - open override func setupView() { - super.setupView() - addMolecule(star) - MVMCoreUIUtility.setMarginsFor(contentView, leading: 0, top: 0, trailing: 0, bottom: 0) - } - - open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - guard let model = model as? StarModel else { return } - star.set(with: model, delegateObject, additionalData) - } -} diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift b/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift index 402ada9a..1d473b86 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift @@ -57,9 +57,9 @@ open class StarModel: MoleculeModelProtocol { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encode(moleculeName, forKey: .moleculeName) - try container.encodeIfPresent(percent, forKey: .percent) - try container.encodeIfPresent(borderColor, forKey: .borderColor) - try container.encodeIfPresent(fillColor, forKey: .fillColor) - try container.encodeIfPresent(size, forKey: .size) + try container.encode(percent, forKey: .percent) + try container.encode(borderColor, forKey: .borderColor) + try container.encode(fillColor, forKey: .fillColor) + try container.encode(size, forKey: .size) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift index 41f80bc0..d7dba177 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift @@ -12,144 +12,71 @@ open class Stars: View { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public var collectionView: CollectionView! - public var stars: [StarModel]? - private var size: CGFloat? + private var stack = UIStackView() + public var starsModel: StarsModel? { + return model as? StarsModel + } private var delegateObject: MVMCoreUIDelegateObject? - private var starBackgroundColor: Color? - private var borderColor: Color? - private var fillColor: Color? - private var progress: CGFloat? - private var cellSize: CGFloat = 30.0 - - //------------------------------------------------------ - // MARK: - Constraints - //------------------------------------------------------ - public var collectionViewHeight: NSLayoutConstraint? private let itemSpacing: CGFloat = 3.0 //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- - open override func layoutSubviews() { - super.layoutSubviews() - // Accounts for any collection size changes - setHeight() - DispatchQueue.main.async { - self.collectionView.collectionViewLayout.invalidateLayout() - } - } - open override func setupView() { super.setupView() - collectionView = createCollectionView() - addSubview(collectionView) - NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) - collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 30) - collectionViewHeight?.isActive = true + stack.translatesAutoresizingMaskIntoConstraints = false + addSubview(stack) + stack.topAnchor.constraint(equalTo: topAnchor).isActive = true + bottomAnchor.constraint(equalTo: stack.bottomAnchor).isActive = true + stack.axis = .horizontal + stack.spacing = itemSpacing } @objc override open func updateView(_ size: CGFloat) { super.updateView(size) - self.size = size - collectionView.updateView(size) + stack.updateView(size) } open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) self.delegateObject = delegateObject - - guard let starsModel = model as? StarsModel else { return } - stars = starsModel.stars - cellSize = starsModel.size - starBackgroundColor = starsModel.starBackgroundColor ?? Color(uiColor: .clear) - borderColor = starsModel.borderColor - fillColor = starsModel.fillColor - progress = starsModel.percent - collectionView.reloadData() + createStar() } //------------------------------------------------------ // MARK: - Methods //------------------------------------------------------ - /// Creates the collection view. - open func createCollectionView() -> CollectionView { - let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) - collection.dataSource = self - collection.delegate = self - collection.register(StarCollectionViewCell.self, forCellWithReuseIdentifier: "StarCollectionViewCell") - return collection - } - - /// Creates the layout for the collection. - open func createCollectionViewLayout() -> UICollectionViewLayout { - let layout = UICollectionViewFlowLayout() - layout.scrollDirection = .vertical - layout.minimumLineSpacing = itemSpacing - layout.minimumInteritemSpacing = itemSpacing - return layout - } - - open func setHeight() { - guard let stars = stars, stars.count > 0 else { - collectionViewHeight?.constant = 0 - return - } - // Calculate the height - let starsInRow = floor(CGFloat(collectionView.bounds.width/(cellSize + itemSpacing))) - let numberOfRows = ceil(CGFloat(stars.count)/starsInRow) - let height = (numberOfRows * cellSize) + (itemSpacing * (numberOfRows-1)) - - if let oldHeight = collectionViewHeight?.constant, - height != oldHeight { - // Notify delegate of height change, called async to avoid various race conditions caused while happening while laying out initially. - DispatchQueue.main.async { - self.delegateObject?.moleculeDelegate?.moleculeLayoutUpdated(self) + func createStar() { + if let starsModel = starsModel { + stack.subviews.forEach({$0.removeFromSuperview()}) + let stars = starsModel.stars + for (index, starModel) in stars.enumerated() { + let star = Star() + + //Fill the stars based on percentage. Ex: if there were 4 stars, 75 percent is 3 full stars + let percentRequiredToFillStarFully = CGFloat(100/(starsModel.stars.count)) + let numberOfFilledStars = Int(starsModel.percent/percentRequiredToFillStarFully) + if index < numberOfFilledStars { + starModel.percent = 100 + } else if index == numberOfFilledStars { + let remainingProgress = (starsModel.percent).truncatingRemainder(dividingBy: percentRequiredToFillStarFully) + let fillPercent = (remainingProgress/percentRequiredToFillStarFully) * 100 + starModel.percent = fillPercent + } else { + starModel.percent = 0 + } + + starModel.backgroundColor = starsModel.starBackgroundColor + if let borderColor = starsModel.borderColor { + starModel.borderColor = borderColor + } + if let fillColor = starsModel.fillColor { + starModel.fillColor = fillColor + } + starModel.size = starsModel.size + star.set(with: starModel, delegateObject, nil) + stack.addArrangedSubview(star) } } - collectionViewHeight?.constant = CGFloat(height) - } -} - -//------------------------------------------------------ -// MARK: - Delegate methods -//------------------------------------------------------ -extension Stars: UICollectionViewDelegateFlowLayout { - open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { - return CGSize(width: cellSize, height: cellSize) - } -} - -extension Stars: UICollectionViewDataSource { - open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - return stars?.count ?? 0 - } - - open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - guard let molecule = stars?[indexPath.row], let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "StarCollectionViewCell", for: indexPath) as? StarCollectionViewCell else { - fatalError() - } - cell.reset() - - //Fill the stars based on percentage. Ex: if there were 4 stars, 75 percent is 3 full stars - let percentRequiredToFillStarFully = CGFloat(100/(stars?.count ?? 1)) - let numberOfFilledStars = Int((progress ?? 0)/percentRequiredToFillStarFully) - if indexPath.row < numberOfFilledStars { - molecule.percent = 100 - } else if indexPath.row == numberOfFilledStars { - let remainingProgress = (progress ?? 0).truncatingRemainder(dividingBy: percentRequiredToFillStarFully) - let fillPercent = (remainingProgress/percentRequiredToFillStarFully) * 100 - molecule.percent = fillPercent - } else { - molecule.percent = 0 - } - molecule.backgroundColor = starBackgroundColor - molecule.borderColor = borderColor ?? Color(uiColor: .mvmBlack) - molecule.fillColor = fillColor ?? Color(uiColor: .mvmBlack) - molecule.size = cellSize - cell.set(with: molecule, delegateObject, nil) - cell.updateView(size ?? collectionView.bounds.width) - cell.layoutIfNeeded() - return cell } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift b/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift index ef94c4b8..ad52b18e 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift @@ -18,8 +18,8 @@ import Foundation public var starBackgroundColor: Color? public var stars: [StarModel] @Percent public var percent: CGFloat = 0 - public var borderColor: Color = Color(uiColor: .mvmBlack) - public var fillColor: Color = Color(uiColor: .mvmBlack) + public var borderColor: Color? + public var fillColor: Color? public var size: CGFloat = 30.0 //-------------------------------------------------- @@ -64,9 +64,9 @@ import Foundation try container.encode(stars, forKey: .stars) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeIfPresent(starBackgroundColor, forKey: .starBackgroundColor) - try container.encodeIfPresent(percent, forKey: .percent) try container.encodeIfPresent(borderColor, forKey: .borderColor) try container.encodeIfPresent(fillColor, forKey: .fillColor) - try container.encodeIfPresent(size, forKey: .size) + try container.encode(percent, forKey: .percent) + try container.encode(size, forKey: .size) } } From ad345dacd81f7d941449b0070e76bf4221ebbddc Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Tue, 6 Oct 2020 13:06:31 +0530 Subject: [PATCH 08/16] Code changes as per review comments. --- MVMCoreUI/Atomic/Atoms/Views/Star.swift | 1 - MVMCoreUI/Atomic/Atoms/Views/Stars.swift | 17 ++++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Star.swift b/MVMCoreUI/Atomic/Atoms/Views/Star.swift index 2c532f46..6e712053 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Star.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Star.swift @@ -104,7 +104,6 @@ import Foundation } func setFrame(with size: CGFloat) { - progressBar.frame = CGRect(x: 0, y: 0, width: size, height: size) widthConstraint?.constant = size setNeedsDisplay() } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift index d7dba177..90a8e43a 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift @@ -18,6 +18,7 @@ open class Stars: View { } private var delegateObject: MVMCoreUIDelegateObject? private let itemSpacing: CGFloat = 3.0 + private var heightConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Lifecycle @@ -26,8 +27,9 @@ open class Stars: View { super.setupView() stack.translatesAutoresizingMaskIntoConstraints = false addSubview(stack) - stack.topAnchor.constraint(equalTo: topAnchor).isActive = true - bottomAnchor.constraint(equalTo: stack.bottomAnchor).isActive = true + NSLayoutConstraint.pinView(toSuperview: stack, useMargins: true) + heightConstraint = heightAnchor.constraint(equalToConstant: 30) + heightConstraint?.isActive = true stack.axis = .horizontal stack.spacing = itemSpacing } @@ -48,14 +50,13 @@ open class Stars: View { //------------------------------------------------------ func createStar() { if let starsModel = starsModel { - stack.subviews.forEach({$0.removeFromSuperview()}) let stars = starsModel.stars + let percentRequiredToFillStarFully = CGFloat(100/(starsModel.stars.count)) + let numberOfFilledStars = Int(starsModel.percent/percentRequiredToFillStarFully) for (index, starModel) in stars.enumerated() { let star = Star() //Fill the stars based on percentage. Ex: if there were 4 stars, 75 percent is 3 full stars - let percentRequiredToFillStarFully = CGFloat(100/(starsModel.stars.count)) - let numberOfFilledStars = Int(starsModel.percent/percentRequiredToFillStarFully) if index < numberOfFilledStars { starModel.percent = 100 } else if index == numberOfFilledStars { @@ -77,6 +78,12 @@ open class Stars: View { star.set(with: starModel, delegateObject, nil) stack.addArrangedSubview(star) } + heightConstraint?.constant = starsModel.size } } + + public override func reset() { + super.reset() + stack.subviews.forEach({$0.removeFromSuperview()}) + } } From 93943b25186065153a9355ccae2d4a447e862d2d Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Mon, 12 Oct 2020 19:46:35 +0530 Subject: [PATCH 09/16] Code changes based on review comments. --- MVMCoreUI/Atomic/Atoms/Views/Star.swift | 13 ++++--- MVMCoreUI/Atomic/Atoms/Views/Stars.swift | 49 ++++++++++++------------ 2 files changed, 32 insertions(+), 30 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Star.swift b/MVMCoreUI/Atomic/Atoms/Views/Star.swift index 6e712053..f2a95b94 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Star.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Star.swift @@ -19,6 +19,12 @@ import Foundation return model as? StarModel } public var progressBar = UIProgressView(progressViewStyle: .bar) + public var size: CGFloat = 30 { + didSet { + widthConstraint?.constant = size + setNeedsDisplay() + } + } //-------------------------------------------------- // MARK: - Constraints @@ -100,11 +106,6 @@ import Foundation progressBar.progress = Float((model.percent) / 100.0) progressBar.progressTintColor = model.fillColor.uiColor progressBar.trackTintColor = .mvmWhite - setFrame(with: model.size) - } - - func setFrame(with size: CGFloat) { - widthConstraint?.constant = size - setNeedsDisplay() + size = model.size } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift index 90a8e43a..42224d87 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift @@ -13,6 +13,7 @@ open class Stars: View { // MARK: - Properties //-------------------------------------------------- private var stack = UIStackView() + public var starElements = [Star]() public var starsModel: StarsModel? { return model as? StarsModel } @@ -43,6 +44,7 @@ open class Stars: View { super.set(with: model, delegateObject, additionalData) self.delegateObject = delegateObject createStar() + updateStar() } //------------------------------------------------------ @@ -50,38 +52,37 @@ open class Stars: View { //------------------------------------------------------ func createStar() { if let starsModel = starsModel { - let stars = starsModel.stars - let percentRequiredToFillStarFully = CGFloat(100/(starsModel.stars.count)) - let numberOfFilledStars = Int(starsModel.percent/percentRequiredToFillStarFully) - for (index, starModel) in stars.enumerated() { + for starModel in starsModel.stars { let star = Star() - - //Fill the stars based on percentage. Ex: if there were 4 stars, 75 percent is 3 full stars - if index < numberOfFilledStars { - starModel.percent = 100 - } else if index == numberOfFilledStars { - let remainingProgress = (starsModel.percent).truncatingRemainder(dividingBy: percentRequiredToFillStarFully) - let fillPercent = (remainingProgress/percentRequiredToFillStarFully) * 100 - starModel.percent = fillPercent - } else { - starModel.percent = 0 - } - - starModel.backgroundColor = starsModel.starBackgroundColor - if let borderColor = starsModel.borderColor { - starModel.borderColor = borderColor - } - if let fillColor = starsModel.fillColor { - starModel.fillColor = fillColor - } - starModel.size = starsModel.size star.set(with: starModel, delegateObject, nil) stack.addArrangedSubview(star) + starElements.append(star) } heightConstraint?.constant = starsModel.size } } + //Update star progress and size with starsModel values + func updateStar() { + if let starsModel = starsModel { + let percentRequiredToFillStarFully = CGFloat(100/(starElements.count)) + let numberOfFilledStars = Int(starsModel.percent/percentRequiredToFillStarFully) + for (index, star) in starElements.enumerated() { + //Fill the stars based on percentage. Ex: if there were 4 stars, 75 percent is 3 full stars + if index < numberOfFilledStars { + star.progressBar.progress = 1 + } else if index == numberOfFilledStars { + let remainingProgress = (starsModel.percent).truncatingRemainder(dividingBy: percentRequiredToFillStarFully) + let fillPercent = remainingProgress/percentRequiredToFillStarFully + star.progressBar.progress = Float(fillPercent) + } else { + star.progressBar.progress = 0 + } + star.size = starsModel.size + } + } + } + public override func reset() { super.reset() stack.subviews.forEach({$0.removeFromSuperview()}) From 4c15bef289b81e8c47fb2dd9f7510a61e0d87222 Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Thu, 15 Oct 2020 17:03:57 +0530 Subject: [PATCH 10/16] Added accessibility and code review fixes. --- MVMCoreUI/Atomic/Atoms/Views/Star.swift | 19 +++++++++++-- MVMCoreUI/Atomic/Atoms/Views/StarModel.swift | 16 +++++------ MVMCoreUI/Atomic/Atoms/Views/Stars.swift | 27 ++++++++++++++++++- .../Strings/en.lproj/Localizable.strings | 5 ++++ .../Strings/es-MX.lproj/Localizable.strings | 4 +++ .../Strings/es.lproj/Localizable.strings | 5 ++++ 6 files changed, 63 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Star.swift b/MVMCoreUI/Atomic/Atoms/Views/Star.swift index f2a95b94..7f12d6f4 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Star.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Star.swift @@ -19,12 +19,14 @@ import Foundation return model as? StarModel } public var progressBar = UIProgressView(progressViewStyle: .bar) + public var percent: Int = 0 public var size: CGFloat = 30 { didSet { widthConstraint?.constant = size setNeedsDisplay() } } + public var borderColor: Color = Color(uiColor: .mvmBlack) //-------------------------------------------------- // MARK: - Constraints @@ -82,7 +84,7 @@ import Foundation shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.opacity = 1.0 shapeLayer.lineWidth = 1 - shapeLayer.strokeColor = starModel?.borderColor.cgColor + shapeLayer.strokeColor = borderColor.cgColor return shapeLayer } @@ -98,14 +100,27 @@ import Foundation widthConstraint?.isActive = true heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1) heightConstraint?.isActive = true + isAccessibilityElement = true + updateAccessibilityLabel() } public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) guard let model = model as? StarModel else { return } progressBar.progress = Float((model.percent) / 100.0) - progressBar.progressTintColor = model.fillColor.uiColor + percent = Int(model.percent) + progressBar.progressTintColor = (model.fillColor != nil) ? model.fillColor?.uiColor : .mvmBlack progressBar.trackTintColor = .mvmWhite + if let border = model.borderColor { + borderColor = border + } size = model.size + updateAccessibilityLabel() + } + + // MARK: - Accessibility + func updateAccessibilityLabel() { + accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "star") + accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "star_percent") ?? "", percent) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift b/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift index 1d473b86..6ab85a3b 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift @@ -16,8 +16,8 @@ open class StarModel: MoleculeModelProtocol { public static var identifier: String = "star" public var backgroundColor: Color? @Percent public var percent: CGFloat = 0 - public var borderColor: Color = Color(uiColor: .mvmBlack) - public var fillColor: Color = Color(uiColor: .mvmBlack) + public var borderColor: Color? + public var fillColor: Color? public var size: CGFloat = 30.0 //-------------------------------------------------- @@ -42,12 +42,8 @@ open class StarModel: MoleculeModelProtocol { self.percent = percent } backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) - if let borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) { - self.borderColor = borderColor - } - if let fillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .fillColor) { - self.fillColor = fillColor - } + borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) + fillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .fillColor) if let size = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .size) { self.size = size } @@ -58,8 +54,8 @@ open class StarModel: MoleculeModelProtocol { try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encode(moleculeName, forKey: .moleculeName) try container.encode(percent, forKey: .percent) - try container.encode(borderColor, forKey: .borderColor) - try container.encode(fillColor, forKey: .fillColor) + try container.encodeIfPresent(borderColor, forKey: .borderColor) + try container.encodeIfPresent(fillColor, forKey: .fillColor) try container.encode(size, forKey: .size) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift index 42224d87..742dc580 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift @@ -20,6 +20,7 @@ open class Stars: View { private var delegateObject: MVMCoreUIDelegateObject? private let itemSpacing: CGFloat = 3.0 private var heightConstraint: NSLayoutConstraint? + private var starsFilledValue: Float = 0 //-------------------------------------------------- // MARK: - Lifecycle @@ -28,11 +29,16 @@ open class Stars: View { super.setupView() stack.translatesAutoresizingMaskIntoConstraints = false addSubview(stack) - NSLayoutConstraint.pinView(toSuperview: stack, useMargins: true) + NSLayoutConstraint.constraintPinSubview(toSuperview: stack) + let leading = stack.leadingAnchor.constraint(equalTo: self.leadingAnchor) + leading.priority = UILayoutPriority(rawValue: 1000) + leading.isActive = true heightConstraint = heightAnchor.constraint(equalToConstant: 30) heightConstraint?.isActive = true stack.axis = .horizontal stack.spacing = itemSpacing + isAccessibilityElement = true + updateAccessibilityLabel() } @objc override open func updateView(_ size: CGFloat) { @@ -45,6 +51,7 @@ open class Stars: View { self.delegateObject = delegateObject createStar() updateStar() + updateAccessibilityLabel() } //------------------------------------------------------ @@ -57,6 +64,17 @@ open class Stars: View { star.set(with: starModel, delegateObject, nil) stack.addArrangedSubview(star) starElements.append(star) + star.isAccessibilityElement = false + //Star model colors should take priority over stars. + if let borderColor = starsModel.borderColor, starModel.borderColor == nil { + star.borderColor = borderColor + } + if let fillColor = starsModel.fillColor, starModel.fillColor == nil { + star.progressBar.progressTintColor = fillColor.uiColor + } + if let backgroundColor = starsModel.starBackgroundColor, starModel.backgroundColor == nil { + star.backgroundColor = backgroundColor.uiColor + } } heightConstraint?.constant = starsModel.size } @@ -67,6 +85,7 @@ open class Stars: View { if let starsModel = starsModel { let percentRequiredToFillStarFully = CGFloat(100/(starElements.count)) let numberOfFilledStars = Int(starsModel.percent/percentRequiredToFillStarFully) + starsFilledValue = Float(numberOfFilledStars) for (index, star) in starElements.enumerated() { //Fill the stars based on percentage. Ex: if there were 4 stars, 75 percent is 3 full stars if index < numberOfFilledStars { @@ -74,6 +93,7 @@ open class Stars: View { } else if index == numberOfFilledStars { let remainingProgress = (starsModel.percent).truncatingRemainder(dividingBy: percentRequiredToFillStarFully) let fillPercent = remainingProgress/percentRequiredToFillStarFully + starsFilledValue += Float(fillPercent) star.progressBar.progress = Float(fillPercent) } else { star.progressBar.progress = 0 @@ -87,4 +107,9 @@ open class Stars: View { super.reset() stack.subviews.forEach({$0.removeFromSuperview()}) } + + // MARK: - Accessibility + func updateAccessibilityLabel() { + accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "stars_filled") ?? "", starsFilledValue, starElements.count) + } } diff --git a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings index ee0b1849..f14fe02e 100644 --- a/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/en.lproj/Localizable.strings @@ -87,3 +87,8 @@ "CountDownHours" = " hours"; "CountDownMins" = " mins"; "CountDownSecs" = " secs"; + +// MARK: Star +"star" = "Star"; +"star_percent" = "%d percent progress"; +"stars_filled" = "%.1f out of %d stars"; diff --git a/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings index 0a37e1a5..4d14292c 100644 --- a/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/es-MX.lproj/Localizable.strings @@ -66,3 +66,7 @@ "CountDownHours" = " horas"; "CountDownMins" = " min"; "CountDownSecs" = " seg"; +// Star +"star" = "Estrella"; +"star_percent" = "%@ porcentaje de progreso"; +"stars_filled" = "%.1f de %d estrellas"; diff --git a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings index a64c3915..585e2c1a 100644 --- a/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings +++ b/MVMCoreUI/SupportingFiles/Strings/es.lproj/Localizable.strings @@ -70,3 +70,8 @@ "CountDownHours" = " horas"; "CountDownMins" = " min"; "CountDownSecs" = " seg"; + +// Star +"star" = "Estrella"; +"star_percent" = "%@ porcentaje de progreso"; +"stars_filled" = "%.1f de %d estrellas"; From ad7e88082c7663ec5c074cd33d14d23bbc1ced16 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Fri, 16 Oct 2020 12:59:48 -0400 Subject: [PATCH 11/16] Stars: Change UIProgressView to draw due to apple defect Remove unneeded constant Re-prioritized Create and update stars functions --- MVMCoreUI/Atomic/Atoms/Views/Star.swift | 78 +++++++++---- MVMCoreUI/Atomic/Atoms/Views/Stars.swift | 107 +++++++++--------- MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift | 8 +- 3 files changed, 106 insertions(+), 87 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Star.swift b/MVMCoreUI/Atomic/Atoms/Views/Star.swift index 7f12d6f4..87e81a72 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Star.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Star.swift @@ -18,15 +18,28 @@ import Foundation public var starModel: StarModel? { return model as? StarModel } - public var progressBar = UIProgressView(progressViewStyle: .bar) - public var percent: Int = 0 + @Percent public var percent = 0 { + didSet { + updateAccessibilityLabel() + setNeedsDisplay() + } + } public var size: CGFloat = 30 { didSet { widthConstraint?.constant = size setNeedsDisplay() } } - public var borderColor: Color = Color(uiColor: .mvmBlack) + public var fillColor = UIColor.mvmBlack { + didSet { + setNeedsDisplay() + } + } + public var borderColor: CGColor = UIColor.mvmBlack.cgColor { + didSet { + setNeedsDisplay() + } + } //-------------------------------------------------- // MARK: - Constraints @@ -38,7 +51,14 @@ import Foundation // MARK: - State Handling //------------------------------------------------------ open override func draw(_ rect: CGRect) { - //Draw the heart + //Draw progress + let width = bounds.size.width * percent / 100.0 + let progressRect = CGRect(x: 0, y: 0, width: width, height: bounds.height) + fillColor.set() + guard let context = UIGraphicsGetCurrentContext() else { return } + context.fill(progressRect) + + //Draw the star starLayer?.removeFromSuperlayer() let star = drawStar() layer.addSublayer(star) @@ -84,18 +104,40 @@ import Foundation shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.opacity = 1.0 shapeLayer.lineWidth = 1 - shapeLayer.strokeColor = borderColor.cgColor + shapeLayer.strokeColor = borderColor return shapeLayer } - + //-------------------------------------------------- - // MARK: - Lifecycle + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + open override func reset() { + super.reset() + borderColor = UIColor.mvmBlack.cgColor + fillColor = .mvmBlack + percent = 0 + } + + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? StarModel else { return } + percent = model.percent + if let fillColor = model.fillColor?.uiColor { + self.fillColor = fillColor + } + if let borderColor = model.borderColor?.cgColor { + self.borderColor = borderColor + } + size = model.size + updateAccessibilityLabel() + } + + //-------------------------------------------------- + // MARK: - MVMCoreViewProtocol //-------------------------------------------------- open override func setupView() { super.setupView() - progressBar.translatesAutoresizingMaskIntoConstraints = false - addSubview(progressBar) - NSLayoutConstraint.constraintPinSubview(toSuperview: progressBar) + backgroundColor = .clear widthConstraint = widthAnchor.constraint(equalToConstant: 30) widthConstraint?.isActive = true heightConstraint = heightAnchor.constraint(equalTo: widthAnchor, multiplier: 1) @@ -104,23 +146,9 @@ import Foundation updateAccessibilityLabel() } - public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { - super.set(with: model, delegateObject, additionalData) - guard let model = model as? StarModel else { return } - progressBar.progress = Float((model.percent) / 100.0) - percent = Int(model.percent) - progressBar.progressTintColor = (model.fillColor != nil) ? model.fillColor?.uiColor : .mvmBlack - progressBar.trackTintColor = .mvmWhite - if let border = model.borderColor { - borderColor = border - } - size = model.size - updateAccessibilityLabel() - } - // MARK: - Accessibility func updateAccessibilityLabel() { accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "star") - accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "star_percent") ?? "", percent) + accessibilityValue = String(format: MVMCoreUIUtility.hardcodedString(withKey: "star_percent") ?? "", Int(percent)) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift index 742dc580..07a78e95 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift @@ -13,7 +13,6 @@ open class Stars: View { // MARK: - Properties //-------------------------------------------------- private var stack = UIStackView() - public var starElements = [Star]() public var starsModel: StarsModel? { return model as? StarsModel } @@ -23,20 +22,17 @@ open class Stars: View { private var starsFilledValue: Float = 0 //-------------------------------------------------- - // MARK: - Lifecycle + // MARK: - MVMCoreViewProtocol //-------------------------------------------------- open override func setupView() { super.setupView() stack.translatesAutoresizingMaskIntoConstraints = false - addSubview(stack) - NSLayoutConstraint.constraintPinSubview(toSuperview: stack) - let leading = stack.leadingAnchor.constraint(equalTo: self.leadingAnchor) - leading.priority = UILayoutPriority(rawValue: 1000) - leading.isActive = true - heightConstraint = heightAnchor.constraint(equalToConstant: 30) - heightConstraint?.isActive = true stack.axis = .horizontal stack.spacing = itemSpacing + addSubview(stack) + NSLayoutConstraint.constraintPinSubview(toSuperview: stack) + heightConstraint = heightAnchor.constraint(equalToConstant: 30) + heightConstraint?.isActive = true isAccessibilityElement = true updateAccessibilityLabel() } @@ -46,70 +42,69 @@ open class Stars: View { stack.updateView(size) } + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- open override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { super.set(with: model, delegateObject, additionalData) self.delegateObject = delegateObject - createStar() - updateStar() + createStars() + updateStars() updateAccessibilityLabel() } + public override func reset() { + stack.subviews.forEach({$0.removeFromSuperview()}) + super.reset() + } + //------------------------------------------------------ // MARK: - Methods //------------------------------------------------------ - func createStar() { - if let starsModel = starsModel { - for starModel in starsModel.stars { - let star = Star() - star.set(with: starModel, delegateObject, nil) - stack.addArrangedSubview(star) - starElements.append(star) - star.isAccessibilityElement = false - //Star model colors should take priority over stars. - if let borderColor = starsModel.borderColor, starModel.borderColor == nil { - star.borderColor = borderColor - } - if let fillColor = starsModel.fillColor, starModel.fillColor == nil { - star.progressBar.progressTintColor = fillColor.uiColor - } - if let backgroundColor = starsModel.starBackgroundColor, starModel.backgroundColor == nil { - star.backgroundColor = backgroundColor.uiColor - } - } - heightConstraint?.constant = starsModel.size + func createStars() { + guard let starsModel = starsModel else { return } + for starModel in starsModel.stars { + let star = Star(model: starModel, delegateObject, nil) + star.isAccessibilityElement = false + stack.addArrangedSubview(star) } + heightConstraint?.constant = starsModel.size } - //Update star progress and size with starsModel values - func updateStar() { - if let starsModel = starsModel { - let percentRequiredToFillStarFully = CGFloat(100/(starElements.count)) - let numberOfFilledStars = Int(starsModel.percent/percentRequiredToFillStarFully) - starsFilledValue = Float(numberOfFilledStars) - for (index, star) in starElements.enumerated() { - //Fill the stars based on percentage. Ex: if there were 4 stars, 75 percent is 3 full stars - if index < numberOfFilledStars { - star.progressBar.progress = 1 - } else if index == numberOfFilledStars { - let remainingProgress = (starsModel.percent).truncatingRemainder(dividingBy: percentRequiredToFillStarFully) - let fillPercent = remainingProgress/percentRequiredToFillStarFully - starsFilledValue += Float(fillPercent) - star.progressBar.progress = Float(fillPercent) - } else { - star.progressBar.progress = 0 - } - star.size = starsModel.size + func updateStars() { + guard let starsModel = starsModel else { return } + let percentRequiredToFillStarFully = CGFloat(100/(stack.arrangedSubviews.count)) + let numberOfFilledStars = Int(starsModel.percent/percentRequiredToFillStarFully) + starsFilledValue = Float(numberOfFilledStars) + for case let (index, star as Star) in stack.arrangedSubviews.enumerated() { + //Star model colors should take priority over stars. + if let borderColor = starsModel.borderColor?.cgColor, star.starModel?.borderColor == nil { + star.borderColor = borderColor } + if let fillColor = starsModel.fillColor?.uiColor, star.starModel?.fillColor == nil { + star.fillColor = fillColor + } + if let backgroundColor = starsModel.starBackgroundColor?.uiColor, star.starModel?.backgroundColor == nil { + star.backgroundColor = backgroundColor + } + + //Fill the stars based on percentage. Ex: if there were 4 stars, 75 percent is 3 full stars + if index < numberOfFilledStars { + star.percent = 100 + } else if index == numberOfFilledStars { + let remainingProgress = (starsModel.percent).truncatingRemainder(dividingBy: percentRequiredToFillStarFully) + let fillPercent = remainingProgress/percentRequiredToFillStarFully + starsFilledValue += Float(fillPercent) + star.percent = fillPercent * 100 + } else { + star.percent = 0 + } + star.size = starsModel.size } } - public override func reset() { - super.reset() - stack.subviews.forEach({$0.removeFromSuperview()}) - } - // MARK: - Accessibility func updateAccessibilityLabel() { - accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "stars_filled") ?? "", starsFilledValue, starElements.count) + accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "stars_filled") ?? "", starsFilledValue, stack.arrangedSubviews.count) } } diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift b/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift index ad52b18e..27515a64 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift @@ -47,12 +47,8 @@ import Foundation if let percent = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .percent) { self.percent = percent } - if let borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) { - self.borderColor = borderColor - } - if let fillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .fillColor) { - self.fillColor = fillColor - } + borderColor = try typeContainer.decodeIfPresent(Color.self, forKey: .borderColor) + fillColor = try typeContainer.decodeIfPresent(Color.self, forKey: .fillColor) if let size = try typeContainer.decodeIfPresent(CGFloat.self, forKey: .size) { self.size = size } From 2702f105b7d72c722ef70b53e06928d8b288efbc Mon Sep 17 00:00:00 2001 From: Lekshmi S Date: Thu, 22 Oct 2020 18:21:55 +0530 Subject: [PATCH 12/16] Code changes after review. --- MVMCoreUI/Atomic/Atoms/Views/Star.swift | 20 ++++++++++++------- MVMCoreUI/Atomic/Atoms/Views/StarModel.swift | 2 -- MVMCoreUI/Atomic/Atoms/Views/Stars.swift | 2 -- MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift | 2 -- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/Star.swift b/MVMCoreUI/Atomic/Atoms/Views/Star.swift index 87e81a72..30ca8e4f 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Star.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Star.swift @@ -6,14 +6,13 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation - @objcMembers open class Star: View { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- private var starLayer: CAShapeLayer? + private var progressLayer: CAShapeLayer? private let maskLayer = CAShapeLayer() public var starModel: StarModel? { return model as? StarModel @@ -52,11 +51,10 @@ import Foundation //------------------------------------------------------ open override func draw(_ rect: CGRect) { //Draw progress - let width = bounds.size.width * percent / 100.0 - let progressRect = CGRect(x: 0, y: 0, width: width, height: bounds.height) - fillColor.set() - guard let context = UIGraphicsGetCurrentContext() else { return } - context.fill(progressRect) + progressLayer?.removeFromSuperlayer() + let progress = drawProgress() + layer.addSublayer(progress) + progressLayer = progress //Draw the star starLayer?.removeFromSuperlayer() @@ -70,6 +68,14 @@ import Foundation layer.mask = maskLayer } + func drawProgress() -> CAShapeLayer { + let shapeLayer = CAShapeLayer() + let width = bounds.size.width * percent / 100.0 + shapeLayer.path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: width, height: bounds.height)).cgPath + shapeLayer.fillColor = fillColor.cgColor + return shapeLayer + } + func drawStar() -> CAShapeLayer { let shapeLayer = CAShapeLayer() shapeLayer.frame = bounds diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift b/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift index 6ab85a3b..dc404a71 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/StarModel.swift @@ -6,8 +6,6 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation - open class StarModel: MoleculeModelProtocol { //-------------------------------------------------- diff --git a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift index 07a78e95..268f955e 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/Stars.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/Stars.swift @@ -6,8 +6,6 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation - open class Stars: View { //-------------------------------------------------- // MARK: - Properties diff --git a/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift b/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift index 27515a64..91470100 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/StarsModel.swift @@ -6,8 +6,6 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import Foundation - @objcMembers public class StarsModel: MoleculeModelProtocol { //-------------------------------------------------- From fd735b81394c87b7731465b56628084082fe7226 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 22 Oct 2020 11:16:54 -0400 Subject: [PATCH 13/16] accessibility and formatting --- .../Atomic/Atoms/Views/LoadImageView.swift | 170 +++++++++++------- .../BGImageHeadlineBodyButton.swift | 1 - .../HeadlineBody.swift | 16 +- 3 files changed, 117 insertions(+), 70 deletions(-) diff --git a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift index 42267a20..0ad4c16b 100644 --- a/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift +++ b/MVMCoreUI/Atomic/Atoms/Views/LoadImageView.swift @@ -6,18 +6,21 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -import UIKit @objcMembers open class LoadImageView: View { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + public let loadingSpinner = MFLoadingSpinner(frame: .zero) public let imageView = MFTransparentGIFView(frame: .zero) + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + public var addSizeConstraintsForAspectRatio = true public var shouldNotifyDelegateOnUpdate = true - var centerX: NSLayoutConstraint? - var centerY: NSLayoutConstraint? - var widthConstraint: NSLayoutConstraint? - var heightConstraint: NSLayoutConstraint? - var loadingSpinnerHeightConstraint: NSLayoutConstraint? // Allows for a view to hardcode which height to use if there is none in the json. var imageWidth: CGFloat? @@ -35,6 +38,25 @@ import UIKit private var containerHelper = ContainerHelper() + open override var accessibilityLabel: String? { + get { imageView.accessibilityLabel } + set { imageView.accessibilityLabel = newValue } + } + + //-------------------------------------------------- + // MARK: - Constraint Properties + //-------------------------------------------------- + + var centerX: NSLayoutConstraint? + var centerY: NSLayoutConstraint? + var widthConstraint: NSLayoutConstraint? + var heightConstraint: NSLayoutConstraint? + var loadingSpinnerHeightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + public init(pinnedEdges edge: UIRectEdge) { edges = edge super.init(frame: .zero) @@ -60,51 +82,24 @@ import UIKit set(with: model, delegateObject, additionalData) } - public func pinEdges(_ edge: UIRectEdge) { - edges = edge - if edge == UIRectEdge.all { - imageView.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.horizontal) - imageView.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.vertical) - } else { - imageView.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: NSLayoutConstraint.Axis.horizontal) - imageView.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: NSLayoutConstraint.Axis.vertical) - } - - if edge.contains(UIRectEdge.top) && edge.contains(UIRectEdge.bottom) { - containerHelper.alignVertical(.fill) - } else if edge.contains(UIRectEdge.top) { - containerHelper.alignVertical(.leading) - } else if edge.contains(UIRectEdge.bottom) { - containerHelper.alignVertical(.trailing) - } else { - containerHelper.alignVertical(.center) - } - - if edge.contains(UIRectEdge.left) && edge.contains(UIRectEdge.right) { - containerHelper.alignHorizontal(.fill) - } else if edge.contains(UIRectEdge.left) { - containerHelper.alignHorizontal(.leading) - } else if edge.contains(UIRectEdge.right) { - containerHelper.alignHorizontal(.trailing) - } else { - containerHelper.alignHorizontal(.center) - } - } + //-------------------------------------------------- + // MARK: - MVMCoreViewProtocol + //-------------------------------------------------- override open func setupView() { super.setupView() - guard subviews.count == 0 else { - return - } + + guard subviews.count == 0 else { return } + clipsToBounds = true - setContentHuggingPriority(UILayoutPriority(rawValue: 950), for: NSLayoutConstraint.Axis.horizontal) - setContentHuggingPriority(UILayoutPriority(rawValue: 950), for: NSLayoutConstraint.Axis.vertical) - + setContentHuggingPriority(UILayoutPriority(rawValue: 950), for: .horizontal) + setContentHuggingPriority(UILayoutPriority(rawValue: 950), for: .vertical) + // Setup image. imageView.translatesAutoresizingMaskIntoConstraints = false; addSubview(imageView) containerHelper.constrainView(imageView) - + // Setup edges constraints pinEdges(edges) @@ -125,8 +120,12 @@ import UIKit } } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + open func defaultCompletionBlock() -> MVMCoreGetImageBlock { - return {image,gifData,_ in MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in + return { image, gifData,_ in MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in if let image = image { self?.imageView.image = image self?.layoutIfNeeded() @@ -139,9 +138,8 @@ import UIKit open func shouldLoadImage(withName imageName: String?, width: CGFloat) -> Bool { // We should load a new image if there is no current image, the image names are different, the width is different, or we are using a fallback image. - guard let currentWidth = self.currentImageWidth else { - return true - } + guard let currentWidth = self.currentImageWidth else { return true } + return (imageView.image == nil && imageView.animatedImage == nil) || imageName != currentImageName || !MVMCoreGetterUtility.cgfequal(width, currentWidth) || isFallbackImage } @@ -150,22 +148,61 @@ import UIKit if ((imageView.image == nil && imageView.animatedImage == nil) || imageName != currentImageName || isFallbackImage) { return true } + // load new image if the width is different if let oldWidth = self.currentImageWidth, let newWidth = width, !MVMCoreGetterUtility.cgfequal(oldWidth, newWidth) { return true + } else if (self.currentImageWidth == nil) != (width == nil) { return true } + // load new image if the height is different if let oldHeight = self.currentImageHeight, let newHeight = height, !MVMCoreGetterUtility.cgfequal(oldHeight, newHeight) { return true + } else if (self.currentImageHeight == nil) || (height == nil) { return true } + return false } - // MARK: - Constraints + //-------------------------------------------------- + // MARK: - Constraint Methods + //-------------------------------------------------- + + public func pinEdges(_ edge: UIRectEdge) { + edges = edge + + if edge == .all { + imageView.setContentHuggingPriority(.defaultLow, for: .horizontal) + imageView.setContentHuggingPriority(.defaultLow, for: .vertical) + } else { + imageView.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: .horizontal) + imageView.setContentHuggingPriority(UILayoutPriority(rawValue: 900), for: .vertical) + } + + if edge.contains(.top) && edge.contains(.bottom) { + containerHelper.alignVertical(.fill) + } else if edge.contains(.top) { + containerHelper.alignVertical(.leading) + } else if edge.contains(.bottom) { + containerHelper.alignVertical(.trailing) + } else { + containerHelper.alignVertical(.center) + } + + if edge.contains(.left) && edge.contains(.right) { + containerHelper.alignHorizontal(.fill) + } else if edge.contains(.left) { + containerHelper.alignHorizontal(.leading) + } else if edge.contains(.right) { + containerHelper.alignHorizontal(.trailing) + } else { + containerHelper.alignHorizontal(.center) + } + } func setHeight(_ height: CGFloat) { if let heightConstraint = heightConstraint, MVMCoreGetterUtility.cgfequal(heightConstraint.multiplier, 1) { @@ -187,9 +224,8 @@ import UIKit } func layoutWillChange(width: CGFloat?, height: CGFloat?, size: CGSize?) -> Bool { - guard addSizeConstraintsForAspectRatio else { - return false - } + guard addSizeConstraintsForAspectRatio else { return false } + let widthWillChange = !MVMCoreGetterUtility.cgfequal(widthConstraint?.constant ?? 0, width ?? 0) let heightWillChange = !MVMCoreGetterUtility.cgfequal(heightConstraint?.constant ?? 0, height ?? 0) let sizeWillChange = (width == nil || height == nil) && !(size?.equalTo(imageView.image?.size ?? CGSize.zero) ?? false) @@ -199,11 +235,11 @@ import UIKit // Constrains the image view to be the size provided. Used to size it to the image to fix aspect fit defect. func addConstraints(width: NSNumber?, height: NSNumber?, size: CGSize?) { + widthConstraint?.isActive = false heightConstraint?.isActive = false - guard addSizeConstraintsForAspectRatio else { - return - } + + guard addSizeConstraintsForAspectRatio else { return } if let width = width, let height = height { setHeight(height.cgfloat()) @@ -221,31 +257,40 @@ import UIKit } } + //-------------------------------------------------- + // MARK: - MoleculeViewProtocol + //-------------------------------------------------- + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { self.delegateObject = delegateObject super.set(with: model, delegateObject, additionalData) - + guard let imageModel = model as? ImageViewModel else { return } + if let accessibilityString = imageModel.accessibilityText { imageView.accessibilityLabel = accessibilityString imageView.accessibilityTraits = .staticText imageView.isAccessibilityElement = true } + let width = imageModel.width ?? imageWidth let height = imageModel.height ?? imageHeight + // For smoother transitions, set constraints that we know immediately. if let width = width, addSizeConstraintsForAspectRatio { setWidth(width) } + if let height = height, addSizeConstraintsForAspectRatio { setHeight(height) } + if shouldLoadImage(withName: imageModel.image, width: width, height: height) { imageView.image = nil imageView.animatedImage = nil loadImage(withName: imageModel.image, format: imageModel.imageFormat, width: width as NSNumber?, height: height as NSNumber?, customFallbackImage: imageModel.fallbackImage, localBundle: imageModel.localBundle) } - + if let contentMode = imageModel.contentMode { imageView.contentMode = contentMode } @@ -260,7 +305,10 @@ import UIKit } } - // MARK: - load functions + //-------------------------------------------------- + // MARK: - Load Methods + //-------------------------------------------------- + public func loadImage(withName imageName: String?, format: String? = nil, width: NSNumber? = nil, height: NSNumber? = nil, customFallbackImage: String? = nil, allowServerParameters: Bool = false, localBundle: Bundle? = nil, completionHandler: MVMCoreGetImageBlock? = nil) { let completionBlock = completionHandler ?? defaultCompletionBlock() @@ -275,7 +323,7 @@ import UIKit let finishedLoadingBlock: MVMCoreGetImageBlock = {[weak self] (image, data, isFallbackImage) in MVMCoreDispatchUtility.performBlock(onMainThread: { [weak self] in guard let self = self, - let loadingImageName = self.currentImageName, loadingImageName == imageName else { return } + let loadingImageName = self.currentImageName, loadingImageName == imageName else { return } self.isFallbackImage = isFallbackImage self.loadingSpinner.pause() let layoutWillChange = self.shouldNotifyDelegateOnUpdate ? self.layoutWillChange(width: self.currentImageWidth, height: self.currentImageHeight, size: image?.size) : false @@ -298,12 +346,12 @@ import UIKit } public func loadCroppedImage(withName imageName: - String?, width: NSNumber?, height: NSNumber?, cropRect: CGRect, flipImage: Bool, customFallbackImage: String?) { + String?, width: NSNumber?, height: NSNumber?, cropRect: CGRect, flipImage: Bool, customFallbackImage: String?) { MVMCoreDispatchUtility.performBlock(onMainThread: { [unowned self] in self.currentImageName = imageName self.loadingSpinner.resumeSpinnerAfterDelay() self.loadingSpinnerHeightConstraint?.constant = self.spinnerHeight - + MVMCoreCache.shared()?.getCroppedImage(imageName, useWidth: width != nil, widthForS7: width?.intValue ?? 0, useHeight: height != nil, heightForS7: height?.intValue ?? 0, finalRect: cropRect, flipImage: flipImage, localFallbackImageName: customFallbackImage ?? MVMCoreUIUtility.localizedImageName("fallback"), completionHandler: { [weak self] (image, data, isFallBackImage) in MVMCoreDispatchUtility.performBlock(onMainThread: { guard let image = image, let loadingImageName = self?.currentImageName, loadingImageName == imageName else { @@ -327,7 +375,7 @@ import UIKit public func loadImage(withName imageName: String?) { loadImage(withName: imageName, format: nil, width: nil, height: nil, customFallbackImage: nil, completionHandler: defaultCompletionBlock()) } - + public func loadImage(withName imageName: String?, width: NSNumber?) { loadImage(withName: imageName, format: nil, width: width, height: nil, customFallbackImage: nil, completionHandler: defaultCompletionBlock()) } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButton.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButton.swift index bac1b1c2..3c6fd2bd 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButton.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/BGImageHeadlineBodyButton.swift @@ -135,7 +135,6 @@ } open override func accessibilityActivate() -> Bool { - return button.accessibilityActivate() } } diff --git a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift index 48d6c129..d111d1c5 100644 --- a/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift +++ b/MVMCoreUI/Atomic/Molecules/VerticalCombinationViews/HeadlineBody.swift @@ -79,7 +79,7 @@ open class HeadlineBody: View { } //-------------------------------------------------- - // MARK: - Setup + // MARK: - MVMCoreViewProtocol //-------------------------------------------------- open override func setupView() { @@ -123,6 +123,13 @@ open class HeadlineBody: View { view.bottomAnchor.constraint(equalTo: messageLabel.bottomAnchor).isActive = true } + open override func updateView(_ size: CGFloat) { + super.updateView(size) + headlineLabel.updateView(size) + messageLabel.updateView(size) + setSpacing() + } + //-------------------------------------------------- // MARK: - Constraining //-------------------------------------------------- @@ -139,13 +146,6 @@ open class HeadlineBody: View { // MARK: - MoleculeViewProtocol //-------------------------------------------------- - open override func updateView(_ size: CGFloat) { - super.updateView(size) - headlineLabel.updateView(size) - messageLabel.updateView(size) - setSpacing() - } - public override class func estimatedHeight(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> CGFloat? { return 58 } From c25e6369a427592fa0ea6f01c9319273247d54f4 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 22 Oct 2020 12:56:44 -0400 Subject: [PATCH 14/16] Border lines for carousel --- MVMCoreUI.xcodeproj/project.pbxproj | 20 ++++++- .../Organisms/{ => Carousel}/Carousel.swift | 8 +-- .../Carousel/CarouselCollectionLayout.swift | 53 +++++++++++++++++++ .../{ => Carousel}/CarouselModel.swift | 0 .../Carousel/LineDecorationView.swift | 20 +++++++ 5 files changed, 95 insertions(+), 6 deletions(-) rename MVMCoreUI/Atomic/Organisms/{ => Carousel}/Carousel.swift (99%) create mode 100644 MVMCoreUI/Atomic/Organisms/Carousel/CarouselCollectionLayout.swift rename MVMCoreUI/Atomic/Organisms/{ => Carousel}/CarouselModel.swift (100%) create mode 100644 MVMCoreUI/Atomic/Organisms/Carousel/LineDecorationView.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index e6eca162..4b0faceb 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -496,6 +496,8 @@ D2E2A99F23E07F8A000B42E6 /* PillButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A99E23E07F8A000B42E6 /* PillButton.swift */; }; D2E2A9A123E095AB000B42E6 /* ButtonModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */; }; D2E2A9A323E096B1000B42E6 /* DisableableModelProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */; }; + D2EAC0A02541EFFB00AA9495 /* LineDecorationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EAC09F2541EFFB00AA9495 /* LineDecorationView.swift */; }; + D2EAC0A42541F00F00AA9495 /* CarouselCollectionLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EAC0A32541F00F00AA9495 /* CarouselCollectionLayout.swift */; }; D2EC7BD52527B7A600F540AF /* MoleculeSectionHeaderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EC7BD42527B7A600F540AF /* MoleculeSectionHeaderModel.swift */; }; D2EC7BD92527B7CF00F540AF /* MoleculeSectionHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EC7BD82527B7CF00F540AF /* MoleculeSectionHeader.swift */; }; D2EC7BDD2527B83700F540AF /* SectionHeaderFooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2EC7BDC2527B83700F540AF /* SectionHeaderFooterView.swift */; }; @@ -1007,6 +1009,8 @@ D2E2A99E23E07F8A000B42E6 /* PillButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PillButton.swift; sourceTree = ""; }; D2E2A9A023E095AB000B42E6 /* ButtonModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonModelProtocol.swift; sourceTree = ""; }; D2E2A9A223E096B1000B42E6 /* DisableableModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisableableModelProtocol.swift; sourceTree = ""; }; + D2EAC09F2541EFFB00AA9495 /* LineDecorationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineDecorationView.swift; sourceTree = ""; }; + D2EAC0A32541F00F00AA9495 /* CarouselCollectionLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CarouselCollectionLayout.swift; sourceTree = ""; }; D2EC7BD42527B7A600F540AF /* MoleculeSectionHeaderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeSectionHeaderModel.swift; sourceTree = ""; }; D2EC7BD82527B7CF00F540AF /* MoleculeSectionHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoleculeSectionHeader.swift; sourceTree = ""; }; D2EC7BDC2527B83700F540AF /* SectionHeaderFooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SectionHeaderFooterView.swift; sourceTree = ""; }; @@ -1413,12 +1417,11 @@ D22479902316A9CB003FCCF9 /* Organisms */ = { isa = PBXGroup; children = ( + D2EAC09E2541EFE200AA9495 /* Carousel */, D260105A23D0BB7100764D80 /* StackModelProtocol.swift */, D260106423D0CEA700764D80 /* StackModel.swift */, D260105C23D0BCD400764D80 /* Stack.swift */, D21B7F5E2437C5BC00051ABF /* MoleculeStackView.swift */, - 012A88AE238C626E00FE3DA1 /* CarouselModel.swift */, - D2A6390022CBB1820052ED1F /* Carousel.swift */, ); path = Organisms; sourceTree = ""; @@ -2142,6 +2145,17 @@ path = TopNotification; sourceTree = ""; }; + D2EAC09E2541EFE200AA9495 /* Carousel */ = { + isa = PBXGroup; + children = ( + 012A88AE238C626E00FE3DA1 /* CarouselModel.swift */, + D2A6390022CBB1820052ED1F /* Carousel.swift */, + D2EAC09F2541EFFB00AA9495 /* LineDecorationView.swift */, + D2EAC0A32541F00F00AA9495 /* CarouselCollectionLayout.swift */, + ); + path = Carousel; + sourceTree = ""; + }; D2EC7BD22527A1E400F540AF /* HeadersAndFooters */ = { isa = PBXGroup; children = ( @@ -2326,6 +2340,7 @@ 0A6682B5243769C700AD3CA1 /* TextView.swift in Sources */, D224799B231965AD003FCCF9 /* AccordionMoleculeTableViewCell.swift in Sources */, D21B7F602437C5BC00051ABF /* MoleculeStackView.swift in Sources */, + D2EAC0A02541EFFB00AA9495 /* LineDecorationView.swift in Sources */, 0A6682A42434DB8D00AD3CA1 /* ListLeftVariableRadioButtonBodyTextModel.swift in Sources */, AA2AD116244EE46800BBFFE3 /* ListDeviceComplexLinkMedium.swift in Sources */, AA7F32AD246C0F8C00C965BA /* ListLeftVariableRadioButtonAllTextAndLinks.swift in Sources */, @@ -2573,6 +2588,7 @@ 279B1569242BBC2F00921D6C /* ActionModelAdapter.swift in Sources */, BB6C6AC0242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTallModel.swift in Sources */, 8DEFA95E243DAC2F000D27E5 /* ListThreeColumnDataUsageDivider.swift in Sources */, + D2EAC0A42541F00F00AA9495 /* CarouselCollectionLayout.swift in Sources */, AAE7270E24AC8B9300A3ED0E /* HeadersH2CaretLink.swift in Sources */, 0AD93A9F24C0AA5100E56A97 /* ImageView.swift in Sources */, D2A638FD22CA98280052ED1F /* HeadlineBody.swift in Sources */, diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift similarity index 99% rename from MVMCoreUI/Atomic/Organisms/Carousel.swift rename to MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index 1abab6e0..7f088b4c 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -16,11 +16,10 @@ public protocol CarouselPageControlProtocol { func scrollViewDidScroll(_ collectionView: UICollectionView) } - open class Carousel: View { public let collectionView: CollectionView = { - let layout = UICollectionViewFlowLayout() + let layout = CarouselCollectionLayout() layout.scrollDirection = .horizontal layout.minimumInteritemSpacing = 0 layout.minimumLineSpacing = 0 @@ -150,8 +149,9 @@ open class Carousel: View { guard let carouselModel = model as? CarouselModel else { return } accessibilityLabel = carouselModel.accessibilityText collectionView.backgroundColor = backgroundColor - collectionView.layer.borderColor = backgroundColor?.cgColor + collectionView.layer.borderColor = UIColor.black.cgColor collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0 + (collectionView.collectionViewLayout as? CarouselCollectionLayout)?.useLines = (carouselModel.border ?? false) backgroundColor = .white (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing = carouselModel.spacing ?? 0 @@ -375,7 +375,7 @@ extension Carousel: UICollectionViewDataSource { open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return molecules?.count ?? 0 } - + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { guard let molecule = molecules?[indexPath.row], let moleculeInfo = getMoleculeInfo(with: molecule, delegateObject: nil) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselCollectionLayout.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselCollectionLayout.swift new file mode 100644 index 00000000..09841c50 --- /dev/null +++ b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselCollectionLayout.swift @@ -0,0 +1,53 @@ +// +// CarouselCollectionLayout.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 10/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class CarouselCollectionLayout: UICollectionViewFlowLayout { + open var useLines = false + + public override init() { + super.init() + registerViews() + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + registerViews() + } + + open func registerViews() { + register(LineDecorationView.self, forDecorationViewOfKind: LineDecorationView.elementKind) + } + + open override func layoutAttributesForDecorationView(ofKind elementKind: String, at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? { + // Lines are added between each item + guard useLines, + elementKind == LineDecorationView.elementKind else { return nil } + let attributes = UICollectionViewLayoutAttributes(forDecorationViewOfKind: elementKind, with: indexPath) + let itemFrame = layoutAttributesForItem(at: indexPath)?.frame ?? .zero + let width: CGFloat = 1.0 + attributes.frame = CGRect(x: itemFrame.maxX + ((minimumLineSpacing - width) / 2.0), y: 0, width: width, height: itemFrame.height) + return attributes + } + + open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { + guard let allAttributes = super.layoutAttributesForElements(in: rect) else { return nil } + guard useLines else { return allAttributes } + + // Add line decorators. + var newAttributes = allAttributes + allAttributes.forEach { (attributes) in + guard attributes.representedElementCategory == .cell, + let lineAttributes = layoutAttributesForDecorationView(ofKind: LineDecorationView.elementKind, at: attributes.indexPath), + rect.intersects(lineAttributes.frame) else { return } + newAttributes.append(lineAttributes) + } + return newAttributes + } +} diff --git a/MVMCoreUI/Atomic/Organisms/CarouselModel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift similarity index 100% rename from MVMCoreUI/Atomic/Organisms/CarouselModel.swift rename to MVMCoreUI/Atomic/Organisms/Carousel/CarouselModel.swift diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/LineDecorationView.swift b/MVMCoreUI/Atomic/Organisms/Carousel/LineDecorationView.swift new file mode 100644 index 00000000..465f2219 --- /dev/null +++ b/MVMCoreUI/Atomic/Organisms/Carousel/LineDecorationView.swift @@ -0,0 +1,20 @@ +// +// LineDecorationView.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 10/22/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +public class LineDecorationView : UICollectionReusableView { + static public let elementKind = "line" + + public override init(frame: CGRect) { + super.init(frame:frame) + backgroundColor = .black + } + + public required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + } +} From 256927760f5773177a440bcecbab0b2a294bf592 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 22 Oct 2020 13:06:26 -0400 Subject: [PATCH 15/16] background color set to clear like android --- .../Atomic/Molecules/Items/MoleculeCollectionViewCell.swift | 1 - MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift | 2 -- MVMCoreUI/BaseClasses/CollectionViewCell.swift | 2 +- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift index f6fe580f..04694bc1 100644 --- a/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift +++ b/MVMCoreUI/Atomic/Molecules/Items/MoleculeCollectionViewCell.swift @@ -30,7 +30,6 @@ open class MoleculeCollectionViewCell: CollectionViewCell { open override func reset() { super.reset() molecule?.reset() - backgroundColor = .mvmWhite } open override class func nameForReuse(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?) -> String? { diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index 7f088b4c..04fc5e70 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -148,11 +148,9 @@ open class Carousel: View { guard let carouselModel = model as? CarouselModel else { return } accessibilityLabel = carouselModel.accessibilityText - collectionView.backgroundColor = backgroundColor collectionView.layer.borderColor = UIColor.black.cgColor collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0 (collectionView.collectionViewLayout as? CarouselCollectionLayout)?.useLines = (carouselModel.border ?? false) - backgroundColor = .white (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing = carouselModel.spacing ?? 0 itemWidthPercent = carouselModel.itemWidthPercent / 100.0 diff --git a/MVMCoreUI/BaseClasses/CollectionViewCell.swift b/MVMCoreUI/BaseClasses/CollectionViewCell.swift index ce910bfb..4ac48dfc 100644 --- a/MVMCoreUI/BaseClasses/CollectionViewCell.swift +++ b/MVMCoreUI/BaseClasses/CollectionViewCell.swift @@ -87,7 +87,7 @@ open class CollectionViewCell: UICollectionViewCell, MoleculeViewProtocol, MVMCo open func reset() { molecule?.reset() - backgroundColor = .mvmWhite + backgroundColor = .clear width = nil } From 901b7438ea65e02cafe3ae4363f0e2be82f6e7e3 Mon Sep 17 00:00:00 2001 From: "Pfeil, Scott Robert" Date: Thu, 22 Oct 2020 14:14:09 -0400 Subject: [PATCH 16/16] update border color don't show line for last cell --- .../Atomic/Organisms/Carousel/Carousel.swift | 4 ++-- .../Carousel/CarouselCollectionLayout.swift | 16 ++++++++++++++-- .../Organisms/Carousel/LineDecorationView.swift | 4 ++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift index 04fc5e70..31222828 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/Carousel.swift @@ -148,9 +148,9 @@ open class Carousel: View { guard let carouselModel = model as? CarouselModel else { return } accessibilityLabel = carouselModel.accessibilityText - collectionView.layer.borderColor = UIColor.black.cgColor + collectionView.layer.borderColor = UIColor.mvmCoolGray3.cgColor collectionView.layer.borderWidth = (carouselModel.border ?? false) ? 1 : 0 - (collectionView.collectionViewLayout as? CarouselCollectionLayout)?.useLines = (carouselModel.border ?? false) + (collectionView.collectionViewLayout as? CarouselCollectionLayout)?.useLines = carouselModel.border ?? false (collectionView.collectionViewLayout as? UICollectionViewFlowLayout)?.minimumLineSpacing = carouselModel.spacing ?? 0 itemWidthPercent = carouselModel.itemWidthPercent / 100.0 diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselCollectionLayout.swift b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselCollectionLayout.swift index 09841c50..9a7ff06e 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/CarouselCollectionLayout.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/CarouselCollectionLayout.swift @@ -36,14 +36,26 @@ open class CarouselCollectionLayout: UICollectionViewFlowLayout { return attributes } + func shouldHaveLine(for indexPath: IndexPath) -> Bool { + // No line for the final index + guard let numberOfSections = collectionView?.numberOfSections, + numberOfSections > 0, + let numberOfItemsInLastSection = collectionView?.numberOfItems(inSection: numberOfSections - 1), + numberOfItemsInLastSection > 0, + indexPath.section == numberOfSections - 1, + indexPath.row == numberOfItemsInLastSection - 1 else { return true } + return false + } + open override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? { guard let allAttributes = super.layoutAttributesForElements(in: rect) else { return nil } guard useLines else { return allAttributes } - // Add line decorators. var newAttributes = allAttributes allAttributes.forEach { (attributes) in - guard attributes.representedElementCategory == .cell, + + guard shouldHaveLine(for: attributes.indexPath), + attributes.representedElementCategory == .cell, let lineAttributes = layoutAttributesForDecorationView(ofKind: LineDecorationView.elementKind, at: attributes.indexPath), rect.intersects(lineAttributes.frame) else { return } newAttributes.append(lineAttributes) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel/LineDecorationView.swift b/MVMCoreUI/Atomic/Organisms/Carousel/LineDecorationView.swift index 465f2219..5ecd77b1 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel/LineDecorationView.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel/LineDecorationView.swift @@ -6,12 +6,12 @@ // Copyright © 2020 Verizon Wireless. All rights reserved. // -public class LineDecorationView : UICollectionReusableView { +public class LineDecorationView: UICollectionReusableView { static public let elementKind = "line" public override init(frame: CGRect) { super.init(frame:frame) - backgroundColor = .black + backgroundColor = .mvmCoolGray3 } public required init?(coder aDecoder: NSCoder) {