diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 9423188c..294f6188 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -202,6 +202,9 @@ BB6C6AC1242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */; }; BB6C6AC824225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */; }; BB6C6AC924225290005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BB6C6AC72422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift */; }; + BBAA4F03243D8E3B005AAD5F /* RadioBoxes.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA4EFF243D8E3B005AAD5F /* RadioBoxes.swift */; }; + BBAA4F04243D8E3B005AAD5F /* RadioBoxModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */; }; + BBAA4F05243D8E3B005AAD5F /* RadioBoxesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBAA4F01243D8E3B005AAD5F /* RadioBoxesModel.swift */; }; BBBBC87C24374A4900B0F079 /* ListThreeColumnBillChangesDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBC87A24374A4900B0F079 /* ListThreeColumnBillChangesDivider.swift */; }; BBBBC87D24374A4900B0F079 /* ListThreeColumnBillChangesDividerModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BBBBC87B24374A4900B0F079 /* ListThreeColumnBillChangesDividerModel.swift */; }; C003506123AA94CD00B6AC29 /* Button.swift in Sources */ = {isa = PBXBuildFile; fileRef = C003506023AA94CD00B6AC29 /* Button.swift */; }; @@ -264,6 +267,9 @@ D264FAA1243CF66B00D98315 /* ContainerCollectionReusableView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */; }; D264FAA3243E632F00D98315 /* ProgrammaticCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */; }; D264FAA5243F66A500D98315 /* CollectionTemplateItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */; }; + D264FAA7243FE13B00D98315 /* RadioBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA6243FE13B00D98315 /* RadioBox.swift */; }; + D264FAAA2440F97600D98315 /* CollectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAA92440F97600D98315 /* CollectionView.swift */; }; + D264FAAC2441009400D98315 /* RadioBoxCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D264FAAB2441009400D98315 /* RadioBoxCollectionViewCell.swift */; }; D268C70C2386DFFD007F2C1C /* MoleculeStackItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368A23609801006832FA /* MoleculeStackItemModel.swift */; }; D268C70E238C22D7007F2C1C /* DropDownFilterTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */; }; D26C5A6B23F4A40D007AEECE /* ListItemModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */; }; @@ -622,6 +628,9 @@ BB6C6ABF242232DF005F7224 /* ListOneColumnTextWithWhitespaceDividerTall.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerTall.swift; sourceTree = ""; }; BB6C6AC62422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShort.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerShort.swift; sourceTree = ""; }; BB6C6AC72422528F005F7224 /* ListOneColumnTextWithWhitespaceDividerShortModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListOneColumnTextWithWhitespaceDividerShortModel.swift; sourceTree = ""; }; + BBAA4EFF243D8E3B005AAD5F /* RadioBoxes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxes.swift; sourceTree = ""; }; + BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxModel.swift; sourceTree = ""; }; + BBAA4F01243D8E3B005AAD5F /* RadioBoxesModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxesModel.swift; sourceTree = ""; }; BBBBC87A24374A4900B0F079 /* ListThreeColumnBillChangesDivider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListThreeColumnBillChangesDivider.swift; sourceTree = ""; }; BBBBC87B24374A4900B0F079 /* ListThreeColumnBillChangesDividerModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListThreeColumnBillChangesDividerModel.swift; sourceTree = ""; }; C003506023AA94CD00B6AC29 /* Button.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Button.swift; sourceTree = ""; }; @@ -684,6 +693,9 @@ D264FAA0243CF66B00D98315 /* ContainerCollectionReusableView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerCollectionReusableView.swift; sourceTree = ""; }; D264FAA2243E632F00D98315 /* ProgrammaticCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgrammaticCollectionViewController.swift; sourceTree = ""; }; D264FAA4243F66A500D98315 /* CollectionTemplateItemProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionTemplateItemProtocol.swift; sourceTree = ""; }; + D264FAA6243FE13B00D98315 /* RadioBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBox.swift; sourceTree = ""; }; + D264FAA92440F97600D98315 /* CollectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionView.swift; sourceTree = ""; }; + D264FAAB2441009400D98315 /* RadioBoxCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioBoxCollectionViewCell.swift; sourceTree = ""; }; D268C70D238C22D7007F2C1C /* DropDownFilterTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DropDownFilterTableViewCell.swift; sourceTree = ""; }; D26C5A6A23F4A40D007AEECE /* ListItemModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = ""; }; D274CA322236A78900B01B62 /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = ""; }; @@ -1404,6 +1416,23 @@ path = Doughnut; sourceTree = ""; }; + D264FAA8243FE17A00D98315 /* Selectors */ = { + isa = PBXGroup; + children = ( + D264FAAB2441009400D98315 /* RadioBoxCollectionViewCell.swift */, + BBAA4F01243D8E3B005AAD5F /* RadioBoxesModel.swift */, + BBAA4EFF243D8E3B005AAD5F /* RadioBoxes.swift */, + BBAA4F00243D8E3B005AAD5F /* RadioBoxModel.swift */, + D264FAA6243FE13B00D98315 /* RadioBox.swift */, + 0116A4E4228B19640094F3ED /* RadioButtonSelectionHelper.swift */, + 011D95AE2407266E000E3791 /* RadioButtonModel.swift */, + 01004F2F22721C3800991ECC /* RadioButton.swift */, + 31BE15CA23D8924C00452370 /* CheckboxModel.swift */, + 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, + ); + path = Selectors; + sourceTree = ""; + }; D29DF0C221E404D4003B2FB9 = { isa = PBXGroup; children = ( @@ -1479,6 +1508,7 @@ D29DF10D21E67A70003B2FB9 /* Atoms */ = { isa = PBXGroup; children = ( + D264FAA8243FE17A00D98315 /* Selectors */, D29DF22B21E6A0FA003B2FB9 /* TextFields */, D29DF17D21E69E26003B2FB9 /* Views */, D29DF16821E69E1F003B2FB9 /* Buttons */, @@ -1621,9 +1651,6 @@ DBC4391A224421A0001AB423 /* CaretLink.swift */, D28A838A23CCDA6B00DFE4FC /* ButtonModel.swift */, D2E2A99E23E07F8A000B42E6 /* PillButton.swift */, - 0116A4E4228B19640094F3ED /* RadioButtonSelectionHelper.swift */, - 011D95AE2407266E000E3791 /* RadioButtonModel.swift */, - 01004F2F22721C3800991ECC /* RadioButton.swift */, ); path = Buttons; sourceTree = ""; @@ -1647,8 +1674,6 @@ 017BEB7A236763000024EF95 /* LineModel.swift */, D213347623843825008E41B3 /* Line.swift */, 94C2D9822386F3E30006CF46 /* Label */, - 31BE15CA23D8924C00452370 /* CheckboxModel.swift */, - 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, 31BE15C923D8924C00452370 /* CheckboxLabelModel.swift */, 0A7BAFA2232BE63400FB8E22 /* CheckboxLabel.swift */, D28A838223CCBD3F00DFE4FC /* CircleProgressModel.swift */, @@ -1823,6 +1848,7 @@ 0AE14F63238315D2005417F8 /* TextField.swift */, D2755D7A23689C7500485468 /* TableViewCell.swift */, D21B7F70243BAC1600051ABF /* CollectionViewCell.swift */, + D264FAA92440F97600D98315 /* CollectionView.swift */, 0A5D59C323AD488600EFD9E9 /* Protocols */, ); path = BaseClasses; @@ -2032,6 +2058,7 @@ AA56A20F243C5EE900303286 /* ListTwoColumnSubsectionDividerModel.swift in Sources */, 94C2D9A923872E5E0006CF46 /* LabelAttributeImageModel.swift in Sources */, DBC4391922442197001AB423 /* DashLine.swift in Sources */, + D264FAAA2440F97600D98315 /* CollectionView.swift in Sources */, 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */, D2FB151D23A40F1500C20E10 /* MoleculeStackItem.swift in Sources */, AA11A41F23F15D3100D7962F /* ListRightVariablePayments.swift in Sources */, @@ -2091,6 +2118,7 @@ D28A837B23C928DA00DFE4FC /* MoleculeListCellProtocol.swift in Sources */, 014AA72F23C5059B006F3E93 /* ThreeLayerPageTemplateModel.swift in Sources */, 0A21DB91235E0EDB00C160A2 /* DigitBox.swift in Sources */, + BBAA4F04243D8E3B005AAD5F /* RadioBoxModel.swift in Sources */, D22D1F572204CE5D0077CEC0 /* MVMCoreUIStackableViewController.m in Sources */, D28A838B23CCDA6B00DFE4FC /* ButtonModel.swift in Sources */, D21B7F71243BAC1600051ABF /* CollectionViewCell.swift in Sources */, @@ -2144,6 +2172,7 @@ 011D959B240451E3000E3791 /* RuleRequiredModel.swift in Sources */, 526A265C240D1FF700B0D828 /* ListTwoColumnCompareChangesModel.swift in Sources */, D2A92886241ACD99004E01C6 /* ProgrammaticTableViewController.swift in Sources */, + BBAA4F05243D8E3B005AAD5F /* RadioBoxesModel.swift in Sources */, 01509D952327ED1900EF99AA /* HeadlineBodyLinkToggle.swift in Sources */, 31BE15CB23D8924D00452370 /* CheckboxLabelModel.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, @@ -2217,6 +2246,7 @@ 526A265E240D200500B0D828 /* ListTwoColumnCompareChanges.swift in Sources */, 8D24041523E7FC0B009E23BE /* ListLeftVariableIconWithRightCaretModel.swift in Sources */, D28A838F23CCDEDE00DFE4FC /* TwoButtonViewModel.swift in Sources */, + D264FAAC2441009400D98315 /* RadioBoxCollectionViewCell.swift in Sources */, BB2C969224330F73006FF80C /* ListRightVariableTextLinkAllTextAndLinks.swift in Sources */, D2D90B42240463E100DD6EC9 /* MoleculeHeaderModel.swift in Sources */, 012A88B1238C880100FE3DA1 /* CarouselPagingModelProtocol.swift in Sources */, @@ -2240,6 +2270,7 @@ 8D084AD02410BF4800951227 /* ListOneColumnFullWidthTextBodyTextModel.swift in Sources */, 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */, 8D24041123E7FB9E009E23BE /* ListLeftVariableIconWithRightCaret.swift in Sources */, + BBAA4F03243D8E3B005AAD5F /* RadioBoxes.swift in Sources */, D2E1FAE12268E81D00AEFD8C /* MoleculeListTemplate.swift in Sources */, 525019E72406853600EED91C /* ListFourColumnDataUsageDivider.swift in Sources */, 0AE98BB323FF0934004C5109 /* ExternalLinkModel.swift in Sources */, @@ -2359,6 +2390,7 @@ D260106523D0CEA700764D80 /* StackModel.swift in Sources */, 0A6682AA2435125F00AD3CA1 /* Styler.swift in Sources */, D29770F521F7C6D600B2F0D0 /* TopLabelsAndBottomButtonsViewController.m in Sources */, + D264FAA7243FE13B00D98315 /* RadioBox.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/MVMCoreUI/Atomic/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Views/Checkbox.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/Checkbox.swift diff --git a/MVMCoreUI/Atomic/Atoms/Views/CheckboxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Views/CheckboxModel.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/CheckboxModel.swift diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift new file mode 100644 index 00000000..da762dbb --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBox.swift @@ -0,0 +1,183 @@ +// +// RadioBox.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/9/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class RadioBox: Control { + public let label = Label.createLabelRegularBodySmall(true) + public let subTextLabel = Label.createLabelRegularMicro(true) + public var isOutOfStock = false + public var accentColor = UIColor.mvmRed + + public let innerPadding: CGFloat = 12.0 + + private var borderLayer: CALayer? + private var strikeLayer: CALayer? + private var maskLayer: CALayer? + + public var subTextLabelHeightConstraint: NSLayoutConstraint? + + public var radioBoxModel: RadioBoxModel? { + return model as? RadioBoxModel + } + + // MARK: - MVMCoreViewProtocol + + open override func updateView(_ size: CGFloat) { + super.updateView(size) + label.updateView(size) + subTextLabel.updateView(size) + layer.setNeedsDisplay() + } + + open override func setupView() { + super.setupView() + + layer.delegate = self + layer.borderColor = UIColor.black.cgColor + layer.borderWidth = 1 + + label.numberOfLines = 1 + addSubview(label) + NSLayoutConstraint.constraintPinSubview(label, pinTop: true, topConstant: innerPadding, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding) + + subTextLabel.textColor = .mvmCoolGray6 + subTextLabel.numberOfLines = 1 + addSubview(subTextLabel) + NSLayoutConstraint.constraintPinSubview(subTextLabel, pinTop: false, topConstant:0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: innerPadding, pinRight: true, rightConstant: innerPadding) + bottomAnchor.constraint(greaterThanOrEqualTo: subTextLabel.bottomAnchor, constant: innerPadding).isActive = true + subTextLabel.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 2).isActive = true + subTextLabelHeightConstraint = subTextLabel.heightAnchor.constraint(equalToConstant: 0) + subTextLabelHeightConstraint?.isActive = true + + addTarget(self, action: #selector(selectBox), for: .touchUpInside) + } + + // MARK: - MoleculeViewProtocol + + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + guard let model = model as? RadioBoxModel else { return } + isSelected = model.selected + isEnabled = model.enabled + label.text = model.text + subTextLabel.text = model.subText + isOutOfStock = model.strikethrough + subTextLabelHeightConstraint?.isActive = (subTextLabel.text?.count ?? 0) == 0 + } + + // MARK: - State Handling + + open override func draw(_ layer: CALayer, in ctx: CGContext) { + // Draw the strikethrough + strikeLayer?.removeFromSuperlayer() + if isOutOfStock { + let line = getStrikeThrough(color: .black, thickness: 1) + layer.addSublayer(line) + strikeLayer = line + } + + // Draw the border + borderLayer?.removeFromSuperlayer() + if isSelected { + layer.borderWidth = 0 + let border = getSelectedBorder() + layer.addSublayer(border) + borderLayer = border + } else { + layer.borderWidth = 1 + } + + // Handle Mask + maskLayer?.removeFromSuperlayer() + if !isEnabled { + let mask = getMaskLayer() + layer.mask = mask + } + } + + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any size changes + layer.setNeedsDisplay() + } + + @objc open func selectBox() { + isSelected = true + radioBoxModel?.selected = isSelected + layer.setNeedsDisplay() + } + + @objc open func deselectBox() { + isSelected = false + radioBoxModel?.selected = isSelected + layer.setNeedsDisplay() + } + + /// Gets the selected state border + func getSelectedBorder() -> CAShapeLayer { + let layer = CAShapeLayer() + + let topLineWidth: CGFloat = 4 + let topLinePath = UIBezierPath() + topLinePath.lineWidth = topLineWidth + topLinePath.move(to: CGPoint(x: 0, y: topLineWidth / 2.0)) + topLinePath.addLine(to: CGPoint(x: bounds.width, y: topLineWidth / 2.0)) + + let topLineLayer = CAShapeLayer() + topLineLayer.fillColor = nil + topLineLayer.strokeColor = UIColor.mvmRed.cgColor + topLineLayer.lineWidth = 4 + topLineLayer.path = topLinePath.cgPath + layer.addSublayer(topLineLayer) + + let lineWidth: CGFloat = 1 + let halfLineWidth: CGFloat = 0.5 + let linePath = UIBezierPath() + linePath.move(to: CGPoint(x: halfLineWidth, y: topLineWidth)) + linePath.addLine(to: CGPoint(x: halfLineWidth, y: bounds.height)) + linePath.move(to: CGPoint(x: 0, y: bounds.height - halfLineWidth)) + linePath.addLine(to: CGPoint(x: bounds.width, y: bounds.height - halfLineWidth)) + linePath.move(to: CGPoint(x: bounds.width - halfLineWidth, y: bounds.height)) + linePath.addLine(to: CGPoint(x: bounds.width - halfLineWidth, y: topLineWidth)) + + let borderLayer = CAShapeLayer() + borderLayer.fillColor = nil + borderLayer.strokeColor = UIColor.black.cgColor + borderLayer.lineWidth = lineWidth + borderLayer.path = linePath.cgPath + layer.addSublayer(borderLayer) + + return layer + } + + /// Adds a border to edge + func getStrikeThrough(color: UIColor, thickness: CGFloat) -> CAShapeLayer { + let border = CAShapeLayer() + border.name = "strikethrough" + border.fillColor = nil + border.opacity = 1.0 + border.lineWidth = thickness + border.strokeColor = color.cgColor + + let linePath = UIBezierPath() + linePath.move(to: CGPoint(x: 0, y: bounds.height)) + linePath.addLine(to: CGPoint(x: bounds.width, y: 0)) + border.path = linePath.cgPath + return border + } + + func getMaskLayer() -> CALayer { + let mask = CALayer() + mask.backgroundColor = UIColor.white.cgColor + mask.opacity = 0.3 + mask.frame = bounds + return mask + } +} + diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxCollectionViewCell.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxCollectionViewCell.swift new file mode 100644 index 00000000..c90b5e92 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxCollectionViewCell.swift @@ -0,0 +1,23 @@ +// +// RadioBoxCollectionViewCell.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 01/04/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +open class RadioBoxCollectionViewCell: CollectionViewCell { + let radioBox = RadioBox() + + open override func setupView() { + super.setupView() + addMolecule(radioBox) + 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? RadioBoxModel else { return } + radioBox.set(with: model, delegateObject, additionalData) + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift new file mode 100644 index 00000000..b2a23c6a --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxModel.swift @@ -0,0 +1,68 @@ +// +// RadioBoxModel.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 31/03/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers public class RadioBoxModel: MoleculeModelProtocol { + public static var identifier: String = "radioBox" + public var text: String + public var subText: String? + public var backgroundColor: Color? = Color(uiColor: .white) + public var selectedAccentColor = Color(uiColor: .mvmRed) + public var selected: Bool = false + public var enabled: Bool = true + public var strikethrough: Bool = false + public var fieldValue: String? + + private enum CodingKeys: String, CodingKey { + case moleculeName + case text + case subText + case selectedAccentColor + case backgroundColor + case selected + case enabled + case strikethrough + case fieldValue + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + text = try typeContainer.decode(String.self, forKey: .text) + subText = try typeContainer.decodeIfPresent(String.self, forKey: .subText) + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) { + selectedAccentColor = color + } + if let color = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) { + backgroundColor = color + } + if let isSelected = try typeContainer.decodeIfPresent(Bool.self, forKey: .selected) { + selected = isSelected + } + if let isEnabled = try typeContainer.decodeIfPresent(Bool.self, forKey: .enabled) { + enabled = isEnabled + } + if let isStrikeTrough = try typeContainer.decodeIfPresent(Bool.self, forKey: .strikethrough) { + strikethrough = isStrikeTrough + } + + fieldValue = try typeContainer.decodeIfPresent(String.self, forKey: .fieldValue) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(text, forKey: .text) + try container.encodeIfPresent(subText, forKey: .subText) + try container.encode(selectedAccentColor, forKey: .selectedAccentColor) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encode(selected, forKey: .selected) + try container.encode(enabled, forKey: .enabled) + try container.encode(strikethrough, forKey: .strikethrough) + try container.encodeIfPresent(fieldValue, forKey: .fieldValue) + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift new file mode 100644 index 00000000..6fe66bc2 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxes.swift @@ -0,0 +1,143 @@ +// +// RadioBoxes.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 31/03/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class RadioBoxes: View { + + public var collectionView: CollectionView! + public var collectionViewHeight: NSLayoutConstraint! + private let boxWidth: CGFloat = 151.0 + private let boxHeight: CGFloat = 64.0 + private let itemSpacing: CGFloat = 8.0 + + private var delegateObject: MVMCoreUIDelegateObject? + + /// The models for the molecules. + public var boxes: [RadioBoxModel]? + + private var size: CGFloat? + + open override func layoutSubviews() { + super.layoutSubviews() + // Accounts for any collection size changes + DispatchQueue.main.async { + self.collectionView.collectionViewLayout.invalidateLayout() + } + } + + // MARK: - MVMCoreViewProtocol + open override func setupView() { + super.setupView() + collectionView = createCollectionView() + addSubview(collectionView) + NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView) + collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: 300) + collectionViewHeight?.isActive = true + } + + // MARK: - MoleculeViewProtocol + public override func set(with model: MoleculeModelProtocol, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + super.set(with: model, delegateObject, additionalData) + self.delegateObject = delegateObject + + guard let radioBoxesModel = model as? RadioBoxesModel else { return } + boxes = radioBoxesModel.boxes + FormValidator.setupValidation(for: radioBoxesModel, delegate: delegateObject?.formHolderDelegate) + + backgroundColor = radioBoxesModel.backgroundColor?.uiColor + registerCells() + setHeight() + collectionView.reloadData() + } + + @objc override open func updateView(_ size: CGFloat) { + super.updateView(size) + self.size = size + collectionView.updateView(size) + } + + // MARK: - Creation + + /// Creates the layout for the collection. + open func createCollectionViewLayout() -> UICollectionViewLayout { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumLineSpacing = itemSpacing + layout.minimumInteritemSpacing = itemSpacing + return layout + } + + /// Creates the collection view. + open func createCollectionView() -> CollectionView { + let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) + collection.dataSource = self + collection.delegate = self + return collection + } + + /// Registers the cells with the collection view + open func registerCells() { + collectionView.register(RadioBoxCollectionViewCell.self, forCellWithReuseIdentifier: "RadioBoxCollectionViewCell") + } + + // MARK: - JSON Setters + open func setHeight() { + guard let boxes = boxes, boxes.count > 0 else { + collectionViewHeight.constant = 0 + return + } + + // Calculate the height + let rows = ceil(CGFloat(boxes.count) / 2.0) + let height = (rows * boxHeight) + ((rows - 1) * itemSpacing) + collectionViewHeight?.constant = height + } +} + +extension RadioBoxes: UICollectionViewDelegateFlowLayout { + open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let itemWidth: CGFloat = (collectionView.bounds.width - itemSpacing) / 2 + return CGSize(width: itemWidth, height: boxHeight) + } +} + +extension RadioBoxes: UICollectionViewDataSource { + open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return boxes?.count ?? 0 + } + + open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let molecule = boxes?[indexPath.row], + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "RadioBoxCollectionViewCell", for: indexPath) as? RadioBoxCollectionViewCell else { + fatalError() + } + cell.radioBox.isUserInteractionEnabled = false + cell.set(with: molecule, delegateObject, nil) + cell.updateView(size ?? collectionView.bounds.width) + if molecule.selected { + collectionView.selectItem(at: indexPath, animated: false, scrollPosition: .centeredVertically) + } + cell.layoutIfNeeded() + return cell + } +} + +extension RadioBoxes: UICollectionViewDelegate { + public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { + guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return } + cell.radioBox.selectBox() + _ = FormValidator.validate(delegate: delegateObject?.formHolderDelegate) + } + + public func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { + guard let cell = collectionView.cellForItem(at: indexPath) as? RadioBoxCollectionViewCell else { return } + cell.radioBox.deselectBox() + } +} + diff --git a/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift new file mode 100644 index 00000000..28c4fab5 --- /dev/null +++ b/MVMCoreUI/Atomic/Atoms/Selectors/RadioBoxesModel.swift @@ -0,0 +1,57 @@ +// +// RadioBoxesModel.swift +// MVMCoreUI +// +// Created by Dhamodaram Nandi on 31/03/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation +@objcMembers public class RadioBoxesModel: MoleculeModelProtocol, FormFieldProtocol { + public static var identifier: String = "radioBoxes" + public var backgroundColor: Color? + public var selectedAccentColor: Color? + public var boxes: [RadioBoxModel] + public var fieldKey: String? + public var groupName: String = FormValidator.defaultGroupName + public var baseValue: AnyHashable? + + /// Returns the fieldValue of the selected box, otherwise the text of the selected box. + public func formFieldValue() -> AnyHashable? { + let selectedBox = boxes.first { (box) -> Bool in + return box.selected + } + return selectedBox?.fieldValue ?? selectedBox?.text + } + + private enum CodingKeys: String, CodingKey { + case moleculeName + case selectedAccentColor + case backgroundColor + case boxes + case fieldKey + case groupName + } + + required public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + selectedAccentColor = try typeContainer.decodeIfPresent(Color.self, forKey: .selectedAccentColor) + backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + boxes = try typeContainer.decode([RadioBoxModel].self, forKey: .boxes) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) + if let groupName = try typeContainer.decodeIfPresent(String.self, forKey: .groupName) { + self.groupName = groupName + } + baseValue = formFieldValue() + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encode(boxes, forKey: .boxes) + try container.encodeIfPresent(selectedAccentColor, forKey: .selectedAccentColor) + try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) + try container.encode(groupName, forKey: .groupName) + } +} diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Buttons/RadioButton.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioButton.swift diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonModel.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonModel.swift diff --git a/MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift b/MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift similarity index 100% rename from MVMCoreUI/Atomic/Atoms/Buttons/RadioButtonSelectionHelper.swift rename to MVMCoreUI/Atomic/Atoms/Selectors/RadioButtonSelectionHelper.swift diff --git a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift index c8286f33..ab0d5b43 100644 --- a/MVMCoreUI/Atomic/MoleculeObjectMapping.swift +++ b/MVMCoreUI/Atomic/MoleculeObjectMapping.swift @@ -47,7 +47,7 @@ import Foundation // need to move labelattributemodel to different method try? ModelRegistry.register(LabelAttributeFontModel.self) try? ModelRegistry.register(LabelAttributeColorModel.self) - try? ModelRegistry.register(LabelAttributeImageModel.self) // We need to separate the registry by types due to collisions... + try? ModelRegistry.register(LabelAttributeImageModel.self) try? ModelRegistry.register(LabelAttributeUnderlineModel.self) try? ModelRegistry.register(LabelAttributeStrikeThroughModel.self) try? ModelRegistry.register(LabelAttributeActionModel.self) @@ -66,6 +66,11 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: ItemDropdownEntryField.self, viewModelClass: ItemDropdownEntryFieldModel.self) MoleculeObjectMapping.shared()?.register(viewClass: DateDropdownEntryField.self, viewModelClass: DateDropdownEntryFieldModel.self) + // Selectors + MoleculeObjectMapping.shared()?.register(viewClass: RadioButton.self, viewModelClass: RadioButtonModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: RadioBoxes.self, viewModelClass: RadioBoxesModel.self) + MoleculeObjectMapping.shared()?.register(viewClass: Checkbox.self, viewModelClass: CheckboxModel.self) + // Other Atoms MoleculeObjectMapping.shared()?.register(viewClass: ProgressBar.self, viewModelClass: ProgressBarModel.self) MoleculeObjectMapping.shared()?.register(viewClass: MultiProgress.self, viewModelClass: MultiProgressBarModel.self) @@ -75,10 +80,8 @@ import Foundation MoleculeObjectMapping.shared()?.register(viewClass: Line.self, viewModelClass: LineModel.self) MoleculeObjectMapping.shared()?.register(viewClass: GraphView.self, viewModelClass: CircleProgressModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Toggle.self, viewModelClass: ToggleModel.self) - MoleculeObjectMapping.shared()?.register(viewClass: Checkbox.self, viewModelClass: CheckboxModel.self) MoleculeObjectMapping.shared()?.register(viewClass: CheckboxLabel.self, viewModelClass: CheckboxLabelModel.self) MoleculeObjectMapping.shared()?.register(viewClass: Arrow.self, viewModelClass: ArrowModel.self) - MoleculeObjectMapping.shared()?.register(viewClass: RadioButton.self, viewModelClass: RadioButtonModel.self) MoleculeObjectMapping.shared()?.register(viewClass: RadioButtonLabel.self, viewModelClass: RadioButtonLabelModel.self) MoleculeObjectMapping.shared()?.register(viewClass: WebView.self, viewModelClass: WebViewModel.self) diff --git a/MVMCoreUI/Atomic/Organisms/Carousel.swift b/MVMCoreUI/Atomic/Organisms/Carousel.swift index 1ed2ecd2..820c5a5d 100644 --- a/MVMCoreUI/Atomic/Organisms/Carousel.swift +++ b/MVMCoreUI/Atomic/Organisms/Carousel.swift @@ -10,7 +10,7 @@ import UIKit open class Carousel: View { - public let collectionView = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) + public let collectionView = CollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout()) /// The current index of the collection view. Includes dummy cells when looping. public var currentIndex = 0 @@ -83,12 +83,8 @@ open class Carousel: View { // MARK: - MVMCoreViewProtocol open override func setupView() { super.setupView() - collectionView.translatesAutoresizingMaskIntoConstraints = false collectionView.dataSource = self collectionView.delegate = self - collectionView.showsHorizontalScrollIndicator = false - collectionView.backgroundColor = .clear - collectionView.isAccessibilityElement = false addSubview(collectionView) bottomPin = NSLayoutConstraint.constraintPinSubview(toSuperview: collectionView)?[ConstraintBot] as? NSLayoutConstraint diff --git a/MVMCoreUI/BaseClasses/CollectionView.swift b/MVMCoreUI/BaseClasses/CollectionView.swift new file mode 100644 index 00000000..a1a57376 --- /dev/null +++ b/MVMCoreUI/BaseClasses/CollectionView.swift @@ -0,0 +1,47 @@ +// +// CollectionView.swift +// MVMCoreUI +// +// Created by Scott Pfeil on 4/10/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +open class CollectionView: UICollectionView, MVMCoreViewProtocol { + + private var initialSetupPerformed = false + + private func initialSetup() { + if !initialSetupPerformed { + initialSetupPerformed = true + setupView() + } + } + + public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { + super.init(frame: frame, collectionViewLayout: layout) + initialSetup() + } + + required public init?(coder: NSCoder) { + super.init(coder: coder) + initialSetup() + } + + public func updateView(_ size: CGFloat) { + for cell in visibleCells { + (cell as? MVMCoreViewProtocol)?.updateView(size) + } + collectionViewLayout.invalidateLayout() + } + + public func setupView() { + translatesAutoresizingMaskIntoConstraints = false + showsHorizontalScrollIndicator = false + showsVerticalScrollIndicator = false + backgroundColor = .clear + isAccessibilityElement = false + contentInsetAdjustmentBehavior = .always + } +} diff --git a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift index 9d279d79..5e7b87b0 100644 --- a/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift +++ b/MVMCoreUI/BaseControllers/ProgrammaticCollectionViewController.swift @@ -46,14 +46,9 @@ import Foundation /// Creates the collection view. open func createCollectionView() -> UICollectionView { - let collection = UICollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) - collection.translatesAutoresizingMaskIntoConstraints = false + let collection = CollectionView(frame: .zero, collectionViewLayout: createCollectionViewLayout()) collection.dataSource = self collection.delegate = self - collection.showsHorizontalScrollIndicator = false - collection.backgroundColor = .white - collection.isAccessibilityElement = false - collection.contentInsetAdjustmentBehavior = .always return collection }