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()