diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 32d9b7ba..85d4b2f4 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0198F79E225679870066C936 /* FormValidationProtocol.swift */; }; 0198F7A62256A80B0066C936 /* MFRadioButton.h in Headers */ = {isa = PBXBuildFile; fileRef = 0198F7A02256A80A0066C936 /* MFRadioButton.h */; settings = {ATTRIBUTES = (Public, ); }; }; 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */ = {isa = PBXBuildFile; fileRef = 0198F7A22256A80A0066C936 /* MFRadioButton.m */; }; + 01C851D323CF9E740021F976 /* LabelToggleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01C851D223CF9E740021F976 /* LabelToggleModel.swift */; }; 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 01EB3684236097C0006832FA /* MoleculeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB3683236097C0006832FA /* MoleculeProtocol.swift */; }; 01EB368F23609801006832FA /* LabelModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EB368823609801006832FA /* LabelModel.swift */; }; @@ -81,6 +82,7 @@ 0A6BF4722360C56C0028F841 /* BaseDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A6BF4712360C56C0028F841 /* BaseDropdownEntryField.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; + 0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B392398524F0067DD0F /* Toggle.swift */; }; 0ABD136B237B193A0081388D /* EntryFieldContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */; }; 0ABD136D237CAD1E0081388D /* DateDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD136C237CAD1E0081388D /* DateDropdownEntryField.swift */; }; 0ABD1371237DB0450081388D /* ItemDropdownEntryField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ABD1370237DB0450081388D /* ItemDropdownEntryField.swift */; }; @@ -335,6 +337,7 @@ 0198F79E225679870066C936 /* FormValidationProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FormValidationProtocol.swift; sourceTree = ""; }; 0198F7A02256A80A0066C936 /* MFRadioButton.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFRadioButton.h; sourceTree = ""; }; 0198F7A22256A80A0066C936 /* MFRadioButton.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFRadioButton.m; sourceTree = ""; }; + 01C851D223CF9E740021F976 /* LabelToggleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelToggleModel.swift; sourceTree = ""; }; 01EB3683236097C0006832FA /* MoleculeProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MoleculeProtocol.swift; sourceTree = ""; }; 01EB368823609801006832FA /* LabelModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LabelModel.swift; sourceTree = ""; }; 01EB368923609801006832FA /* ListItemModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListItemModel.swift; sourceTree = ""; }; @@ -355,6 +358,7 @@ 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; + 0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = ""; }; 0A8321AE2355FE9500CB7F00 /* DigitBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigitBox.swift; sourceTree = ""; }; 0AA33B33239813C50067DD0F /* UIColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIColor+Extension.swift"; sourceTree = ""; }; 0ABD136A237B193A0081388D /* EntryFieldContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntryFieldContainer.swift; sourceTree = ""; }; @@ -764,6 +768,7 @@ children = ( 01509D942327ED1900EF99AA /* HeadlineBodyTextButtonSwitch.swift */, D22479892314445E003FCCF9 /* LabelSwitch.swift */, + 01C851D223CF9E740021F976 /* LabelToggleModel.swift */, D224798B231450C8003FCCF9 /* HeadlineBodySwitch.swift */, ); path = SwitchMolecules; @@ -1116,6 +1121,7 @@ D28A838223CCBD3F00DFE4FC /* CircleProgressModel.swift */, 943784F3236B77BB006A1E82 /* GraphView.swift */, 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */, + 0AA33B392398524F0067DD0F /* Toggle.swift */, ); path = Views; sourceTree = ""; @@ -1438,6 +1444,7 @@ D282AAB4223FDDAE00C46919 /* MFLoadImageView.swift in Sources */, D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */, D2B18B7F2360913400A9AEDC /* Control.swift in Sources */, + 0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, 012A88C8238DB02000FE3DA1 /* ModelMoleculeDelegateProtocol.swift in Sources */, DBC4392122491730001AB423 /* LabelWithInternalButton.swift in Sources */, @@ -1446,6 +1453,7 @@ 9445890C2385BCE300DE9FD4 /* ProgressBarModel.swift in Sources */, D29DF17C21E69E1F003B2FB9 /* MFTextButton.m in Sources */, 9445891F2385D2E900DE9FD4 /* CaretViewModel.swift in Sources */, + 01C851D323CF9E740021F976 /* LabelToggleModel.swift in Sources */, D29DF2C521E7BF57003B2FB9 /* MFTabBarSwipeAnimator.m in Sources */, 012A88AD238C418100FE3DA1 /* TemplateProtocol.swift in Sources */, D29DF2B421E7B76D003B2FB9 /* MFLoadingSpinner.m in Sources */, diff --git a/MVMCoreUI/Atoms/Views/GraphView.swift b/MVMCoreUI/Atoms/Views/GraphView.swift index cda77660..1f6ea45b 100644 --- a/MVMCoreUI/Atoms/Views/GraphView.swift +++ b/MVMCoreUI/Atoms/Views/GraphView.swift @@ -112,7 +112,9 @@ import UIKit //if number of colors is even, need to display gradient layer, otherwise make top layer as solid color layer if graphObject.colors.count % 2 == 0 { leftColors.removeLast() - topLayer.colors = [leftColors.last!, rightColors.first!] + let firstColor = leftColors.last!.uiColor.cgColor + let secondColor = rightColors.first!.uiColor.cgColor + topLayer.colors = [firstColor, secondColor] } else { topLayer.backgroundColor = leftColors.last?.uiColor.cgColor } @@ -125,7 +127,9 @@ import UIKit //count of graidentLayer.colors must be bigger than 1, otherwise set backgroundColor if leftColors.count > 1 { - leftLayer.colors = Array(leftColors) + leftLayer.colors = leftColors.map({ (color) -> CGColor in + return color.uiColor.cgColor + }) } else { leftLayer.backgroundColor = leftColors.first?.uiColor.cgColor } @@ -136,7 +140,9 @@ import UIKit rightLayer.startPoint = CGPoint(x: 0, y: 0) rightLayer.endPoint = CGPoint(x: 0, y: 1) if rightColors.count > 1 { - rightLayer.colors = Array(rightColors) + rightLayer.colors = rightColors.map({ (color) -> CGColor in + return color.uiColor.cgColor + }) } else { rightLayer.backgroundColor = rightColors.first?.uiColor.cgColor } diff --git a/MVMCoreUI/Atoms/Views/LeftRightLabelModel.swift b/MVMCoreUI/Atoms/Views/LeftRightLabelModel.swift index 5e091446..be703bef 100644 --- a/MVMCoreUI/Atoms/Views/LeftRightLabelModel.swift +++ b/MVMCoreUI/Atoms/Views/LeftRightLabelModel.swift @@ -9,7 +9,7 @@ import UIKit @objcMembers public class LeftRightLabelModel: MoleculeProtocol { - public static var identifier: String = "leftRightLabel" + public static var identifier: String = "leftRightLabelView" public var backgroundColor: Color? public var leftText: LabelModel public var rightText: LabelModel diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch+Model.swift b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch+Model.swift index 68482f09..e62a8b94 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch+Model.swift +++ b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch+Model.swift @@ -17,7 +17,7 @@ extension MVMCoreUISwitch: ModelMoleculeViewProtocol { FormValidator.setupValidation(molecule: castSelf, delegate: delegateObject?.formValidationProtocol) } - setState(model.on, animated: false) + setState(model.state, animated: false) guard let action = model.action else { return } actionBlock = { diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m index 4b66d519..1c40bc5a 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m +++ b/MVMCoreUI/Atoms/Views/MVMCoreUISwitch.m @@ -423,16 +423,16 @@ const CGFloat SwitchShakeIntensity = 2; return UIAccessibilityTraitButton; } -- (NSString * _Nullable)formFieldGroupName { - return [self.json string:@"groupName"]; -} - - (NSString *)accessibilityHint { return [MVMCoreUIUtility hardcodedStringWithKey:@"AccToggleHint"]; } #pragma mark FormValidationProtocol +- (NSString * _Nullable)formFieldGroupName { + return [self.json string:@"groupName"]; +} + - (BOOL)isValidField { return self.isOn && [self.json boolForKey:@"required"]; } diff --git a/MVMCoreUI/Atoms/Views/Toggle.swift b/MVMCoreUI/Atoms/Views/Toggle.swift new file mode 100644 index 00000000..4e855532 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -0,0 +1,425 @@ +// +// Toggle.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 12/4/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import MVMCore +import UIKit + +public typealias ActionBlockConfirmation = () -> (Bool) + +/** + A custom implementation of Apple's UISwitch. + + By default this class begins in the off state. + + Container: The background of the toggle control. + Knob: The circular indicator that slides on the container. + */ +@objcMembers open class Toggle: Control, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + /// Holds the on and off colors for the container. + public var containerTintColor: (on: UIColor?, off: UIColor?)? = (on: .mfShamrock(), off: .black) + + /// Holds the on and off colors for the knob. + public var knobTintColor: (on: UIColor?, off: UIColor?)? = (on: .white, off: .white) + + /// Holds the on and off colors for the disabled state.. + public var disabledTintColor: (container: UIColor?, knob: UIColor?)? = (container: .mfSilver(), knob: .white) + + /// Set this flag to false if you do not want to animate state changes. + public var isAnimated = true + + public var didToggleAction: ActionBlock? + + /// Executes logic before state change. If false, then toggle state will not change and the didToggleAction will not execute. + public var shouldToggleAction: ActionBlockConfirmation? = { + return { return true } + }() + + // Sizes are from InVision design specs. + static let containerSize = CGSize(width: 46, height: 24) + static let knobSize = CGSize(width: 22, height: 22) + + private var knobView: View = { + let view = View() + view.backgroundColor = .white + view.layer.cornerRadius = Toggle.getKnobHeight() / 2.0 + return view + }() + + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + + open override var isEnabled: Bool { + didSet { + isUserInteractionEnabled = isEnabled + changeStateNoAnimation(isEnabled ? isOn : false) + backgroundColor = isEnabled ? containerTintColor?.off : disabledTintColor?.container + knobView.backgroundColor = isEnabled ? knobTintColor?.off : disabledTintColor?.knob + } + } + + /// Simple means to prevent user interaction with the toggle. + public var isLocked: Bool = false { + didSet { + isUserInteractionEnabled = !isLocked + } + } + + /// The state on the toggle. Default value: false. + open var isOn: Bool = false { + didSet { + if isAnimated { + UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: { + if self.isOn { + self.knobView.backgroundColor = self.knobTintColor?.on + self.backgroundColor = self.containerTintColor?.on + + } else { + self.knobView.backgroundColor = self.knobTintColor?.off + self.backgroundColor = self.containerTintColor?.off + } + }, completion: nil) + + UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.2, options: [], animations: { + self.constrainKnob() + self.knobWidthConstraint?.constant = Self.getKnobWidth() + self.layoutIfNeeded() + }, completion: nil) + + } else { + backgroundColor = isOn ? containerTintColor?.on : containerTintColor?.off + knobView.backgroundColor = isOn ? knobTintColor?.on : knobTintColor?.off + self.constrainKnob() + } + + FormValidator.enableByValidationWith(delegate: delegateObject?.formValidationProtocol) + accessibilityValue = isOn ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") + setNeedsLayout() + layoutIfNeeded() + } + } + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + + private var delegateObject: MVMCoreUIDelegateObject? + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + private var knobLeadingConstraint: NSLayoutConstraint? + private var knobTrailingConstraint: NSLayoutConstraint? + private var knobHeightConstraint: NSLayoutConstraint? + private var knobWidthConstraint: NSLayoutConstraint? + private var heightConstraint: NSLayoutConstraint? + private var widthConstraint: NSLayoutConstraint? + + private func constrainKnob() { + knobLeadingConstraint?.isActive = !isOn + knobTrailingConstraint?.isActive = isOn + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + public override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + public convenience override init() { + self.init(frame: .zero) + } + + public convenience init(isOn: Bool) { + self.init(frame: .zero) + self.isOn = isOn + } + + /// - parameter isOn: Bool to set the state of the toggle. + /// - parameter didToggleAction: A closure which is executed after the toggle changes states. + public convenience init(isOn: Bool = false, didToggleAction: ActionBlock?) { + self.init(frame: .zero) + changeStateNoAnimation(isOn) + self.didToggleAction = didToggleAction + } + + /// - parameter shouldToggleAction: Takes a closure that returns a boolean. + /// - parameter didToggleAction: A closure which is executed after the toggle changes states. + public convenience init(shouldToggleAction: ActionBlockConfirmation?, didToggleAction: ActionBlock?) { + self.init(frame: .zero) + self.didToggleAction = didToggleAction + self.shouldToggleAction = shouldToggleAction + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("Toggle does not support xib.") + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + + heightConstraint?.constant = Self.getContainerHeight() + widthConstraint?.constant = Self.getContainerWidth() + + knobHeightConstraint?.constant = Self.getKnobHeight() + knobWidthConstraint?.constant = Self.getKnobWidth() + + layer.cornerRadius = Self.getContainerHeight() / 2.0 + knobView.layer.cornerRadius = Self.getKnobHeight() / 2.0 + } + + public override func setupView() { + super.setupView() + + guard subviews.isEmpty else { return } + + heightConstraint = heightAnchor.constraint(equalToConstant: Self.containerSize.height) + heightConstraint?.isActive = true + + widthConstraint = widthAnchor.constraint(equalToConstant: Self.containerSize.width) + widthConstraint?.isActive = true + + layer.cornerRadius = Self.containerSize.height / 2.0 + backgroundColor = containerTintColor?.off + + addSubview(knobView) + + knobHeightConstraint = knobView.heightAnchor.constraint(equalToConstant: Self.knobSize.height) + knobHeightConstraint?.isActive = true + knobWidthConstraint = knobView.widthAnchor.constraint(equalToConstant: Self.knobSize.width) + knobWidthConstraint?.isActive = true + knobView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + knobView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: knobView.bottomAnchor).isActive = true + + knobTrailingConstraint = trailingAnchor.constraint(equalTo: knobView.trailingAnchor, constant: 1) + knobLeadingConstraint = knobView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 1) + knobLeadingConstraint?.isActive = true + + accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel") + } + + public override func reset() { + super.reset() + + backgroundColor = containerTintColor?.off + knobView.backgroundColor = knobTintColor?.off + isAnimated = false + isOn = false + constrainKnob() + didToggleAction = nil + shouldToggleAction = { return true } + } + + class func getContainerWidth() -> CGFloat { + let containerWidth = Self.containerSize.width + return (MFSizeObject(standardSize: containerWidth, standardiPadPortraitSize: CGFloat(Self.containerSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerWidth + } + + class func getContainerHeight() -> CGFloat { + let containerHeight = Self.containerSize.height + return (MFSizeObject(standardSize: containerHeight, standardiPadPortraitSize: CGFloat(Self.containerSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerHeight + } + + class func getKnobWidth() -> CGFloat { + let knobWidth = Self.knobSize.width + return (MFSizeObject(standardSize: knobWidth, standardiPadPortraitSize: CGFloat(Self.knobSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobWidth + } + + class func getKnobHeight() -> CGFloat { + let knobHeight = Self.knobSize.width + return (MFSizeObject(standardSize: knobHeight, standardiPadPortraitSize: CGFloat(Self.knobSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobHeight + } + + //-------------------------------------------------- + // MARK: - Actions + //-------------------------------------------------- + + open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { + super.sendAction(action, to: target, for: event) + toggleAndAction() + } + + open override func sendActions(for controlEvents: UIControl.Event) { + super.sendActions(for: controlEvents) + toggleAndAction() + } + + /// This will toggle the state of the Toggle and execute the actionBlock if provided. + public func toggleAndAction() { + + if let result = shouldToggleAction?(), result { + isOn.toggle() + didToggleAction?() + } + } + + private func changeStateNoAnimation(_ state: Bool) { + + // Hold state in case User wanted isAnimated to remain off. + let isAnimatedState = isAnimated + + isAnimated = false + isOn = state + isAnimated = isAnimatedState + } + + //-------------------------------------------------- + // MARK: - UIResponder + //-------------------------------------------------- + + open override func touchesBegan(_ touches: Set, with event: UIEvent?) { + + UIView.animate(withDuration: 0.1, animations: { + self.knobWidthConstraint?.constant += PaddingOne + self.layoutIfNeeded() + }) + } + + public override func touchesEnded(_ touches: Set, with event: UIEvent?) { + + knobReformAnimation() + + // Action only occurs of the user lifts up from withing acceptable region of the toggle. + guard let coordinates = touches.first?.location(in: self), + coordinates.x > -20, + coordinates.x < bounds.width + 20, + coordinates.y > -20, + coordinates.y < bounds.height + 20 + else { return } + + sendActions(for: .touchUpInside) + } + + public func touchesCancelled(_ touches: Set, with event: UIEvent) { + + knobReformAnimation() + sendActions(for: .touchCancel) + } + + //-------------------------------------------------- + // MARK: - Animations + //-------------------------------------------------- + + public func knobReformAnimation() { + + if isAnimated { + UIView.animate(withDuration: 0.1, animations: { + self.knobWidthConstraint?.constant = Self.getKnobWidth() + self.layoutIfNeeded() + }, completion: nil) + + } else { + knobWidthConstraint?.constant = Self.getKnobWidth() + layoutIfNeeded() + } + } + + public override func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let toggleModel = model as? ToggleModel else { + return + } + + let toggleModelJSON = toggleModel.toJSON() + setWithJSON(toggleModelJSON, delegateObject: delegateObject, additionalData: additionalData) + } +} + +// MARK: - Accessibility +extension Toggle { + + public func formFieldGroupName() -> String? { + return json?["groupName"] as? String + } +} + +// MARK: - FormValidationProtocol +extension Toggle { + + public func isValidField() -> Bool { + return isOn && json?["required"] as? Bool ?? false + } + + public func formFieldName() -> String? { + return json?[KeyFieldKey] as? String ?? "" + } + + public func formFieldValue() -> Any? { + return NSNumber(value: isOn) + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +extension Toggle { + + public override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject + + FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) + + guard let dictionary = json else { return } + + if let color = dictionary["onTintColor"] as? String { + containerTintColor?.on = UIColor.mfGet(forHex: color) + } + + if let color = dictionary["offTintColor"] as? String { + containerTintColor?.off = UIColor.mfGet(forHex: color) + } + + if let color = dictionary["onKnobTintColor"] as? String { + knobTintColor?.on = UIColor.mfGet(forHex: color) + } + + if let color = dictionary["offKnobTintColor"] as? String { + knobTintColor?.off = UIColor.mfGet(forHex: color) + } + + if let state = dictionary["state"] as? Bool { + changeStateNoAnimation(state) + } + + if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { + didToggleAction = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } + } + + if let isAnimated = dictionary["isAnimated"] as? Bool { + self.isAnimated = isAnimated + } + + if let isEnabled = dictionary["isEnabled"] as? Bool{ + self.isEnabled = isEnabled + } + } + + public class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return Self.getContainerHeight() + } + + public func needsToBeConstrained() -> Bool { + return true + } + + public func alignment() -> UIStackView.Alignment { + return .trailing + } +} diff --git a/MVMCoreUI/Atoms/Views/ToggleModel.swift b/MVMCoreUI/Atoms/Views/ToggleModel.swift index df7d2ba8..81602c2a 100644 --- a/MVMCoreUI/Atoms/Views/ToggleModel.swift +++ b/MVMCoreUI/Atoms/Views/ToggleModel.swift @@ -10,32 +10,44 @@ import UIKit public class ToggleModel: MoleculeProtocol { public static var identifier: String = "toggle" + public var moleculeName: String? public var backgroundColor: Color? - public var on: Bool = true + public var state: Bool = true public var action: ActionProtocol? + public var required: Bool? + public var fieldKey: String? enum CodingKeys: String, CodingKey { - case on + case moleculeName + case state case action case backgroundColor + case required + case fieldKey } - public init(_ on: Bool) { - self.on = on + public init(_ state: Bool) { + self.state = state } required public init(from decoder: Decoder) throws { let typeContainer = try decoder.container(keyedBy: CodingKeys.self) - if let on = try typeContainer.decodeIfPresent(Bool.self, forKey: .on) { - self.on = on + if let state = try typeContainer.decodeIfPresent(Bool.self, forKey: .state) { + self.state = state } action = try typeContainer.decodeModelIfPresent(codingKey: .action, typeCodingKey: ActionCodingKey.actionType) backgroundColor = try typeContainer.decodeIfPresent(Color.self, forKey: .backgroundColor) + required = try typeContainer.decodeIfPresent(Bool.self, forKey: .required) + fieldKey = try typeContainer.decodeIfPresent(String.self, forKey: .fieldKey) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(backgroundColor, forKey: .backgroundColor) try container.encodeModelIfPresent(action, forKey: .action) + try container.encode(moleculeName, forKey: .moleculeName) + try container.encodeIfPresent(state, forKey: .state) + try container.encodeIfPresent(required, forKey: .required) + try container.encodeIfPresent(fieldKey, forKey: .fieldKey) } } diff --git a/MVMCoreUI/BaseClasses/Control.swift b/MVMCoreUI/BaseClasses/Control.swift index 915e0891..3eb268e5 100644 --- a/MVMCoreUI/BaseClasses/Control.swift +++ b/MVMCoreUI/BaseClasses/Control.swift @@ -81,6 +81,7 @@ extension Control: MVMCoreViewProtocol { // MARK: - MVMCoreUIMoleculeViewProtocol extension Control: MVMCoreUIMoleculeViewProtocol { + public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.json = json diff --git a/MVMCoreUI/Models/Primitive Models/Color.swift b/MVMCoreUI/Models/Primitive Models/Color.swift index e4c0e4fa..d72e1af9 100644 --- a/MVMCoreUI/Models/Primitive Models/Color.swift +++ b/MVMCoreUI/Models/Primitive Models/Color.swift @@ -19,6 +19,11 @@ public final class Color: Codable { //-------------------------------------------------- public let uiColor: UIColor + + public var cgColor: CGColor { + return uiColor.cgColor + } + public private(set) var hex: String = "" public private(set) var name: String = "" diff --git a/MVMCoreUI/Molecules/DoughnutChart.swift b/MVMCoreUI/Molecules/DoughnutChart.swift index d7f25ac6..1175a563 100644 --- a/MVMCoreUI/Molecules/DoughnutChart.swift +++ b/MVMCoreUI/Molecules/DoughnutChart.swift @@ -173,10 +173,15 @@ open class DoughnutChart: View, MVMCoreUIViewConstrainingProtocol { return doughnutChartModel?.spaceRequired ?? true } - func updateLabelContainer() { - labelContainer.leftPin?.constant = lineWidth() - labelContainer.topPin?.constant = lineWidth() - labelContainer.setNeedsDisplay() - } + func updateLabelContainer() { + labelContainer.setNeedsDisplay() + labelContainer.layoutIfNeeded() + let radius = sizeObject.getValueBasedOnApplicationWidth()/2 - lineWidth() + let labelheight = labelContainer.frame.height/2 + let padding = sizeObject.getValueBasedOnApplicationWidth()/2 - sqrt(pow(radius, 2) - pow(labelheight, 2)) + + labelContainer.leftPin?.constant = padding + labelContainer.topPin?.constant = max(radius - labelheight, labelheight) + } } diff --git a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift index b2f43529..9a7bd952 100644 --- a/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift +++ b/MVMCoreUI/Molecules/HorizontalCombinationViews/TwoButtonView.swift @@ -297,8 +297,8 @@ extension TwoButtonView { } } -extension TwoButtonView: MoleculeViewProtocol { - func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { +extension TwoButtonView: ModelMoleculeViewProtocol { + public func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { guard let model = model as? TwoButtonViewModel else { return } setupUI(primaryButtonShowing: model.primaryButton != nil, secondaryButtonShowing: model.secondaryButton != nil) setDefaultCustom() diff --git a/MVMCoreUI/Molecules/Items/TableViewCell.swift b/MVMCoreUI/Molecules/Items/TableViewCell.swift index 537af049..dc62ae65 100644 --- a/MVMCoreUI/Molecules/Items/TableViewCell.swift +++ b/MVMCoreUI/Molecules/Items/TableViewCell.swift @@ -192,6 +192,7 @@ import UIKit @objc public func addCaretViewAccessory() { guard accessoryView == nil else { return } caretView = CaretView(lineWidth: 1) + caretView?.translatesAutoresizingMaskIntoConstraints = true caretView?.size = .small(.vertical) caretView?.setConstraints() diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift index 50c19c1d..0713d40f 100644 --- a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelSwitch.swift @@ -8,9 +8,9 @@ import UIKit -@objcMembers public class LabelSwitch: ViewConstrainingView { +@objcMembers public class LabelSwitch: ViewConstrainingView, ModelMoleculeViewProtocol { let label = Label.commonLabelB1(true) - let mvmSwitch = MVMCoreUISwitch.mvmSwitchDefault() + let mvmSwitch = Toggle() // MARK: - MVMCoreViewProtocol open override func updateView(_ size: CGFloat) { @@ -40,6 +40,14 @@ import UIKit label.setWithJSON(json?.optionalDictionaryForKey("label"), delegateObject: delegateObject, additionalData: additionalData) mvmSwitch.setWithJSON(json?.optionalDictionaryForKey("toggle"), delegateObject: delegateObject, additionalData: additionalData) } + + public func setWithModel(_ model: MoleculeProtocol?, _ delegateObject: MVMCoreUIDelegateObject?, _ additionalData: [AnyHashable: Any]?) { + guard let labelToggleModel = model as? LabelToggleModel else { + return + } + label.setWithModel(labelToggleModel.label, delegateObject, additionalData) + mvmSwitch.setWithModel(labelToggleModel.toggle, delegateObject, additionalData) + } public override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return MVMCoreUISwitch.estimatedHeight(forRow: json, delegateObject: delegateObject) diff --git a/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelToggleModel.swift b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelToggleModel.swift new file mode 100644 index 00000000..6ac592d6 --- /dev/null +++ b/MVMCoreUI/Molecules/LeftRightViews/SwitchMolecules/LabelToggleModel.swift @@ -0,0 +1,16 @@ +// +// LabelToggle.swift +// MVMCoreUI +// +// Created by Suresh, Kamlesh on 1/15/20. +// Copyright © 2020 Verizon Wireless. All rights reserved. +// + +import Foundation + +public class LabelToggleModel: MoleculeProtocol { + public static var identifier: String = "labelToggle" + public var backgroundColor: Color? + public var label: LabelModel + public var toggle: ToggleModel +} diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 6c3e139e..b76474a7 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -53,7 +53,7 @@ @"radioButtonLabel": RadioButtonLabel.class, @"listItem": MoleculeTableViewCell.class, @"accordionListItem": AccordionMoleculeTableViewCell.class, - @"toggle": MVMCoreUISwitch.class, + @"toggle": Toggle.class, @"leftRightLabelView": LeftRightLabelView.class, @"actionDetailWithImage": ActionDetailWithImage.class, @"image": MFLoadImageView.class, diff --git a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift index 86e7ee7e..d42517ca 100644 --- a/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift +++ b/MVMCoreUI/OtherHandlers/MoleculeObjectMapping.swift @@ -12,6 +12,7 @@ import Foundation public static func registerObjects() { ModelRegistry.register(LabelModel.self) ModelRegistry.register(HeaderModel.self) + ModelRegistry.register(FooterModel.self) ModelRegistry.register(HeadlineBodyModel.self) ModelRegistry.register(MoleculeStackModel.self) ModelRegistry.register(StackItemModel.self) @@ -52,6 +53,7 @@ import Foundation ModelRegistry.register(LeftRightLabelModel.self) ModelRegistry.register(CaretViewModel.self) ModelRegistry.register(CaretLinkModel.self) + ModelRegistry.register(LabelToggleModel.self) ModelRegistry.register(DoughnutChartModel.self) } }