From 8238a42177fb749d63b1e9f946bb604a83c88f9c Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 13 Sep 2019 12:28:34 -0400 Subject: [PATCH 01/14] Added the new classes. --- MVMCoreUI.xcodeproj/project.pbxproj | 8 + MVMCoreUI/Atoms/Views/CheckBox.swift | 165 ++++++++++++++++++ MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift | 46 +++++ 3 files changed, 219 insertions(+) create mode 100644 MVMCoreUI/Atoms/Views/CheckBox.swift create mode 100644 MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index c1f4a798..c8cebe2b 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -18,6 +18,8 @@ 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */; }; 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; + 0A7BAFA1232BE61800FB8E22 /* CheckBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* CheckBox.swift */; }; + 0A7BAFA3232BE63400FB8E22 /* CheckBoxWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckBoxWithLabel.swift */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E182281DC1A007245F4 /* CornerLabels.swift */; }; @@ -202,6 +204,8 @@ 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = ""; }; 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; + 0A7BAFA0232BE61800FB8E22 /* CheckBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBox.swift; sourceTree = ""; }; + 0A7BAFA2232BE63400FB8E22 /* CheckBoxWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBoxWithLabel.swift; sourceTree = ""; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; B8200E182281DC1A007245F4 /* CornerLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = ""; }; @@ -719,6 +723,8 @@ DB891E822253FA8500022516 /* Label.swift */, 0198F7A02256A80A0066C936 /* MFRadioButton.h */, 0198F7A22256A80A0066C936 /* MFRadioButton.m */, + 0A7BAFA0232BE61800FB8E22 /* CheckBox.swift */, + 0A7BAFA2232BE63400FB8E22 /* CheckBoxWithLabel.swift */, ); path = Views; sourceTree = ""; @@ -1040,6 +1046,7 @@ D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */, + 0A7BAFA1232BE61800FB8E22 /* CheckBox.swift in Sources */, D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */, D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, @@ -1067,6 +1074,7 @@ D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */, + 0A7BAFA3232BE63400FB8E22 /* CheckBoxWithLabel.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */, D29DF29821E7ADB8003B2FB9 /* MFScrollingViewController.m in Sources */, diff --git a/MVMCoreUI/Atoms/Views/CheckBox.swift b/MVMCoreUI/Atoms/Views/CheckBox.swift new file mode 100644 index 00000000..a990aa90 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/CheckBox.swift @@ -0,0 +1,165 @@ +// +// CheckBox.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 9/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import MVMCore + +class CheckBox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + var lineWidth: CGFloat = 0.0 + var lineColor: UIColor? + + + let startXOffset: Float = 42.0 / 124.0 + let startYOffset: Float = 66.0 / 124.0 + let pivotXOffset: Float = 58.0 / 124.0 + let pivotYOffset: Float = 80.0 / 124.0 + let endXOffset: Float = 83.0 / 124.0 + let endYOffset: Float = 46.0 / 124.0 + let pivotPercentage: Float = 0.34 + let endPercentage = 1.0 - pivotPercentage + let animationInterval: Float = 0.01 + + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + private var drawPercentage: Float = 0.0 + private var animationTimer: Timer? + private var checkLayer: CAShapeLayer? + private var selected = false + + func setupView() { + super.setupView() + backgroundColor = UIColor.clear + drawPercentage = 1.0 + lineColor = UIColor.black + lineWidth = 1.0 + } + + func update(_ size: CGFloat) { + super.update(size) + } + + var lineWidth: CGFloat { + get { + return super.lineWidth + } + set(lineWidth) { + self.lineWidth = lineWidth + if checkLayer { + checkLayer.removeFromSuperlayer() + checkLayer = nil + updateCheckSelected(selected, animated: false) + } + } + } + + + //-------------------------------------------------- + // MARK: - Draw + //-------------------------------------------------- + + func drawCheck() { + if !checkLayer { + layoutIfNeeded() + let path = UIBezierPath() + path.move(to: CGPoint(x: lineWidth / 2, y: bounds.size.height * 0.55)) + path.addLine(to: CGPoint(x: bounds.size.width * 0.45, y: bounds.size.height * 0.85)) + path.addLine(to: CGPoint(x: bounds.size.width - lineWidth / 2, y: lineWidth / 2)) + + checkLayer = CAShapeLayer() + checkLayer.frame = bounds + layer.addSublayer(checkLayer) + checkLayer.strokeColor = lineColor.cgColor ?? UIColor.black.cgColor + checkLayer.fillColor = UIColor.clear.cgColor + checkLayer.path = path.cgPath + checkLayer.lineWidth = lineWidth + + CATransaction.begin() + CATransaction.setDisableActions(true) + checkLayer.strokeEnd = 0.0 + CATransaction.commit() + } + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + func updateCheckSelected(_ selected: Bool, animated: Bool) { + MVMCoreDispatchUtility.performBlock(onMainThread: { + self.selected = selected + + // animate this bar + self.drawCheck() + + var layer: CAShapeLayer? + if self.checkLayer.presentation() != nil && animated { + layer = self.checkLayer.presentation() + } else { + layer = self.checkLayer + } + + if animated { + let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") + animateStrokeEnd.fillMode = .both + animateStrokeEnd.isRemovedOnCompletion = false + animateStrokeEnd.duration = 0.3 + + animateStrokeEnd.fromValue = NSNumber(value: Float(layer?.strokeEnd ?? 0.0)) + if selected { + animateStrokeEnd.toValue = NSNumber(value: 1) + } else { + animateStrokeEnd.toValue = NSNumber(value: 0) + } + + animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) + layer?.add(animateStrokeEnd, forKey: "strokeEndAnimation") + } else { + layer?.removeAllAnimations() + CATransaction.begin() + CATransaction.setDisableActions(true) + if selected { + layer?.strokeEnd = 1.0 + } else { + layer?.strokeEnd = 0.0 + } + CATransaction.commit() + } + }) + } + + + func setLineColor(_ lineColor: UIColor?) { + self.lineColor = lineColor + if checkLayer { + checkLayer.strokeColor = lineColor?.cgColor + updateCheckSelected(selected, animated: false) + } + } + + func layoutSubviews() { + drawCheck() + } + + //-------------------------------------------------- + // MARK: - Molecular + //-------------------------------------------------- + + func updateView(_ size: CGFloat) { + <#code#> + } + + func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + <#code#> + } +} diff --git a/MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift b/MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift new file mode 100644 index 00000000..5e7c7739 --- /dev/null +++ b/MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift @@ -0,0 +1,46 @@ +// +// CheckBoxWithLabel.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 9/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + + +class CheckBoxWithLabel: ViewConstrainingView { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + weak var checkMark: CheckBox? + private(set) var isSelected = false + var sizeObject: MFSizeObject? + //?????? + var checkedColor: UIColor? + var unCheckedColor: UIColor? + // Label to the right of the check box. + weak var descriptionLabel: Label? + // Setter for the descriptionLabel.text. Also sets the accessibility text. + var descriptionText: String? + var descriptionAttributedText: NSAttributedString? + // A block that is called when the switch is selected. + var switchSelected: ((_ selected: Bool) -> ())? + + //-------------------------------------------------- + // MARK: - Life Cycle + //-------------------------------------------------- + + // TODO: MVMCoreUICheckBox.m + + //-------------------------------------------------- + // MARK: - Molecular + //-------------------------------------------------- + + override func updateView(_ size: CGFloat) { + + } + + override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + + } +} From bbdb0301eb612b2ee49348f91cda249fec95b3a9 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 16 Sep 2019 09:50:35 -0400 Subject: [PATCH 02/14] Further checkbox setups. --- MVMCoreUI/Atoms/Views/CheckBox.swift | 155 ++++++------ MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift | 231 ++++++++++++++++++ MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m | 2 + 3 files changed, 317 insertions(+), 71 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/CheckBox.swift b/MVMCoreUI/Atoms/Views/CheckBox.swift index a990aa90..ac6adf1a 100644 --- a/MVMCoreUI/Atoms/Views/CheckBox.swift +++ b/MVMCoreUI/Atoms/Views/CheckBox.swift @@ -8,14 +8,18 @@ import MVMCore + class CheckBox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - - var lineWidth: CGFloat = 0.0 - var lineColor: UIColor? + private var _lineColor: UIColor = .black + private var _lineWidth: CGFloat = 1.0 + + private var drawPercentage: Float = 0.0 + private var animationTimer: Timer? + private var checkLayer: CAShapeLayer? let startXOffset: Float = 42.0 / 124.0 let startYOffset: Float = 66.0 / 124.0 @@ -24,7 +28,7 @@ class CheckBox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, M let endXOffset: Float = 83.0 / 124.0 let endYOffset: Float = 46.0 / 124.0 let pivotPercentage: Float = 0.34 - let endPercentage = 1.0 - pivotPercentage + let endPercentage = 0.66 let animationInterval: Float = 0.01 @@ -32,63 +36,73 @@ class CheckBox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, M // MARK: - Lifecycle //-------------------------------------------------- - private var drawPercentage: Float = 0.0 - private var animationTimer: Timer? - private var checkLayer: CAShapeLayer? - private var selected = false + override init(frame: CGRect) { + super.init(frame: frame) - func setupView() { - super.setupView() - backgroundColor = UIColor.clear - drawPercentage = 1.0 - lineColor = UIColor.black - lineWidth = 1.0 - } - - func update(_ size: CGFloat) { - super.update(size) - } + backgroundColor = .clear + drawPercentage = 1.0 + lineColor = .black + lineWidth = 1.0 + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + fatalError("Xib File is not implemented for CheckBox.") + } var lineWidth: CGFloat { get { - return super.lineWidth + return _lineWidth } set(lineWidth) { self.lineWidth = lineWidth - if checkLayer { - checkLayer.removeFromSuperlayer() - checkLayer = nil - updateCheckSelected(selected, animated: false) - } + guard let checkLayer = checkLayer else { return } + checkLayer.removeFromSuperlayer() + checkLayer = nil + updateCheckSelected(isSelected, animated: false) } } + var lineColor: UIColor { + get { + return _lineColor + } + set(lineColor) { + _lineColor = lineColor + + if let checkLayer = checkLayer { + checkLayer.strokeColor = lineColor.cgColor + updateCheckSelected(isSelected, animated: false) + } + } + } //-------------------------------------------------- // MARK: - Draw //-------------------------------------------------- func drawCheck() { - if !checkLayer { - layoutIfNeeded() - let path = UIBezierPath() - path.move(to: CGPoint(x: lineWidth / 2, y: bounds.size.height * 0.55)) - path.addLine(to: CGPoint(x: bounds.size.width * 0.45, y: bounds.size.height * 0.85)) - path.addLine(to: CGPoint(x: bounds.size.width - lineWidth / 2, y: lineWidth / 2)) - - checkLayer = CAShapeLayer() - checkLayer.frame = bounds - layer.addSublayer(checkLayer) - checkLayer.strokeColor = lineColor.cgColor ?? UIColor.black.cgColor - checkLayer.fillColor = UIColor.clear.cgColor - checkLayer.path = path.cgPath - checkLayer.lineWidth = lineWidth - - CATransaction.begin() - CATransaction.setDisableActions(true) - checkLayer.strokeEnd = 0.0 - CATransaction.commit() - } + + guard let checkLayer = checkLayer else { return } + + layoutIfNeeded() + let path = UIBezierPath() + path.move(to: CGPoint(x: lineWidth / 2, y: bounds.size.height * 0.55)) + path.addLine(to: CGPoint(x: bounds.size.width * 0.45, y: bounds.size.height * 0.85)) + path.addLine(to: CGPoint(x: bounds.size.width - lineWidth / 2, y: lineWidth / 2)) + + checkLayer = CAShapeLayer() + checkLayer.frame = bounds + layer.addSublayer(checkLayer) + checkLayer.strokeColor = lineColor.cgColor + checkLayer.fillColor = UIColor.clear.cgColor + checkLayer.path = path.cgPath + checkLayer.lineWidth = lineWidth + + CATransaction.begin() + CATransaction.setDisableActions(true) + checkLayer.strokeEnd = 0.0 + CATransaction.commit() } //-------------------------------------------------- @@ -96,15 +110,17 @@ class CheckBox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, M //-------------------------------------------------- func updateCheckSelected(_ selected: Bool, animated: Bool) { - MVMCoreDispatchUtility.performBlock(onMainThread: { - self.selected = selected + + DispatchQueue.main.async { + + self.isSelected = selected // animate this bar self.drawCheck() var layer: CAShapeLayer? - if self.checkLayer.presentation() != nil && animated { - layer = self.checkLayer.presentation() + if self.checkLayer?.presentation() != nil && animated { + layer = self.checkLayer!.presentation() } else { layer = self.checkLayer } @@ -116,11 +132,8 @@ class CheckBox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, M animateStrokeEnd.duration = 0.3 animateStrokeEnd.fromValue = NSNumber(value: Float(layer?.strokeEnd ?? 0.0)) - if selected { - animateStrokeEnd.toValue = NSNumber(value: 1) - } else { - animateStrokeEnd.toValue = NSNumber(value: 0) - } + + animateStrokeEnd.toValue = NSNumber(value: selected ? 1 : 0) animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) layer?.add(animateStrokeEnd, forKey: "strokeEndAnimation") @@ -128,38 +141,38 @@ class CheckBox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, M layer?.removeAllAnimations() CATransaction.begin() CATransaction.setDisableActions(true) - if selected { - layer?.strokeEnd = 1.0 - } else { - layer?.strokeEnd = 0.0 - } + layer?.strokeEnd = selected ? 1.0 : 0.0 + CATransaction.commit() } - }) - } - - - func setLineColor(_ lineColor: UIColor?) { - self.lineColor = lineColor - if checkLayer { - checkLayer.strokeColor = lineColor?.cgColor - updateCheckSelected(selected, animated: false) } } - func layoutSubviews() { + override func layoutSubviews() { drawCheck() } + private func defaultState() { + + } + //-------------------------------------------------- // MARK: - Molecular //-------------------------------------------------- + open func reset() { + + } + + open func setAsMolecule() { + + } + func updateView(_ size: CGFloat) { - <#code#> + } func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - <#code#> + } } diff --git a/MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift b/MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift index 5e7c7739..972100d4 100644 --- a/MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift +++ b/MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift @@ -32,6 +32,237 @@ class CheckBoxWithLabel: ViewConstrainingView { // TODO: MVMCoreUICheckBox.m + func needsToBeConstrained() -> Bool { + return true + } + + func alignment() -> UIStackView.Alignment { + return .leading + } + + func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + + FormValidator.setupValidation(withMolecule: self, delegate: delegateObject?.formValidationProtocol) + delegate = delegateObject + fieldKey = json?.string(for: KeyFieldKey) + isRequired = json?.bool(forKey: KeyRequired) + + let checkedColorHex = json?.string("checkedColor") + let unCheckedColorHex = json?.string("unCheckedColor") + + let checkedColor = checkedColorHex != nil ? UIColor.mfGet(forHex: checkedColorHex) : UIColor.clear + let unCheckedColor = unCheckedColorHex != nil ? UIColor.mfGet(forHex: unCheckedColorHex) : UIColor.clear + + setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, label: json?.dict(KeyLabel), delegateObject: delegateObject, additionalData: additionalData) + } + + class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return CGFloat(CheckBoxHeightWidth) + } + + // MARK: - convenient class methods + class func mf() -> Self { + let checkBox = self.init(frame: CGRect.zero) + checkBox.translatesAutoresizingMaskIntoConstraints = false + return checkBox + } + + class func mfCheckBoxWithRoundedRect() -> Self? { + let checkBox = self.init(roundRect: true) + checkBox.translatesAutoresizingMaskIntoConstraints = false + return checkBox + } + + class func mfCheckBox(withCheckedColor checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, text: String?) -> Self { + let checkBox = self.init(checkedColor: checkedColor, unCheck: unCheckedColor, text: text) + checkBox?.translatesAutoresizingMaskIntoConstraints = false + return checkBox + } + + class func mfCheckBox(withCheckedColor checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, atributedText attributedText: NSAttributedString?) -> Self { + let checkBox = self.init(checkedColor: checkedColor, unCheck: unCheckedColor, attributedText: attributedText) + checkBox?.translatesAutoresizingMaskIntoConstraints = false + return checkBox + } + + // MARK: - FormValidationProtocol + func isValidField() -> Bool { + if isRequired { + return isSelected() + } + return true + } + + func formFieldName() -> String? { + return fieldKey + } + + func formFieldValue() -> Any? { + return NSNumber(value: isSelected()) + } + + // MARK: - inits + init() { + super.init() + setupView() + } + + init(checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, text: String?) { + super.init() + setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: text) + addAccessibleProperties() + } + + init(checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, attributedText: NSAttributedString?) { + super.init() + setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: nil) + descriptionAttributedText = attributedText + addAccessibleProperties() + } + + init?(checkMarkView: MVMCoreUICheckMarkView?, checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, text: String?) { + super.init() + checkMark = checkMarkView + setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: text) + addAccessibleProperties() + } + + init?(checkMarkView: MVMCoreUICheckMarkView?, checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, attributedText: NSAttributedString?) { + super.init() + checkMark = checkMarkView + setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: nil) + descriptionAttributedText = attributedText + addAccessibleProperties() + } + + init(roundRect isRoundRect: Bool) { + super.init() + isRoundRectCheckMark = isRoundRect + setup(withCheckedColor: UIColor.white, unCheck: UIColor.white, text: nil) + addAccessibleProperties() + setCheckMarkLayer() + } + + //default inits + required init?(coder: NSCoder) { + super.init(coder: coder) + setupView() + setup(withCheckedColor: UIColor.white, unCheck: UIColor.white, text: nil) + addAccessibleProperties() + } + + init(frame: CGRect) { + super.init(frame: frame) + setupView() + setup(withCheckedColor: UIColor.white, unCheck: UIColor.white, text: nil) + addAccessibleProperties() + } + + func awakeFromNib() { + super.awakeFromNib() + setup(withCheckedColor: UIColor.white, unCheck: UIColor.white, text: nil) + } + + func setupView() { + + let containterView = MVMCoreUICommonViewsUtility.commonView() + containterView?.isUserInteractionEnabled = false + if !sizeObject { + sizeObject = MFSizeObject(standardSize: CheckBoxHeightWidth, standardiPadPortraitSize: Int(CheckBoxHeightWidth) + 6) + } + + //checked circle + if !self.checkedSquare { + let checkedSquare = MVMCoreUICommonViewsUtility.commonView() + checkedSquare?.backgroundColor = UIColor.white + if let checkedSquare = checkedSquare { + containterView?.addSubview(checkedSquare) + } + let size = sizeObject.getValueBasedOnApplicationWidth() + let constraints = NSLayoutConstraint.constraintPinView(checkedSquare, heightConstraint: true, heightConstant: size, widthConstraint: true, widthConstant: size) + checkboxWidth = constraints[ConstraintWidth] + checkboxHeight = constraints[ConstraintHeight] + NSLayoutConstraint.constraintPinSubview(checkedSquare, pinTop: true, pinBottom: true, pinLeft: true, pinRight: false) + + checkboxRightPinConstraint = checkedSquare?.trailingAnchor.constraintEqual(to: containterView?.trailingAnchor) + + NSLayoutConstraint.constraintPinSubview(checkedSquare, pinCenterX: false, pinCenterY: true) + self.checkedSquare = checkedSquare + self.checkBoxBorder = UIColor.black + } + + // TODO: OBJC CODE + + //check mark + if (!self.checkMark) { + MVMCoreUICheckMarkView *checkMark = [[MVMCoreUICheckMarkView alloc] initWithFrame:self.frame]; + checkMark.lineWidth = 2.0; + self.checkMark = checkMark; + self.checkMark.translatesAutoresizingMaskIntoConstraints = NO; + [self.checkedSquare addSubview:self.checkMark]; + [self.checkMark.widthAnchor constraintEqualToAnchor:self.checkedSquare.widthAnchor multiplier:.4].active = YES; + [self.checkMark.heightAnchor constraintEqualToAnchor:self.checkedSquare.heightAnchor multiplier:.4].active = YES; + [self.checkMark.centerXAnchor constraintEqualToAnchor:self.checkedSquare.centerXAnchor].active = YES; + [self.checkMark.centerYAnchor constraintEqualToAnchor:self.checkedSquare.centerYAnchor].active = YES; + } + + //label + if (!self.descriptionLabel) { + Label *descriptionLabel = [Label commonLabelB2:YES]; + [containterView addSubview:descriptionLabel]; + [NSLayoutConstraint constraintPinSubview:descriptionLabel pinCenterX:NO pinCenterY:YES]; + [NSLayoutConstraint constraintPinSubview:descriptionLabel pinTop:NO pinBottom:NO pinLeft:NO pinRight:YES]; + + self.descriptionLabelLeadingConstraint = [NSLayoutConstraint constraintWithItem:descriptionLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.checkedSquare attribute:NSLayoutAttributeTrailing multiplier:1 constant:11]; + self.descriptionLabelLeadingConstraint.active = YES; + + self.descriptionLabelLeadingConstraint.active = YES; + self.descriptionLabel = descriptionLabel; + [self setSelected:NO]; + } + + if (!self.containerView) { + [self addSubview:containterView]; + self.containerView = containterView; + [NSLayoutConstraint constraintPinSubviewToSuperview:containterView]; + } + } + + func setup(withCheckedColor checkedColor: UIColor?, unCheck unCheckedColor: UIColor?) { + if checkedColor != nil { + self.checkedColor = checkedColor + } + if unCheckedColor != nil { + self.unCheckedColor = unCheckedColor + } + } + + func setup(withCheckedColor checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, text: String?) { + setup(withCheckedColor: checkedColor, unCheck: unCheckedColor) + descriptionText = text ?? "" + } + + func setup(withCheckedColor checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, label labelJson: [AnyHashable : Any]?, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) { + setup(withCheckedColor: checkedColor, unCheck: unCheckedColor) + descriptionLabel.setWithJSON(labelJson, delegateObject: delegateObject, additionalData: additionalData) + } + + @objc func updateView(_ size: CGFloat) { + MVMCoreDispatchUtility.performBlock(onMainThread: { + self.descriptionLabel.updateView(size) + if self.checkMark.responds(to: #selector(updateView(_:))) { + let width = self.sizeObject.getValueBased(onSize: size) + self.checkboxWidth.constant = width + self.checkboxHeight.constant = width + self.checkMark.updateView(size) + } + }) + } + + func setCheckMarkLayer() { + checkedSquare.layer.cornerRadius = isRoundRectCheckMark ? 5.0 : 0 + } + //-------------------------------------------------- // MARK: - Molecular //-------------------------------------------------- diff --git a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m index 2cde1bc3..d219f2f3 100644 --- a/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m +++ b/MVMCoreUI/Atoms/Views/MVMCoreUICheckBox.m @@ -306,6 +306,8 @@ static const CGFloat CheckBoxHeightWidth = 18.0; self.checkedSquare.layer.cornerRadius = self.isRoundRectCheckMark ? 5.0f : 0; } +// TODO:..................................... + #pragma mark - XIB Helpers - (instancetype)awakeAfterUsingCoder:(NSCoder *)aDecoder { From 35f7024f8d47504bae8ddd8b0b35304a66409eed Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 25 Sep 2019 14:19:49 -0400 Subject: [PATCH 03/14] Adding two new checkbox swift classes. --- MVMCoreUI.xcodeproj/project.pbxproj | 16 +- MVMCoreUI/Atoms/Views/CheckBox.swift | 178 ----------- MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift | 277 ----------------- MVMCoreUI/Atoms/Views/Checkbox.swift | 288 +++++++++++++++++ .../Atoms/Views/CheckboxWithLabelView.swift | 289 ++++++++++++++++++ .../MVMCoreUIMoleculeMappingObject.m | 4 +- 6 files changed, 587 insertions(+), 465 deletions(-) delete mode 100644 MVMCoreUI/Atoms/Views/CheckBox.swift delete mode 100644 MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift create mode 100644 MVMCoreUI/Atoms/Views/Checkbox.swift create mode 100644 MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index b2dd623a..9692652c 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -18,8 +18,8 @@ 01DF567021FA5AB300CC099B /* TextFieldListFormViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */; }; 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; - 0A7BAFA1232BE61800FB8E22 /* CheckBox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* CheckBox.swift */; }; - 0A7BAFA3232BE63400FB8E22 /* CheckBoxWithLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckBoxWithLabel.swift */; }; + 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */; }; + 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; B8200E192281DC1A007245F4 /* CornerLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E182281DC1A007245F4 /* CornerLabels.swift */; }; @@ -205,8 +205,8 @@ 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = ""; }; 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; - 0A7BAFA0232BE61800FB8E22 /* CheckBox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBox.swift; sourceTree = ""; }; - 0A7BAFA2232BE63400FB8E22 /* CheckBoxWithLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckBoxWithLabel.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 = ""; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; B8200E142280C4CF007245F4 /* ProgressBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProgressBar.swift; sourceTree = ""; }; B8200E182281DC1A007245F4 /* CornerLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerLabels.swift; sourceTree = ""; }; @@ -726,8 +726,8 @@ DB891E822253FA8500022516 /* Label.swift */, 0198F7A02256A80A0066C936 /* MFRadioButton.h */, 0198F7A22256A80A0066C936 /* MFRadioButton.m */, - 0A7BAFA0232BE61800FB8E22 /* CheckBox.swift */, - 0A7BAFA2232BE63400FB8E22 /* CheckBoxWithLabel.swift */, + 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */, + 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */, ); path = Views; sourceTree = ""; @@ -1042,6 +1042,7 @@ D2D6CD4022E78C1A00D701B8 /* Scroller.swift in Sources */, D29DF13021E6851E003B2FB9 /* MVMCoreUITopAlertShortView.m in Sources */, D28B4F8B21FF967C00712C7A /* MVMCoreUIObject.m in Sources */, + 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */, D260D7B222D65BDD007E7233 /* MVMCoreUIPageControl.m in Sources */, D29DF26D21E6AA0B003B2FB9 /* FLAnimatedImageView.m in Sources */, D29DF2EF21ECEAE1003B2FB9 /* MFFonts.m in Sources */, @@ -1049,7 +1050,7 @@ D282AACB2243C61700C46919 /* ButtonView.swift in Sources */, D2D6CD4222E78FAB00D701B8 /* ThreeLayerTemplate.swift in Sources */, 0105618F224BBE7700E1557D /* FormValidator+FormParams.swift in Sources */, - 0A7BAFA1232BE61800FB8E22 /* CheckBox.swift in Sources */, + 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */, D22479962316AF6E003FCCF9 /* HeadlineBodyTextButton.swift in Sources */, D2E1FADD2268B25E00AEFD8C /* MoleculeTableViewCell.swift in Sources */, D29DF2AE21E7B3A4003B2FB9 /* MFTextView.m in Sources */, @@ -1077,7 +1078,6 @@ D22D1F47220496A30077CEC0 /* MVMCoreUISwitch.m in Sources */, D29DF28C21E7AC2B003B2FB9 /* ViewConstrainingView.m in Sources */, D29DF17B21E69E1F003B2FB9 /* PrimaryButton.m in Sources */, - 0A7BAFA3232BE63400FB8E22 /* CheckBoxWithLabel.swift in Sources */, D27CD4102339057800C1DC07 /* EyebrowHeadlineBodyLink.swift in Sources */, D29DF11D21E684A9003B2FB9 /* MVMCoreUISplitViewController.m in Sources */, 0198F79F225679880066C936 /* FormValidationProtocol.swift in Sources */, diff --git a/MVMCoreUI/Atoms/Views/CheckBox.swift b/MVMCoreUI/Atoms/Views/CheckBox.swift deleted file mode 100644 index ac6adf1a..00000000 --- a/MVMCoreUI/Atoms/Views/CheckBox.swift +++ /dev/null @@ -1,178 +0,0 @@ -// -// CheckBox.swift -// MVMCoreUI -// -// Created by Kevin Christiano on 9/13/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - -import MVMCore - - -class CheckBox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - - private var _lineColor: UIColor = .black - private var _lineWidth: CGFloat = 1.0 - - private var drawPercentage: Float = 0.0 - private var animationTimer: Timer? - private var checkLayer: CAShapeLayer? - - let startXOffset: Float = 42.0 / 124.0 - let startYOffset: Float = 66.0 / 124.0 - let pivotXOffset: Float = 58.0 / 124.0 - let pivotYOffset: Float = 80.0 / 124.0 - let endXOffset: Float = 83.0 / 124.0 - let endYOffset: Float = 46.0 / 124.0 - let pivotPercentage: Float = 0.34 - let endPercentage = 0.66 - let animationInterval: Float = 0.01 - - - //-------------------------------------------------- - // MARK: - Lifecycle - //-------------------------------------------------- - - override init(frame: CGRect) { - super.init(frame: frame) - - backgroundColor = .clear - drawPercentage = 1.0 - lineColor = .black - lineWidth = 1.0 - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - fatalError("Xib File is not implemented for CheckBox.") - } - - var lineWidth: CGFloat { - get { - return _lineWidth - } - set(lineWidth) { - self.lineWidth = lineWidth - guard let checkLayer = checkLayer else { return } - checkLayer.removeFromSuperlayer() - checkLayer = nil - updateCheckSelected(isSelected, animated: false) - } - } - - var lineColor: UIColor { - get { - return _lineColor - } - set(lineColor) { - _lineColor = lineColor - - if let checkLayer = checkLayer { - checkLayer.strokeColor = lineColor.cgColor - updateCheckSelected(isSelected, animated: false) - } - } - } - - //-------------------------------------------------- - // MARK: - Draw - //-------------------------------------------------- - - func drawCheck() { - - guard let checkLayer = checkLayer else { return } - - layoutIfNeeded() - let path = UIBezierPath() - path.move(to: CGPoint(x: lineWidth / 2, y: bounds.size.height * 0.55)) - path.addLine(to: CGPoint(x: bounds.size.width * 0.45, y: bounds.size.height * 0.85)) - path.addLine(to: CGPoint(x: bounds.size.width - lineWidth / 2, y: lineWidth / 2)) - - checkLayer = CAShapeLayer() - checkLayer.frame = bounds - layer.addSublayer(checkLayer) - checkLayer.strokeColor = lineColor.cgColor - checkLayer.fillColor = UIColor.clear.cgColor - checkLayer.path = path.cgPath - checkLayer.lineWidth = lineWidth - - CATransaction.begin() - CATransaction.setDisableActions(true) - checkLayer.strokeEnd = 0.0 - CATransaction.commit() - } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - func updateCheckSelected(_ selected: Bool, animated: Bool) { - - DispatchQueue.main.async { - - self.isSelected = selected - - // animate this bar - self.drawCheck() - - var layer: CAShapeLayer? - if self.checkLayer?.presentation() != nil && animated { - layer = self.checkLayer!.presentation() - } else { - layer = self.checkLayer - } - - if animated { - let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") - animateStrokeEnd.fillMode = .both - animateStrokeEnd.isRemovedOnCompletion = false - animateStrokeEnd.duration = 0.3 - - animateStrokeEnd.fromValue = NSNumber(value: Float(layer?.strokeEnd ?? 0.0)) - - animateStrokeEnd.toValue = NSNumber(value: selected ? 1 : 0) - - animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) - layer?.add(animateStrokeEnd, forKey: "strokeEndAnimation") - } else { - layer?.removeAllAnimations() - CATransaction.begin() - CATransaction.setDisableActions(true) - layer?.strokeEnd = selected ? 1.0 : 0.0 - - CATransaction.commit() - } - } - } - - override func layoutSubviews() { - drawCheck() - } - - private func defaultState() { - - } - - //-------------------------------------------------- - // MARK: - Molecular - //-------------------------------------------------- - - open func reset() { - - } - - open func setAsMolecule() { - - } - - func updateView(_ size: CGFloat) { - - } - - func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - - } -} diff --git a/MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift b/MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift deleted file mode 100644 index 972100d4..00000000 --- a/MVMCoreUI/Atoms/Views/CheckBoxWithLabel.swift +++ /dev/null @@ -1,277 +0,0 @@ -// -// CheckBoxWithLabel.swift -// MVMCoreUI -// -// Created by Kevin Christiano on 9/13/19. -// Copyright © 2019 Verizon Wireless. All rights reserved. -// - - -class CheckBoxWithLabel: ViewConstrainingView { - //-------------------------------------------------- - // MARK: - Properties - //-------------------------------------------------- - - weak var checkMark: CheckBox? - private(set) var isSelected = false - var sizeObject: MFSizeObject? - //?????? - var checkedColor: UIColor? - var unCheckedColor: UIColor? - // Label to the right of the check box. - weak var descriptionLabel: Label? - // Setter for the descriptionLabel.text. Also sets the accessibility text. - var descriptionText: String? - var descriptionAttributedText: NSAttributedString? - // A block that is called when the switch is selected. - var switchSelected: ((_ selected: Bool) -> ())? - - //-------------------------------------------------- - // MARK: - Life Cycle - //-------------------------------------------------- - - // TODO: MVMCoreUICheckBox.m - - func needsToBeConstrained() -> Bool { - return true - } - - func alignment() -> UIStackView.Alignment { - return .leading - } - - func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - - FormValidator.setupValidation(withMolecule: self, delegate: delegateObject?.formValidationProtocol) - delegate = delegateObject - fieldKey = json?.string(for: KeyFieldKey) - isRequired = json?.bool(forKey: KeyRequired) - - let checkedColorHex = json?.string("checkedColor") - let unCheckedColorHex = json?.string("unCheckedColor") - - let checkedColor = checkedColorHex != nil ? UIColor.mfGet(forHex: checkedColorHex) : UIColor.clear - let unCheckedColor = unCheckedColorHex != nil ? UIColor.mfGet(forHex: unCheckedColorHex) : UIColor.clear - - setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, label: json?.dict(KeyLabel), delegateObject: delegateObject, additionalData: additionalData) - } - - class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return CGFloat(CheckBoxHeightWidth) - } - - // MARK: - convenient class methods - class func mf() -> Self { - let checkBox = self.init(frame: CGRect.zero) - checkBox.translatesAutoresizingMaskIntoConstraints = false - return checkBox - } - - class func mfCheckBoxWithRoundedRect() -> Self? { - let checkBox = self.init(roundRect: true) - checkBox.translatesAutoresizingMaskIntoConstraints = false - return checkBox - } - - class func mfCheckBox(withCheckedColor checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, text: String?) -> Self { - let checkBox = self.init(checkedColor: checkedColor, unCheck: unCheckedColor, text: text) - checkBox?.translatesAutoresizingMaskIntoConstraints = false - return checkBox - } - - class func mfCheckBox(withCheckedColor checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, atributedText attributedText: NSAttributedString?) -> Self { - let checkBox = self.init(checkedColor: checkedColor, unCheck: unCheckedColor, attributedText: attributedText) - checkBox?.translatesAutoresizingMaskIntoConstraints = false - return checkBox - } - - // MARK: - FormValidationProtocol - func isValidField() -> Bool { - if isRequired { - return isSelected() - } - return true - } - - func formFieldName() -> String? { - return fieldKey - } - - func formFieldValue() -> Any? { - return NSNumber(value: isSelected()) - } - - // MARK: - inits - init() { - super.init() - setupView() - } - - init(checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, text: String?) { - super.init() - setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: text) - addAccessibleProperties() - } - - init(checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, attributedText: NSAttributedString?) { - super.init() - setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: nil) - descriptionAttributedText = attributedText - addAccessibleProperties() - } - - init?(checkMarkView: MVMCoreUICheckMarkView?, checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, text: String?) { - super.init() - checkMark = checkMarkView - setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: text) - addAccessibleProperties() - } - - init?(checkMarkView: MVMCoreUICheckMarkView?, checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, attributedText: NSAttributedString?) { - super.init() - checkMark = checkMarkView - setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: nil) - descriptionAttributedText = attributedText - addAccessibleProperties() - } - - init(roundRect isRoundRect: Bool) { - super.init() - isRoundRectCheckMark = isRoundRect - setup(withCheckedColor: UIColor.white, unCheck: UIColor.white, text: nil) - addAccessibleProperties() - setCheckMarkLayer() - } - - //default inits - required init?(coder: NSCoder) { - super.init(coder: coder) - setupView() - setup(withCheckedColor: UIColor.white, unCheck: UIColor.white, text: nil) - addAccessibleProperties() - } - - init(frame: CGRect) { - super.init(frame: frame) - setupView() - setup(withCheckedColor: UIColor.white, unCheck: UIColor.white, text: nil) - addAccessibleProperties() - } - - func awakeFromNib() { - super.awakeFromNib() - setup(withCheckedColor: UIColor.white, unCheck: UIColor.white, text: nil) - } - - func setupView() { - - let containterView = MVMCoreUICommonViewsUtility.commonView() - containterView?.isUserInteractionEnabled = false - if !sizeObject { - sizeObject = MFSizeObject(standardSize: CheckBoxHeightWidth, standardiPadPortraitSize: Int(CheckBoxHeightWidth) + 6) - } - - //checked circle - if !self.checkedSquare { - let checkedSquare = MVMCoreUICommonViewsUtility.commonView() - checkedSquare?.backgroundColor = UIColor.white - if let checkedSquare = checkedSquare { - containterView?.addSubview(checkedSquare) - } - let size = sizeObject.getValueBasedOnApplicationWidth() - let constraints = NSLayoutConstraint.constraintPinView(checkedSquare, heightConstraint: true, heightConstant: size, widthConstraint: true, widthConstant: size) - checkboxWidth = constraints[ConstraintWidth] - checkboxHeight = constraints[ConstraintHeight] - NSLayoutConstraint.constraintPinSubview(checkedSquare, pinTop: true, pinBottom: true, pinLeft: true, pinRight: false) - - checkboxRightPinConstraint = checkedSquare?.trailingAnchor.constraintEqual(to: containterView?.trailingAnchor) - - NSLayoutConstraint.constraintPinSubview(checkedSquare, pinCenterX: false, pinCenterY: true) - self.checkedSquare = checkedSquare - self.checkBoxBorder = UIColor.black - } - - // TODO: OBJC CODE - - //check mark - if (!self.checkMark) { - MVMCoreUICheckMarkView *checkMark = [[MVMCoreUICheckMarkView alloc] initWithFrame:self.frame]; - checkMark.lineWidth = 2.0; - self.checkMark = checkMark; - self.checkMark.translatesAutoresizingMaskIntoConstraints = NO; - [self.checkedSquare addSubview:self.checkMark]; - [self.checkMark.widthAnchor constraintEqualToAnchor:self.checkedSquare.widthAnchor multiplier:.4].active = YES; - [self.checkMark.heightAnchor constraintEqualToAnchor:self.checkedSquare.heightAnchor multiplier:.4].active = YES; - [self.checkMark.centerXAnchor constraintEqualToAnchor:self.checkedSquare.centerXAnchor].active = YES; - [self.checkMark.centerYAnchor constraintEqualToAnchor:self.checkedSquare.centerYAnchor].active = YES; - } - - //label - if (!self.descriptionLabel) { - Label *descriptionLabel = [Label commonLabelB2:YES]; - [containterView addSubview:descriptionLabel]; - [NSLayoutConstraint constraintPinSubview:descriptionLabel pinCenterX:NO pinCenterY:YES]; - [NSLayoutConstraint constraintPinSubview:descriptionLabel pinTop:NO pinBottom:NO pinLeft:NO pinRight:YES]; - - self.descriptionLabelLeadingConstraint = [NSLayoutConstraint constraintWithItem:descriptionLabel attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.checkedSquare attribute:NSLayoutAttributeTrailing multiplier:1 constant:11]; - self.descriptionLabelLeadingConstraint.active = YES; - - self.descriptionLabelLeadingConstraint.active = YES; - self.descriptionLabel = descriptionLabel; - [self setSelected:NO]; - } - - if (!self.containerView) { - [self addSubview:containterView]; - self.containerView = containterView; - [NSLayoutConstraint constraintPinSubviewToSuperview:containterView]; - } - } - - func setup(withCheckedColor checkedColor: UIColor?, unCheck unCheckedColor: UIColor?) { - if checkedColor != nil { - self.checkedColor = checkedColor - } - if unCheckedColor != nil { - self.unCheckedColor = unCheckedColor - } - } - - func setup(withCheckedColor checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, text: String?) { - setup(withCheckedColor: checkedColor, unCheck: unCheckedColor) - descriptionText = text ?? "" - } - - func setup(withCheckedColor checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, label labelJson: [AnyHashable : Any]?, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) { - setup(withCheckedColor: checkedColor, unCheck: unCheckedColor) - descriptionLabel.setWithJSON(labelJson, delegateObject: delegateObject, additionalData: additionalData) - } - - @objc func updateView(_ size: CGFloat) { - MVMCoreDispatchUtility.performBlock(onMainThread: { - self.descriptionLabel.updateView(size) - if self.checkMark.responds(to: #selector(updateView(_:))) { - let width = self.sizeObject.getValueBased(onSize: size) - self.checkboxWidth.constant = width - self.checkboxHeight.constant = width - self.checkMark.updateView(size) - } - }) - } - - func setCheckMarkLayer() { - checkedSquare.layer.cornerRadius = isRoundRectCheckMark ? 5.0 : 0 - } - - //-------------------------------------------------- - // MARK: - Molecular - //-------------------------------------------------- - - override func updateView(_ size: CGFloat) { - - } - - override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - - } -} diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift new file mode 100644 index 00000000..08f6227f --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -0,0 +1,288 @@ +// +// Checkbox.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 9/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import MVMCore + + +@objcMembers open class Checkbox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public static let defaultHeightWidth: CGFloat = 18.0 + /* + //Offsets based on the 124x124 example checkmark + let startXOffset: Float = 42.0 / 124.0 + let startYOffset: Float = 66.0 / 124.0 + let pivotXOffset: Float = 58.0 / 124.0 + let pivotYOffset: Float = 80.0 / 124.0 + let endXOffset: Float = 83.0 / 124.0 + let endYOffset: Float = 46.0 / 124.0 + let pivotPercentage: Float = 0.34 + let endPercentage = 1.0 - pivotPercentage + let animationInterval: Float = 0.01 + */ + + public var checkedColor: UIColor = .black + public var unCheckedColor: UIColor = .mfPaleGrey() + + public var hasRoundBorder = false + + private var shapeLayer: CAShapeLayer? + private var _lineColor: UIColor = .black + private var _borderColor: UIColor = .black + private var _lineWidth: CGFloat = 1.0 + private var _cornerRadius: CGFloat = 5.0 + + public func setCheckMarkLayer() { + checkedSquare.layer.cornerRadius = checkbox.isRoundRectCheckMark ? 5.0 : 0 + } + + public var lineWidth: CGFloat { + get { return _lineWidth } + set (newLineWidth) { + _lineWidth = newLineWidth + if shapeLayer != nil { + shapeLayer?.removeFromSuperlayer() + shapeLayer = nil + updateCheckSelected(isSelected, animated: false) + } + } + } + + public var lineColor: UIColor { + get { return _lineColor } + set (newLineColor) { + _lineColor = newLineColor + if let shapeLayer = shapeLayer { + shapeLayer.strokeColor = lineColor.cgColor + updateCheckSelected(isSelected, animated: false) + } + } + } + + public var borderColor: UIColor { + get { return _borderColor } + set (newBorderColor) { + _borderColor = newBorderColor + if let shapeLayer = shapeLayer { + shapeLayer.strokeColor = borderColor.cgColor + updateCheckSelected(isSelected, animated: false) + } + } + } + + override open var isSelected: Bool { + didSet { + if isSelected { + layer.addSublayer(shapeLayer!) + shapeLayer?.strokeEnd = 1 + shapeLayer?.removeAllAnimations() + shapeLayer?.add(checkedAnimation, forKey: "strokeEnd") + } else { + shapeLayer?.strokeEnd = 0 + shapeLayer?.removeAllAnimations() + shapeLayer?.add(uncheckedAnimation, forKey: "strokeEnd") + } + } + } + + lazy private var checkedAnimation: CABasicAnimation = { + let check = CABasicAnimation(keyPath: "strokeEnd") + check.timingFunction = CAMediaTimingFunction(name: .linear) + check.fillMode = .both + check.duration = 0.33 + check.fromValue = 0 + check.toValue = 1 + return check + }() + + lazy private var uncheckedAnimation: CABasicAnimation = { + let unCheck = CABasicAnimation(keyPath: "strokeEnd") + unCheck.fillMode = .both + unCheck.duration = 0.33 + unCheck.fromValue = 0 + unCheck.toValue = 1 + return unCheck + }() + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + override public init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + public convenience init() { + self.init(frame:.zero) + } + + /// There is currently no intention on using xib files. + required public init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + fatalError("xib file is not implemented for CheckBox.") + } + + public convenience init(checkedColor: UIColor, uncheckColor: UIColor, isChecked: Bool) { + self.init(frame: .zero) + isSelected = isChecked + // TODO: define the rest.... + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + override open func layoutSubviews() { + super.layoutSubviews() + drawCheck() + } + + public func setupView() { + + translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .white + lineColor = .black + lineWidth = 1.0 + + let path = UIBezierPath() + path.move(to: CGPoint(x: lineWidth / 2, y: bounds.size.height * 0.55)) + path.addLine(to: CGPoint(x: bounds.size.width * 0.45, y: bounds.size.height * 0.85)) + path.addLine(to: CGPoint(x: bounds.size.width - lineWidth / 2, y: lineWidth / 2)) + + let shapeLayer = CAShapeLayer() + self.shapeLayer = shapeLayer + shapeLayer.frame = bounds + layer.addSublayer(shapeLayer) + shapeLayer.strokeColor = lineColor.cgColor + shapeLayer.fillColor = UIColor.clear.cgColor + shapeLayer.path = path.cgPath + shapeLayer.lineJoin = .bevel + shapeLayer.lineWidth = lineWidth + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + private func drawCheck() { + + if shapeLayer == nil { + + layoutIfNeeded() + + let path = UIBezierPath() + path.move(to: CGPoint(x: lineWidth / 2, y: bounds.size.height * 0.55)) + path.addLine(to: CGPoint(x: bounds.size.width * 0.45, y: bounds.size.height * 0.85)) + path.addLine(to: CGPoint(x: bounds.size.width - lineWidth / 2, y: lineWidth / 2)) + + shapeLayer = CAShapeLayer() + shapeLayer?.frame = bounds + layer.addSublayer(shapeLayer!) + shapeLayer?.strokeColor = lineColor.cgColor + shapeLayer?.fillColor = UIColor.clear.cgColor + shapeLayer?.path = path.cgPath + shapeLayer?.lineJoin = .bevel + shapeLayer?.lineWidth = lineWidth + + CATransaction.begin() + CATransaction.setDisableActions(true) + shapeLayer?.strokeEnd = 0.0 + CATransaction.commit() + } + } + + public func updateCheckSelected(_ selected: Bool, animated: Bool) { + + DispatchQueue.main.async { + + self.isSelected = selected + + self.drawCheck() + + var layer: CAShapeLayer? + if let presentation = self.shapeLayer?.presentation(), animated { + layer = presentation + } else { + layer = self.shapeLayer + } + + if animated { + let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") + animateStrokeEnd.fillMode = .both + animateStrokeEnd.isRemovedOnCompletion = false + animateStrokeEnd.duration = 0.33 + animateStrokeEnd.fromValue = layer?.strokeEnd ?? 0.0 + animateStrokeEnd.toValue = selected ? 1 : 0 + animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) + layer?.add(animateStrokeEnd, forKey: "strokeEndAnimation") + } else { + layer?.removeAllAnimations() + CATransaction.begin() + CATransaction.setDisableActions(true) + layer?.strokeEnd = selected ? 1 : 0 + CATransaction.commit() + } + } + } + + //-------------------------------------------------- + // MARK: - Molecular + //-------------------------------------------------- + + open func needsToBeConstrained() -> Bool { + return true + } + + open func reset() { + setupView() + } + + open func setAsMolecule() { + setupView() + } + + public func updateView(_ size: CGFloat) { + + } + + public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + + guard let dictionary = json else { return } + + if let borderColor = dictionary["borderColor"] as? String { + layer.borderColor = UIColor.mfGet(forHex: borderColor).cgColor + } + + if let checkColor = dictionary["lineColor"] as? String { + _lineColor = UIColor.mfGet(forHex: checkColor) + } + + if let checkColor = dictionary["checkedColor"] as? String { + checkedColor = UIColor.mfGet(forHex: checkColor) + } + + if let unCheckedColor = dictionary["unCheckedColor"] as? String { + uncheckedColor = UIColor.mfGet(forHex: unCheckedColor) + } + + if let backroundColor = dictionary["backroundColor"] as? String { + self.backgroundColor = UIColor.mfGet(forHex: backroundColor) + } + + if let borderWidth = dictionary["borderWidth"] as? CGFloat { + _lineWidth = borderWidth + } + + if let cornerRadius = dictionary["cornerRadius"] as? CGFloat { + _cornerRadius = cornerRadius + } + } +} diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift new file mode 100644 index 00000000..609937ee --- /dev/null +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -0,0 +1,289 @@ +// +// CheckboxWithLabelView.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 9/13/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + + +@objcMembers open class CheckboxWithLabelView: ViewConstrainingView { + //-------------------------------------------------- + // MARK: - Outlets + //-------------------------------------------------- + + let checkbox = Checkbox() + let label = Label.commonLabelB2(true) + + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0) + + var checkboxWidthConstraint: NSLayoutConstraint? + var checkboxHeightConstraint: NSLayoutConstraint? + + + // Setter for the descriptionLabel.text. Also sets the accessibility text. + var labelText: String? + var labelAttributedText: NSAttributedString? + + // A block that is called when the switch is selected. + public var checkBoxAction: ((_ selected: Bool) -> ())? + + func setDescriptionAttributedText(_ descriptionAttributedText: NSAttributedString?) { + descriptionLabel?.text = nil + descriptionLabel?.attributedText = descriptionAttributedText + self.valueForAccessibilityText = descriptionAttributedText?.string + } + + var descriptionText: String { + get { return super.descriptionText } + set(descriptionText) { + descriptionLabel?.attributedText = nil + descriptionLabel.text = descriptionText + self.valueForAccessibilityText = descriptionText + } + } + + var isRequired = false + var fieldKey: String? + var delegate: DelegateObject? + + + //-------------------------------------------------- + // MARK: - Life Cycle + //-------------------------------------------------- + + override open func setupView() { + super.setupView() + + guard subviews.isEmpty else { return } + + translatesAutoresizingMaskIntoConstraints = false + + addSubview(checkbox) + + let dimension = sizeObject?.getValueBasedOnApplicationWidth() ?? Checkbox.defaultHeightWidth + + checkboxWidthConstraint = checkbox.heightAnchor.constraint(equalToConstant: dimension) + checkboxWidthConstraint?.isActive = true + checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension) + checkboxHeightConstraint?.isActive = true + + NSLayoutConstraint.constraintPinSubview(checkbox, pinTop: true, pinBottom: true, pinLeft: true, pinRight: false) + checkbox.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + checkbox.lineWidth = 2.0 + addSubview(label) + + NSLayoutConstraint.constraintPinSubview(label, pinCenterX: false, pinCenterY: true) + NSLayoutConstraint.constraintPinSubview(label, pinTop: false, pinBottom: false, pinLeft: false, pinRight: true) + + label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo).isActive = true + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + //default inits + required public init?(coder: NSCoder) { + super.init(coder: coder) + fatalError("xib file is not implemented for CheckboxWithLabelView") + } + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + addAccessibleProperties() + } + + convenience init() { + self.init(frame: .zero) + } + + init(checkedColor: UIColor?, unCheckedColor: UIColor?, text: String?) { + super.init(frame: .zero) + checkbox.checkedColor = checkedColor + checkbox.unCheckedColor = unCheckedColor + label.text = text + addAccessibleProperties() + } + + init(checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, attributedText: NSAttributedString?) { + super.init(frame: .zero) + setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: nil) + descriptionAttributedText = attributedText + addAccessibleProperties() + } + + init?(checkMarkView: MVMCoreUICheckMarkView?, checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, text: String?) { + super.init(frame: .zero) + checkMark = checkMarkView + setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: text) + addAccessibleProperties() + } + + init?(checkMarkView: MVMCoreUICheckMarkView?, checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, attributedText: NSAttributedString?) { + super.init(frame: .zero) + checkMark = checkMarkView + setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: nil) + descriptionAttributedText = attributedText + addAccessibleProperties() + } + + init(roundRect isRoundRect: Bool) { + super.init(frame: .zero) + + isRoundRectCheckMark = isRoundRect + setup(withCheckedColor: .white, unCheck: .white, text: nil) + addAccessibleProperties() + setCheckMarkLayer() + } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return CGFloat(CheckBoxHeightWidth) + } + + // MARK: - control methods + func setSelected(_ selected: Bool) { + setSelected(selected, animated: true) + checkbox?.is + } + + func setSelected(_ selected: Bool, animated: Bool) { + setSelected(selected, animated: animated, runBlock: true) + } + + func setSelected(_ selected: Bool, animated: Bool, runBlock: Bool) { + addAccessibilityLabel(selected) + + isSelected = selected + if (switchSelected != nil) && runBlock { + switchSelected(selected) + } + + if selected { + UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { + self.checkedSquare.backgroundColor = self.checkedColor + }) + checkMark.updateCheckSelected(true, animated: animated) + } else { + UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { + self.checkedSquare.backgroundColor = self.unCheckedColor + }) + checkMark?.updateCheckSelected(false, animated: animated) + } + + if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { + let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator + formValidator?.enableByValidation() + } + } + + //-------------------------------------------------- + // MARK: - UITouch + //-------------------------------------------------- + + func touchesEnded(_ touches: Set, with event: UIEvent) { + + if touchIsOutside(touches.first) { + sendActions(for: .touchUpOutside) + } else { + self.selected = !isSelected() + sendActions(for: .touchUpInside) + } + } + + func touchIsOutside(_ touch: UITouch?) -> Bool { + let endLocation = touch?.location(in: self) + let x = endLocation?.x ?? 0.0 + let y = endLocation?.y ?? 0.0 + let faultTolerance: CGFloat = 20.0 + let widthLimit = CGFloat(frame.size.width + faultTolerance) + let heightLimt = CGFloat(frame.size.height + faultTolerance) + + return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt + } +} + +// MARK: - Molecular +extension CheckboxWithLabelView { + + @objc override open func updateView(_ size: CGFloat) { + + DispatchQueue.main.async { + self.label.updateView(size) + if self.checkbox.responds(to: #selector(self.updateView(_:))) { + if let dimension = self.sizeObject?.getValueBased(onSize: size) { + self.checkboxWidthConstraint?.constant = dimension + self.checkboxHeightConstraint?.constant = dimension + self.checkbox.updateView(size) + } + } + } + } + + override open func alignment() -> UIStackView.Alignment { + return .leading + } + + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + delegate = delegateObject + FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) + + guard let dictionary = json else { return } + + fieldKey = dictionary.string(for: KeyFieldKey) + isRequired = dictionary.bool(forKey: KeyRequired) + + checkbox.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + label.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + } +} + +// MARK:- FormValidationProtocol +extension CheckboxWithLabelView: FormValidationProtocol { + + public func isValidField() -> Bool { + return isRequired ? checkbox.isSelected : true + } + + public func formFieldName() -> String? { + return fieldKey + } + + public func formFieldValue() -> Any? { + return NSNumber(value: checkbox.isSelected) + } + + +} + +// MARK:- Accessibility +extension CheckboxWithLabelView { + + func addAccessibleProperties() { + accessibilityTraits = .none + accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") + } + + func addAccessibilityLabel(_ selected: Bool) { + let state = selected ? MVMCoreUIUtility.hardcodedString(withKey: "checkbox_checked_state") : MVMCoreUIUtility.hardcodedString(withKey: "checkbox_unchecked_state") + let description = accessibilityText.length ? accessibilityText : "" + accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state"), description, state) + } + + func hideDescriptionLabelAndPinCheckboxToRight() { + + descriptionLabel?.hidden = true + checkboxRightPinConstraint.active = true + descriptionLabelLeadingConstraint.constant = 0 + } +} diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 945eb7bf..aeaf6b8d 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -38,11 +38,11 @@ @"caretButton": CaretButton.class, @"textField" : MFTextField.class, @"digitTextField" : MFDigitTextField.class, - @"checkbox" : MVMCoreUICheckBox.class, + @"checkbox" : Checkbox.class, @"cornerLabels" : CornerLabels.class, @"progressBar": ProgressBar.class, @"multiProgressBar": MultiProgress.class, - @"checkbox": MVMCoreUICheckBox.class, + @"checkboxWithLabelView": CheckboxWithLabelView.class, @"listItem": MoleculeTableViewCell.class, @"accordionListItem": AccordionMoleculeTableViewCell.class, @"switch": MVMCoreUISwitch.class, From 38c74acc6e86acbd8ea4e3631af9d4c1cd2ca2b4 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 25 Sep 2019 14:43:29 -0400 Subject: [PATCH 04/14] moar. --- MVMCoreUI/Atoms/Views/Checkbox.swift | 8 +++---- .../Atoms/Views/CheckboxWithLabelView.swift | 23 +++---------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index 08f6227f..f29940f6 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -31,16 +31,16 @@ import MVMCore public var checkedColor: UIColor = .black public var unCheckedColor: UIColor = .mfPaleGrey() - public var hasRoundBorder = false + public var hasRoundCorners = false private var shapeLayer: CAShapeLayer? private var _lineColor: UIColor = .black private var _borderColor: UIColor = .black - private var _lineWidth: CGFloat = 1.0 - private var _cornerRadius: CGFloat = 5.0 + private var _lineWidth: CGFloat = 1 + private var _cornerRadius: CGFloat = 0 public func setCheckMarkLayer() { - checkedSquare.layer.cornerRadius = checkbox.isRoundRectCheckMark ? 5.0 : 0 + layer.cornerRadius = hasRoundCorners ? 5 : 0 } public var lineWidth: CGFloat { diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index 609937ee..62589b43 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -114,29 +114,14 @@ init(checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, attributedText: NSAttributedString?) { super.init(frame: .zero) setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: nil) - descriptionAttributedText = attributedText + setDescriptionAttributedText = attributedText addAccessibleProperties() } - init?(checkMarkView: MVMCoreUICheckMarkView?, checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, text: String?) { - super.init(frame: .zero) - checkMark = checkMarkView - setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: text) - addAccessibleProperties() - } - - init?(checkMarkView: MVMCoreUICheckMarkView?, checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, attributedText: NSAttributedString?) { - super.init(frame: .zero) - checkMark = checkMarkView - setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: nil) - descriptionAttributedText = attributedText - addAccessibleProperties() - } - - init(roundRect isRoundRect: Bool) { + init(hasRoundedCheckbox: Bool) { super.init(frame: .zero) - isRoundRectCheckMark = isRoundRect + checkbox.hasRoundBorder = hasRoundRectCheckbox setup(withCheckedColor: .white, unCheck: .white, text: nil) addAccessibleProperties() setCheckMarkLayer() @@ -262,8 +247,6 @@ extension CheckboxWithLabelView: FormValidationProtocol { public func formFieldValue() -> Any? { return NSNumber(value: checkbox.isSelected) } - - } // MARK:- Accessibility From c75337bfdf03461953defbc53b36be884e10471d Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 26 Sep 2019 11:42:16 -0400 Subject: [PATCH 05/14] Getting closer to testing phase. --- MVMCoreUI/Atoms/Views/Checkbox.swift | 154 +++++++--------- .../Atoms/Views/CheckboxWithLabelView.swift | 164 +++++++----------- 2 files changed, 130 insertions(+), 188 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index f29940f6..4408ea6d 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -15,67 +15,27 @@ import MVMCore //-------------------------------------------------- public static let defaultHeightWidth: CGFloat = 18.0 - /* - //Offsets based on the 124x124 example checkmark - let startXOffset: Float = 42.0 / 124.0 - let startYOffset: Float = 66.0 / 124.0 - let pivotXOffset: Float = 58.0 / 124.0 - let pivotYOffset: Float = 80.0 / 124.0 - let endXOffset: Float = 83.0 / 124.0 - let endYOffset: Float = 46.0 / 124.0 - let pivotPercentage: Float = 0.34 - let endPercentage = 1.0 - pivotPercentage - let animationInterval: Float = 0.01 - */ + /// The color of the box and line when checked. public var checkedColor: UIColor = .black - public var unCheckedColor: UIColor = .mfPaleGrey() + /// The color of the border when unChecked. + public var unCheckedColor: UIColor = .black + + /// If true the border of this checkbox will be circular. public var hasRoundCorners = false + // Internal values to manage the appearance of the checkbox. private var shapeLayer: CAShapeLayer? - private var _lineColor: UIColor = .black - private var _borderColor: UIColor = .black - private var _lineWidth: CGFloat = 1 - private var _cornerRadius: CGFloat = 0 - public func setCheckMarkLayer() { - layer.cornerRadius = hasRoundCorners ? 5 : 0 + public var cornerRadiusValue: CGFloat { + return bounds.height / 2.0 } - public var lineWidth: CGFloat { - get { return _lineWidth } - set (newLineWidth) { - _lineWidth = newLineWidth - if shapeLayer != nil { - shapeLayer?.removeFromSuperlayer() - shapeLayer = nil - updateCheckSelected(isSelected, animated: false) - } - } - } - - public var lineColor: UIColor { - get { return _lineColor } - set (newLineColor) { - _lineColor = newLineColor - if let shapeLayer = shapeLayer { - shapeLayer.strokeColor = lineColor.cgColor - updateCheckSelected(isSelected, animated: false) - } - } - } - - public var borderColor: UIColor { - get { return _borderColor } - set (newBorderColor) { - _borderColor = newBorderColor - if let shapeLayer = shapeLayer { - shapeLayer.strokeColor = borderColor.cgColor - updateCheckSelected(isSelected, animated: false) - } - } - } + public var lineWidth: CGFloat = 1 + public var lineColor: UIColor = .black + public var borderColor: UIColor = .black + private var checkedBackgroundColor: UIColor = .white override open var isSelected: Bool { didSet { @@ -104,6 +64,7 @@ import MVMCore lazy private var uncheckedAnimation: CABasicAnimation = { let unCheck = CABasicAnimation(keyPath: "strokeEnd") + unCheck.timingFunction = CAMediaTimingFunction(name: .linear) unCheck.fillMode = .both unCheck.duration = 0.33 unCheck.fromValue = 0 @@ -120,20 +81,21 @@ import MVMCore setupView() } - public convenience init() { - self.init(frame:.zero) - } - /// There is currently no intention on using xib files. required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) fatalError("xib file is not implemented for CheckBox.") } - public convenience init(checkedColor: UIColor, uncheckColor: UIColor, isChecked: Bool) { + public convenience init() { + self.init(frame:.zero) + } + + public convenience init(checkedColor: UIColor, unCheckedColor: UIColor, isChecked: Bool = false) { self.init(frame: .zero) isSelected = isChecked - // TODO: define the rest.... + self.checkedColor = checkedColor + self.unCheckedColor = unCheckedColor } //-------------------------------------------------- @@ -149,8 +111,6 @@ import MVMCore translatesAutoresizingMaskIntoConstraints = false backgroundColor = .white - lineColor = .black - lineWidth = 1.0 let path = UIBezierPath() path.move(to: CGPoint(x: lineWidth / 2, y: bounds.size.height * 0.55)) @@ -172,8 +132,16 @@ import MVMCore // MARK: - Methods //-------------------------------------------------- - private func drawCheck() { + open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { + print("Action initiated") + } + open override func sendActions(for controlEvents: UIControl.Event) { + print("Actions Inititaled") + } + + private func drawCheck() { + if shapeLayer == nil { layoutIfNeeded() @@ -199,12 +167,27 @@ import MVMCore } } - public func updateCheckSelected(_ selected: Bool, animated: Bool) { + /* + //Offsets based on the 124x124 example checkmark + let startXOffset: Float = 42.0 / 124.0 + let startYOffset: Float = 66.0 / 124.0 + let pivotXOffset: Float = 58.0 / 124.0 + let pivotYOffset: Float = 80.0 / 124.0 + let endXOffset: Float = 83.0 / 124.0 + let endYOffset: Float = 46.0 / 124.0 + let pivotPercentage: Float = 0.34 + let endPercentage = 1.0 - pivotPercentage + let animationInterval: Float = 0.01 + */ + + public func updateCheckboxSelection(_ selected: Bool, animated: Bool) { + + shapeLayer?.removeFromSuperlayer() + shapeLayer = nil DispatchQueue.main.async { self.isSelected = selected - self.drawCheck() var layer: CAShapeLayer? @@ -227,12 +210,13 @@ import MVMCore layer?.removeAllAnimations() CATransaction.begin() CATransaction.setDisableActions(true) + CATransaction.setAnimationDuration(0) layer?.strokeEnd = selected ? 1 : 0 CATransaction.commit() } } } - + //-------------------------------------------------- // MARK: - Molecular //-------------------------------------------------- @@ -250,39 +234,35 @@ import MVMCore } public func updateView(_ size: CGFloat) { - + // TODO: Ensure the check logic is resized. } public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { guard let dictionary = json else { return } - if let borderColor = dictionary["borderColor"] as? String { - layer.borderColor = UIColor.mfGet(forHex: borderColor).cgColor + if let borderColorHex = dictionary["borderColor"] as? String { + layer.borderColor = UIColor.mfGet(forHex: borderColorHex).cgColor } - if let checkColor = dictionary["lineColor"] as? String { - _lineColor = UIColor.mfGet(forHex: checkColor) - } - - if let checkColor = dictionary["checkedColor"] as? String { - checkedColor = UIColor.mfGet(forHex: checkColor) - } - - if let unCheckedColor = dictionary["unCheckedColor"] as? String { - uncheckedColor = UIColor.mfGet(forHex: unCheckedColor) - } - - if let backroundColor = dictionary["backroundColor"] as? String { - self.backgroundColor = UIColor.mfGet(forHex: backroundColor) - } - if let borderWidth = dictionary["borderWidth"] as? CGFloat { - _lineWidth = borderWidth + layer.borderWidth = borderWidth } - - if let cornerRadius = dictionary["cornerRadius"] as? CGFloat { - _cornerRadius = cornerRadius + + if let checkColorHex = dictionary["lineColor"] as? String { + lineColor = UIColor.mfGet(forHex: checkColorHex) + } + + if let checkColorHex = dictionary["checkedColor"] as? String { + checkedColor = UIColor.mfGet(forHex: checkColorHex) + } + + if let unCheckedColorHex = dictionary["unCheckedColor"] as? String { + unCheckedColor = UIColor.mfGet(forHex: unCheckedColorHex) + } + + if let backroundColorHex = dictionary["backroundColor"] as? String { + backgroundColor = UIColor.mfGet(forHex: backroundColorHex) } } } diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index 62589b43..93f8a894 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -24,34 +24,13 @@ var checkboxWidthConstraint: NSLayoutConstraint? var checkboxHeightConstraint: NSLayoutConstraint? - - // Setter for the descriptionLabel.text. Also sets the accessibility text. - var labelText: String? - var labelAttributedText: NSAttributedString? - // A block that is called when the switch is selected. - public var checkBoxAction: ((_ selected: Bool) -> ())? - - func setDescriptionAttributedText(_ descriptionAttributedText: NSAttributedString?) { - descriptionLabel?.text = nil - descriptionLabel?.attributedText = descriptionAttributedText - self.valueForAccessibilityText = descriptionAttributedText?.string - } - - var descriptionText: String { - get { return super.descriptionText } - set(descriptionText) { - descriptionLabel?.attributedText = nil - descriptionLabel.text = descriptionText - self.valueForAccessibilityText = descriptionText - } - } + public var checkboxAction: ((_ selected: Bool) -> ())? var isRequired = false var fieldKey: String? var delegate: DelegateObject? - //-------------------------------------------------- // MARK: - Life Cycle //-------------------------------------------------- @@ -66,7 +45,6 @@ addSubview(checkbox) let dimension = sizeObject?.getValueBasedOnApplicationWidth() ?? Checkbox.defaultHeightWidth - checkboxWidthConstraint = checkbox.heightAnchor.constraint(equalToConstant: dimension) checkboxWidthConstraint?.isActive = true checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension) @@ -75,11 +53,10 @@ NSLayoutConstraint.constraintPinSubview(checkbox, pinTop: true, pinBottom: true, pinLeft: true, pinRight: false) checkbox.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true checkbox.lineWidth = 2.0 + addSubview(label) - - NSLayoutConstraint.constraintPinSubview(label, pinCenterX: false, pinCenterY: true) - NSLayoutConstraint.constraintPinSubview(label, pinTop: false, pinBottom: false, pinLeft: false, pinRight: true) - + label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo).isActive = true } @@ -87,111 +64,99 @@ // MARK: - Initializers //-------------------------------------------------- - //default inits required public init?(coder: NSCoder) { super.init(coder: coder) fatalError("xib file is not implemented for CheckboxWithLabelView") } - override init(frame: CGRect) { + override public init(frame: CGRect) { super.init(frame: frame) setupView() - addAccessibleProperties() +// addAccessibleProperties() } - convenience init() { + public convenience init() { self.init(frame: .zero) } - init(checkedColor: UIColor?, unCheckedColor: UIColor?, text: String?) { - super.init(frame: .zero) + public convenience init(checkedColor: UIColor, unCheckedColor: UIColor, text: String?, isChecked: Bool = false) { + self.init(frame: .zero) + checkbox.checkedColor = checkedColor checkbox.unCheckedColor = unCheckedColor label.text = text - addAccessibleProperties() } - init(checkedColor: UIColor?, unCheck unCheckedColor: UIColor?, attributedText: NSAttributedString?) { - super.init(frame: .zero) - setup(withCheckedColor: checkedColor, unCheck: unCheckedColor, text: nil) - setDescriptionAttributedText = attributedText - addAccessibleProperties() - } - - init(hasRoundedCheckbox: Bool) { - super.init(frame: .zero) + public convenience init(checkedColor: UIColor, unCheck unCheckedColor: UIColor, attributedText: NSAttributedString, isChecked: Bool = false) { + self.init(frame: .zero) - checkbox.hasRoundBorder = hasRoundRectCheckbox - setup(withCheckedColor: .white, unCheck: .white, text: nil) - addAccessibleProperties() - setCheckMarkLayer() + checkbox.checkedColor = checkedColor + checkbox.unCheckedColor = unCheckedColor + label.attributedText = attributedText + } + + public convenience init(isRoundedCheckbox: Bool) { + self.init(frame: .zero) + + checkbox.hasRoundCorners = isRoundedCheckbox } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - override class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return CGFloat(CheckBoxHeightWidth) + override open class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return CGFloat(Checkbox.defaultHeightWidth) } - // MARK: - control methods - func setSelected(_ selected: Bool) { - setSelected(selected, animated: true) - checkbox?.is - } - - func setSelected(_ selected: Bool, animated: Bool) { - setSelected(selected, animated: animated, runBlock: true) - } - - func setSelected(_ selected: Bool, animated: Bool, runBlock: Bool) { - addAccessibilityLabel(selected) + @objc public func checkboxTapped(checkbox: Checkbox) { +// addAccessibilityLabel(selected) - isSelected = selected - if (switchSelected != nil) && runBlock { - switchSelected(selected) + checkbox.isSelected.toggle() + + if let checkboxAction = checkboxAction { + checkboxAction(checkbox.isSelected) } - if selected { - UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { - self.checkedSquare.backgroundColor = self.checkedColor - }) - checkMark.updateCheckSelected(true, animated: animated) - } else { - UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { - self.checkedSquare.backgroundColor = self.unCheckedColor - }) - checkMark?.updateCheckSelected(false, animated: animated) - } +// if checkbox.isSelected { +// UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { +// self.checkbox.backgroundColor = self.checkedColor +// }) +// checkbox.updateCheckSelected(true, animated: animated) +// } else { +// UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { +// self.checkbox.backgroundColor = self.unCheckedColor +// }) +// +// checkbox.updateCheckSelected(false, animated: animated) +// } - if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { - let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator - formValidator?.enableByValidation() - } + // if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { + // let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator + // formValidator?.enableByValidation() + // } } - + //-------------------------------------------------- // MARK: - UITouch //-------------------------------------------------- func touchesEnded(_ touches: Set, with event: UIEvent) { - if touchIsOutside(touches.first) { - sendActions(for: .touchUpOutside) + if touchIsAcceptablyOutside(touches.first) { + checkbox.sendActions(for: .touchUpOutside) } else { - self.selected = !isSelected() - sendActions(for: .touchUpInside) + checkbox.sendActions(for: .touchUpInside) } } - func touchIsOutside(_ touch: UITouch?) -> Bool { + func touchIsAcceptablyOutside(_ touch: UITouch?) -> Bool { let endLocation = touch?.location(in: self) let x = endLocation?.x ?? 0.0 let y = endLocation?.y ?? 0.0 let faultTolerance: CGFloat = 20.0 - let widthLimit = CGFloat(frame.size.width + faultTolerance) - let heightLimt = CGFloat(frame.size.height + faultTolerance) + let widthLimit = CGFloat(bounds.size.width + faultTolerance) + let heightLimt = CGFloat(bounds.size.height + faultTolerance) return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt } @@ -225,8 +190,13 @@ extension CheckboxWithLabelView { guard let dictionary = json else { return } - fieldKey = dictionary.string(for: KeyFieldKey) - isRequired = dictionary.bool(forKey: KeyRequired) + if let fieldKey = dictionary[KeyFieldKey] as? String { + self.fieldKey = fieldKey + } + + if let isRequired = dictionary[KeyRequired] as? Bool { + self.isRequired = isRequired + } checkbox.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) label.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) @@ -253,20 +223,12 @@ extension CheckboxWithLabelView: FormValidationProtocol { extension CheckboxWithLabelView { func addAccessibleProperties() { - accessibilityTraits = .none - accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") +// accessibilityTraits = .none +// accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") } func addAccessibilityLabel(_ selected: Bool) { - let state = selected ? MVMCoreUIUtility.hardcodedString(withKey: "checkbox_checked_state") : MVMCoreUIUtility.hardcodedString(withKey: "checkbox_unchecked_state") - let description = accessibilityText.length ? accessibilityText : "" - accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state"), description, state) - } - - func hideDescriptionLabelAndPinCheckboxToRight() { - - descriptionLabel?.hidden = true - checkboxRightPinConstraint.active = true - descriptionLabelLeadingConstraint.constant = 0 +// let state = selected ? MVMCoreUIUtility.hardcodedString(withKey: "checkbox_checked_state") : MVMCoreUIUtility.hardcodedString(withKey: "checkbox_unchecked_state") +// accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state"), label.text ?? "", state) } } From afcf8d535ba987f9ddfd87ab32acbb7e159749e1 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 26 Sep 2019 13:56:02 -0400 Subject: [PATCH 06/14] updates. --- MVMCoreUI/Atoms/Views/Checkbox.swift | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index 4408ea6d..1ab976ef 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -29,13 +29,13 @@ import MVMCore private var shapeLayer: CAShapeLayer? public var cornerRadiusValue: CGFloat { - return bounds.height / 2.0 + return bounds.height / 2 } public var lineWidth: CGFloat = 1 public var lineColor: UIColor = .black public var borderColor: UIColor = .black - private var checkedBackgroundColor: UIColor = .white + public var checkedBackgroundColor: UIColor = .white override open var isSelected: Bool { didSet { @@ -129,7 +129,7 @@ import MVMCore } //-------------------------------------------------- - // MARK: - Methods + // MARK: - Action //-------------------------------------------------- open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { @@ -140,6 +140,10 @@ import MVMCore print("Actions Inititaled") } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + private func drawCheck() { if shapeLayer == nil { @@ -169,6 +173,7 @@ import MVMCore /* //Offsets based on the 124x124 example checkmark + let startXOffset: Float = 42.0 / 124.0 let startYOffset: Float = 66.0 / 124.0 let pivotXOffset: Float = 58.0 / 124.0 @@ -202,7 +207,7 @@ import MVMCore animateStrokeEnd.fillMode = .both animateStrokeEnd.isRemovedOnCompletion = false animateStrokeEnd.duration = 0.33 - animateStrokeEnd.fromValue = layer?.strokeEnd ?? 0.0 + animateStrokeEnd.fromValue = layer?.strokeEnd ?? 0 animateStrokeEnd.toValue = selected ? 1 : 0 animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) layer?.add(animateStrokeEnd, forKey: "strokeEndAnimation") From f5f994648aca33f941fead0178d8ed4fed149c91 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 27 Sep 2019 13:43:02 -0400 Subject: [PATCH 07/14] Decent working state with animation and touch. --- MVMCoreUI/Atoms/Views/Checkbox.swift | 270 ++++++++++++------ .../Atoms/Views/CheckboxWithLabelView.swift | 101 ++----- 2 files changed, 212 insertions(+), 159 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index 1ab976ef..d00c2463 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -16,36 +16,54 @@ import MVMCore public static let defaultHeightWidth: CGFloat = 18.0 - /// The color of the box and line when checked. - public var checkedColor: UIColor = .black + /// The color of the background when checked. + public var checkedBackgroundColor: UIColor = .white - /// The color of the border when unChecked. - public var unCheckedColor: UIColor = .black + /// The color of the background when unChecked. + public var unCheckedBackgroundColor: UIColor = .black /// If true the border of this checkbox will be circular. public var hasRoundCorners = false + // Action Block called when the switch is selected. + public var actionBlock: ActionBlock? + // Internal values to manage the appearance of the checkbox. private var shapeLayer: CAShapeLayer? public var cornerRadiusValue: CGFloat { - return bounds.height / 2 + return bounds.size.height / 2 } - public var lineWidth: CGFloat = 1 + public var lineWidth: CGFloat = 2 public var lineColor: UIColor = .black - public var borderColor: UIColor = .black - public var checkedBackgroundColor: UIColor = .white + + open var borderColor: UIColor { + get { + guard let color = layer.borderColor else { return .black } + return UIColor(cgColor: color) + } + set (newColor) { + layer.borderColor = newColor.cgColor + } + } + open var borderWidth: CGFloat { + get { return layer.borderWidth } + set (newWidth) { + layer.borderWidth = newWidth + } + } + override open var isSelected: Bool { didSet { if isSelected { - layer.addSublayer(shapeLayer!) - shapeLayer?.strokeEnd = 1 +// layer.addSublayer(shapeLayer!) +// shapeLayer?.strokeEnd = 1 shapeLayer?.removeAllAnimations() shapeLayer?.add(checkedAnimation, forKey: "strokeEnd") } else { - shapeLayer?.strokeEnd = 0 +// shapeLayer?.strokeEnd = 0 shapeLayer?.removeAllAnimations() shapeLayer?.add(uncheckedAnimation, forKey: "strokeEnd") } @@ -55,8 +73,9 @@ import MVMCore lazy private var checkedAnimation: CABasicAnimation = { let check = CABasicAnimation(keyPath: "strokeEnd") check.timingFunction = CAMediaTimingFunction(name: .linear) + check.isRemovedOnCompletion = false check.fillMode = .both - check.duration = 0.33 + check.duration = 0.3 check.fromValue = 0 check.toValue = 1 return check @@ -65,10 +84,11 @@ import MVMCore lazy private var uncheckedAnimation: CABasicAnimation = { let unCheck = CABasicAnimation(keyPath: "strokeEnd") unCheck.timingFunction = CAMediaTimingFunction(name: .linear) + unCheck.isRemovedOnCompletion = false unCheck.fillMode = .both - unCheck.duration = 0.33 - unCheck.fromValue = 0 - unCheck.toValue = 1 + unCheck.duration = 0.3 + unCheck.fromValue = 1 + unCheck.toValue = 0 return unCheck }() @@ -84,7 +104,7 @@ import MVMCore /// There is currently no intention on using xib files. required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) - fatalError("xib file is not implemented for CheckBox.") + fatalError("xib file is not implemented for Checkbox.") } public convenience init() { @@ -94,8 +114,8 @@ import MVMCore public convenience init(checkedColor: UIColor, unCheckedColor: UIColor, isChecked: Bool = false) { self.init(frame: .zero) isSelected = isChecked - self.checkedColor = checkedColor - self.unCheckedColor = unCheckedColor + self.checkedBackgroundColor = checkedColor + self.unCheckedBackgroundColor = unCheckedColor } //-------------------------------------------------- @@ -109,35 +129,31 @@ import MVMCore public func setupView() { + isUserInteractionEnabled = true translatesAutoresizingMaskIntoConstraints = false backgroundColor = .white - - let path = UIBezierPath() - path.move(to: CGPoint(x: lineWidth / 2, y: bounds.size.height * 0.55)) - path.addLine(to: CGPoint(x: bounds.size.width * 0.45, y: bounds.size.height * 0.85)) - path.addLine(to: CGPoint(x: bounds.size.width - lineWidth / 2, y: lineWidth / 2)) - - let shapeLayer = CAShapeLayer() - self.shapeLayer = shapeLayer - shapeLayer.frame = bounds - layer.addSublayer(shapeLayer) - shapeLayer.strokeColor = lineColor.cgColor - shapeLayer.fillColor = UIColor.clear.cgColor - shapeLayer.path = path.cgPath - shapeLayer.lineJoin = .bevel - shapeLayer.lineWidth = lineWidth + layer.borderWidth = 1 + layer.borderColor = UIColor.black.cgColor } //-------------------------------------------------- - // MARK: - Action + // MARK: - Actions //-------------------------------------------------- open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { - print("Action initiated") + super.sendAction(action, to: target, for: event) + + isSelected.toggle() + actionBlock?() + drawCheck() } - + open override func sendActions(for controlEvents: UIControl.Event) { - print("Actions Inititaled") + super.sendActions(for: controlEvents) + + isSelected.toggle() + actionBlock?() + drawCheck() } //-------------------------------------------------- @@ -147,54 +163,62 @@ import MVMCore private func drawCheck() { if shapeLayer == nil { - - layoutIfNeeded() - - let path = UIBezierPath() - path.move(to: CGPoint(x: lineWidth / 2, y: bounds.size.height * 0.55)) - path.addLine(to: CGPoint(x: bounds.size.width * 0.45, y: bounds.size.height * 0.85)) - path.addLine(to: CGPoint(x: bounds.size.width - lineWidth / 2, y: lineWidth / 2)) - - shapeLayer = CAShapeLayer() - shapeLayer?.frame = bounds - layer.addSublayer(shapeLayer!) - shapeLayer?.strokeColor = lineColor.cgColor - shapeLayer?.fillColor = UIColor.clear.cgColor - shapeLayer?.path = path.cgPath - shapeLayer?.lineJoin = .bevel - shapeLayer?.lineWidth = lineWidth - - CATransaction.begin() - CATransaction.setDisableActions(true) - shapeLayer?.strokeEnd = 0.0 - CATransaction.commit() + + let shapeLayer = CAShapeLayer() + self.shapeLayer = shapeLayer + shapeLayer.frame = bounds + layer.addSublayer(shapeLayer) + shapeLayer.strokeColor = lineColor.cgColor + shapeLayer.fillColor = UIColor.clear.cgColor + shapeLayer.path = checkMarkBezierPath.cgPath + shapeLayer.lineJoin = .bevel + shapeLayer.lineWidth = lineWidth + + CATransaction.withDisabledAnimations { + shapeLayer.strokeEnd = 0.0 + } } } /* - //Offsets based on the 124x124 example checkmark - - let startXOffset: Float = 42.0 / 124.0 - let startYOffset: Float = 66.0 / 124.0 - let pivotXOffset: Float = 58.0 / 124.0 - let pivotYOffset: Float = 80.0 / 124.0 - let endXOffset: Float = 83.0 / 124.0 - let endYOffset: Float = 46.0 / 124.0 + // Offsets based on the 124x124 example checkmark + let startXOffset: Float = 42.0 / 124.0 ~~ 0.33871 + let startYOffset: Float = 66.0 / 124.0 ~~ 0.53225 + let pivotXOffset: Float = 58.0 / 124.0 ~~ 0.46774 + let pivotYOffset: Float = 80.0 / 124.0 ~~ 0.64516 + let endXOffset: Float = 83.0 / 124.0 ~~ 0.66935 + let endYOffset: Float = 46.0 / 124.0 ~~ 0.37097 let pivotPercentage: Float = 0.34 let endPercentage = 1.0 - pivotPercentage let animationInterval: Float = 0.01 */ - public func updateCheckboxSelection(_ selected: Bool, animated: Bool) { + /// Returns a UIBezierPath detailing the path of a checkmark + var checkMarkBezierPath: UIBezierPath { - shapeLayer?.removeFromSuperlayer() - shapeLayer = nil + let sideLength = bounds.size.height + let startPoint = CGPoint(x: 0.33871 * sideLength, y: 0.53225 * sideLength) + let pivotOffSet = CGPoint(x: 0.46774 * sideLength, y: 0.64516 * sideLength) + let endOffset = CGPoint(x: 0.66935 * sideLength , y: 0.37097 * sideLength) + + let path = UIBezierPath() + path.move(to: startPoint) + path.addLine(to: pivotOffSet) + path.addLine(to: endOffset) + + return path + } + + public func updateSelection(_ selected: Bool, animated: Bool) { + +// shapeLayer?.removeFromSuperlayer() +// shapeLayer = nil DispatchQueue.main.async { self.isSelected = selected self.drawCheck() - + var layer: CAShapeLayer? if let presentation = self.shapeLayer?.presentation(), animated { layer = presentation @@ -204,24 +228,93 @@ import MVMCore if animated { let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") + animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) + animateStrokeEnd.duration = 0.33 animateStrokeEnd.fillMode = .both animateStrokeEnd.isRemovedOnCompletion = false - animateStrokeEnd.duration = 0.33 animateStrokeEnd.fromValue = layer?.strokeEnd ?? 0 animateStrokeEnd.toValue = selected ? 1 : 0 - animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) layer?.add(animateStrokeEnd, forKey: "strokeEndAnimation") } else { layer?.removeAllAnimations() - CATransaction.begin() - CATransaction.setDisableActions(true) - CATransaction.setAnimationDuration(0) - layer?.strokeEnd = selected ? 1 : 0 - CATransaction.commit() + CATransaction.withDisabledAnimations { + layer?.strokeEnd = selected ? 1 : 0 + } } } } + //-------------------------------------------------- + // MARK: - UITouch + //-------------------------------------------------- + + open override func touchesEnded(_ touches: Set, with event: UIEvent?) { + + if touchIsAcceptablyOutside(touches.first) { + sendActions(for: .touchUpOutside) + } else { + sendActions(for: .touchUpInside) + } + } + + func touchIsAcceptablyOutside(_ touch: UITouch?) -> Bool { + let endLocation = touch?.location(in: self) + let x = endLocation?.x ?? 0.0 + let y = endLocation?.y ?? 0.0 + let faultTolerance: CGFloat = 20.0 + let widthLimit = CGFloat(bounds.size.width + faultTolerance) + let heightLimt = CGFloat(bounds.size.height + faultTolerance) + + return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt + } + + // if checkbox.isSelected { + // UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { + // self.checkbox.backgroundColor = self.checkedColor + // }) + // checkbox.updateCheckSelected(true, animated: animated) + // } else { + // UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { + // self.checkbox.backgroundColor = self.unCheckedColor + // }) + // + // checkbox.updateCheckSelected(false, animated: animated) + // } + + // if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { + // let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator + // formValidator?.enableByValidation() + // } + + + /* + + - (void)setSelected:(BOOL)selected animated:(BOOL)animated runBlock:(BOOL)runBlock { + [self addAccessibilityLabel:selected]; + + self.isSelected = selected; + if (self.switchSelected && runBlock) { + self.switchSelected(selected); + } + if (selected) { + [UIView animateWithDuration:0.2 delay:0.1 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.checkedSquare.backgroundColor = self.checkedColor; + } completion:nil]; + [self.checkMark updateCheckSelected:YES animated:animated]; + } else { + [UIView animateWithDuration:0.2 delay:0.1 options:UIViewAnimationOptionCurveEaseOut animations:^{ + self.checkedSquare.backgroundColor = self.unCheckedColor; + } completion:nil]; + [self.checkMark updateCheckSelected:NO animated:animated]; + } + + if (self.delegate && [self.delegate respondsToSelector:@selector(formValidationProtocol)] && [[self.delegate performSelector:@selector(formValidationProtocol)] respondsToSelector:@selector(formValidatorModel)]) { + FormValidator *formValidator = [[self.delegate performSelector:@selector(formValidationProtocol)] performSelector:@selector(formValidatorModel)]; + [formValidator enableByValidation]; + } + } + */ + //-------------------------------------------------- // MARK: - Molecular //-------------------------------------------------- @@ -239,6 +332,7 @@ import MVMCore } public func updateView(_ size: CGFloat) { + // TODO: Ensure the check logic is resized. } @@ -259,15 +353,27 @@ import MVMCore } if let checkColorHex = dictionary["checkedColor"] as? String { - checkedColor = UIColor.mfGet(forHex: checkColorHex) + checkedBackgroundColor = UIColor.mfGet(forHex: checkColorHex) } if let unCheckedColorHex = dictionary["unCheckedColor"] as? String { - unCheckedColor = UIColor.mfGet(forHex: unCheckedColorHex) + unCheckedBackgroundColor = UIColor.mfGet(forHex: unCheckedColorHex) } - if let backroundColorHex = dictionary["backroundColor"] as? String { - backgroundColor = UIColor.mfGet(forHex: backroundColorHex) - } +// if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { +// actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } +// } + } +} + +// TODO: Move to its own extension file. +extension CATransaction { + + /// Performs changes without activating animation actions. + static func withDisabledAnimations(_ actionBlock: ActionBlock) { + CATransaction.begin() + CATransaction.setDisableActions(true) + actionBlock() + CATransaction.commit() } } diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index 93f8a894..94cd86e2 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -21,16 +21,17 @@ var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0) - var checkboxWidthConstraint: NSLayoutConstraint? - var checkboxHeightConstraint: NSLayoutConstraint? - - // A block that is called when the switch is selected. - public var checkboxAction: ((_ selected: Bool) -> ())? - var isRequired = false var fieldKey: String? var delegate: DelegateObject? + + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + var checkboxWidthConstraint: NSLayoutConstraint? + var checkboxHeightConstraint: NSLayoutConstraint? + //-------------------------------------------------- // MARK: - Life Cycle //-------------------------------------------------- @@ -43,6 +44,7 @@ translatesAutoresizingMaskIntoConstraints = false addSubview(checkbox) + addSubview(label) let dimension = sizeObject?.getValueBasedOnApplicationWidth() ?? Checkbox.defaultHeightWidth checkboxWidthConstraint = checkbox.heightAnchor.constraint(equalToConstant: dimension) @@ -50,13 +52,15 @@ checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension) checkboxHeightConstraint?.isActive = true - NSLayoutConstraint.constraintPinSubview(checkbox, pinTop: true, pinBottom: true, pinLeft: true, pinRight: false) + checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor).isActive = true + checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true checkbox.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - checkbox.lineWidth = 2.0 - addSubview(label) + label.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true + layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo).isActive = true } @@ -82,89 +86,32 @@ public convenience init(checkedColor: UIColor, unCheckedColor: UIColor, text: String?, isChecked: Bool = false) { self.init(frame: .zero) - checkbox.checkedColor = checkedColor - checkbox.unCheckedColor = unCheckedColor + checkbox.checkedBackgroundColor = checkedColor + checkbox.unCheckedBackgroundColor = unCheckedColor label.text = text } public convenience init(checkedColor: UIColor, unCheck unCheckedColor: UIColor, attributedText: NSAttributedString, isChecked: Bool = false) { self.init(frame: .zero) - checkbox.checkedColor = checkedColor - checkbox.unCheckedColor = unCheckedColor + checkbox.checkedBackgroundColor = checkedColor + checkbox.unCheckedBackgroundColor = unCheckedColor label.attributedText = attributedText } public convenience init(isRoundedCheckbox: Bool) { self.init(frame: .zero) - checkbox.hasRoundCorners = isRoundedCheckbox } - - //-------------------------------------------------- - // MARK: - Methods - //-------------------------------------------------- - - override open class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return CGFloat(Checkbox.defaultHeightWidth) - } - - @objc public func checkboxTapped(checkbox: Checkbox) { -// addAccessibilityLabel(selected) - - checkbox.isSelected.toggle() - - if let checkboxAction = checkboxAction { - checkboxAction(checkbox.isSelected) - } - -// if checkbox.isSelected { -// UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { -// self.checkbox.backgroundColor = self.checkedColor -// }) -// checkbox.updateCheckSelected(true, animated: animated) -// } else { -// UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { -// self.checkbox.backgroundColor = self.unCheckedColor -// }) -// -// checkbox.updateCheckSelected(false, animated: animated) -// } - - // if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { - // let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator - // formValidator?.enableByValidation() - // } - } - - //-------------------------------------------------- - // MARK: - UITouch - //-------------------------------------------------- - - func touchesEnded(_ touches: Set, with event: UIEvent) { - - if touchIsAcceptablyOutside(touches.first) { - checkbox.sendActions(for: .touchUpOutside) - } else { - checkbox.sendActions(for: .touchUpInside) - } - } - - func touchIsAcceptablyOutside(_ touch: UITouch?) -> Bool { - let endLocation = touch?.location(in: self) - let x = endLocation?.x ?? 0.0 - let y = endLocation?.y ?? 0.0 - let faultTolerance: CGFloat = 20.0 - let widthLimit = CGFloat(bounds.size.width + faultTolerance) - let heightLimt = CGFloat(bounds.size.height + faultTolerance) - - return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt - } } // MARK: - Molecular extension CheckboxWithLabelView { + override open class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return CGFloat(Checkbox.defaultHeightWidth) + } + @objc override open func updateView(_ size: CGFloat) { DispatchQueue.main.async { @@ -198,8 +145,8 @@ extension CheckboxWithLabelView { self.isRequired = isRequired } - checkbox.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - label.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + checkbox.setWithJSON(dictionary.dictionaryForKey("checkbox"), delegateObject: delegateObject, additionalData: additionalData) + label.setWithJSON(dictionary.dictionaryForKey("label"), delegateObject: delegateObject, additionalData: additionalData) } } From b032e40400833219e9aa800ca0d3eb24ea13aaeb Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 27 Sep 2019 14:23:46 -0400 Subject: [PATCH 08/14] Checkbox in good place. --- MVMCoreUI/Atoms/Views/Checkbox.swift | 88 ++++++++++--------- .../Atoms/Views/CheckboxWithLabelView.swift | 6 +- 2 files changed, 49 insertions(+), 45 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index d00c2463..29ecc3c6 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -17,13 +17,17 @@ import MVMCore public static let defaultHeightWidth: CGFloat = 18.0 /// The color of the background when checked. - public var checkedBackgroundColor: UIColor = .white + public var checkedBackgroundColor: UIColor? - /// The color of the background when unChecked. - public var unCheckedBackgroundColor: UIColor = .black + /// The color of the background when unChecked. Will change of the view's background color. + public var unCheckedBackgroundColor: UIColor = .white { + didSet (newColor) { + backgroundColor = newColor + } + } /// If true the border of this checkbox will be circular. - public var hasRoundCorners = false + public var hasRoundedCorners = false // Action Block called when the switch is selected. public var actionBlock: ActionBlock? @@ -37,7 +41,7 @@ import MVMCore public var lineWidth: CGFloat = 2 public var lineColor: UIColor = .black - + open var borderColor: UIColor { get { guard let color = layer.borderColor else { return .black } @@ -54,17 +58,23 @@ import MVMCore layer.borderWidth = newWidth } } - + override open var isSelected: Bool { didSet { + shapeLayer?.removeAllAnimations() if isSelected { -// layer.addSublayer(shapeLayer!) -// shapeLayer?.strokeEnd = 1 - shapeLayer?.removeAllAnimations() + if checkedBackgroundColor != nil { + UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { + self.backgroundColor = self.checkedBackgroundColor + }) + } shapeLayer?.add(checkedAnimation, forKey: "strokeEnd") } else { -// shapeLayer?.strokeEnd = 0 - shapeLayer?.removeAllAnimations() + if checkedBackgroundColor != nil { + UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { + self.backgroundColor = self.unCheckedBackgroundColor + }) + } shapeLayer?.add(uncheckedAnimation, forKey: "strokeEnd") } } @@ -118,6 +128,11 @@ import MVMCore self.unCheckedBackgroundColor = unCheckedColor } + public convenience init(isCircular: Bool) { + self.init(frame: .zero) + hasRoundedCorners = isCircular + } + //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- @@ -125,13 +140,14 @@ import MVMCore override open func layoutSubviews() { super.layoutSubviews() drawCheck() + layer.cornerRadius = hasRoundedCorners ? cornerRadiusValue : 0 + backgroundColor = unCheckedBackgroundColor ?? .white } public func setupView() { isUserInteractionEnabled = true translatesAutoresizingMaskIntoConstraints = false - backgroundColor = .white layer.borderWidth = 1 layer.borderColor = UIColor.black.cgColor } @@ -145,15 +161,13 @@ import MVMCore isSelected.toggle() actionBlock?() - drawCheck() } - + open override func sendActions(for controlEvents: UIControl.Event) { super.sendActions(for: controlEvents) isSelected.toggle() actionBlock?() - drawCheck() } //-------------------------------------------------- @@ -163,7 +177,7 @@ import MVMCore private func drawCheck() { if shapeLayer == nil { - + let shapeLayer = CAShapeLayer() self.shapeLayer = shapeLayer shapeLayer.frame = bounds @@ -173,7 +187,7 @@ import MVMCore shapeLayer.path = checkMarkBezierPath.cgPath shapeLayer.lineJoin = .bevel shapeLayer.lineWidth = lineWidth - + CATransaction.withDisabledAnimations { shapeLayer.strokeEnd = 0.0 } @@ -181,6 +195,9 @@ import MVMCore } /* + !!! -- DO NOT REMOVE -- !!! + (Unless Design changes the appearance of the checkmark). + // Offsets based on the 124x124 example checkmark let startXOffset: Float = 42.0 / 124.0 ~~ 0.33871 let startYOffset: Float = 66.0 / 124.0 ~~ 0.53225 @@ -208,17 +225,17 @@ import MVMCore return path } - + public func updateSelection(_ selected: Bool, animated: Bool) { -// shapeLayer?.removeFromSuperlayer() -// shapeLayer = nil + // shapeLayer?.removeFromSuperlayer() + // shapeLayer = nil DispatchQueue.main.async { self.isSelected = selected self.drawCheck() - + var layer: CAShapeLayer? if let presentation = self.shapeLayer?.presentation(), animated { layer = presentation @@ -268,19 +285,6 @@ import MVMCore return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt } - // if checkbox.isSelected { - // UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { - // self.checkbox.backgroundColor = self.checkedColor - // }) - // checkbox.updateCheckSelected(true, animated: animated) - // } else { - // UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { - // self.checkbox.backgroundColor = self.unCheckedColor - // }) - // - // checkbox.updateCheckSelected(false, animated: animated) - // } - // if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { // let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator // formValidator?.enableByValidation() @@ -288,7 +292,7 @@ import MVMCore /* - + - (void)setSelected:(BOOL)selected animated:(BOOL)animated runBlock:(BOOL)runBlock { [self addAccessibilityLabel:selected]; @@ -313,8 +317,8 @@ import MVMCore [formValidator enableByValidation]; } } - */ - + */ + //-------------------------------------------------- // MARK: - Molecular //-------------------------------------------------- @@ -360,9 +364,13 @@ import MVMCore unCheckedBackgroundColor = UIColor.mfGet(forHex: unCheckedColorHex) } -// if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { -// actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } -// } + if let isRound = dictionary["isRound"] as? Bool { + hasRoundedCorners = isRound + } + + // if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { + // actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } + // } } } diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index 94cd86e2..db551800 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -56,6 +56,7 @@ layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor).isActive = true checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true checkbox.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + checkbox.checkedBackgroundColor = .yellow label.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true @@ -98,11 +99,6 @@ checkbox.unCheckedBackgroundColor = unCheckedColor label.attributedText = attributedText } - - public convenience init(isRoundedCheckbox: Bool) { - self.init(frame: .zero) - checkbox.hasRoundCorners = isRoundedCheckbox - } } // MARK: - Molecular From 5f8780614f881ce8b38946cdf1ef090365bfa86c Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Fri, 27 Sep 2019 14:28:16 -0400 Subject: [PATCH 09/14] borders. --- MVMCoreUI/Atoms/Views/Checkbox.swift | 29 ++++++++-------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index 29ecc3c6..d0599043 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -8,7 +8,9 @@ import MVMCore - +/** + This class expects its height and width to be equal. + */ @objcMembers open class Checkbox: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol { //-------------------------------------------------- // MARK: - Properties @@ -41,23 +43,8 @@ import MVMCore public var lineWidth: CGFloat = 2 public var lineColor: UIColor = .black - - open var borderColor: UIColor { - get { - guard let color = layer.borderColor else { return .black } - return UIColor(cgColor: color) - } - set (newColor) { - layer.borderColor = newColor.cgColor - } - } - - open var borderWidth: CGFloat { - get { return layer.borderWidth } - set (newWidth) { - layer.borderWidth = newWidth - } - } + public var borderColor: UIColor = .black + public var borderWidth: CGFloat = 1 override open var isSelected: Bool { didSet { @@ -141,15 +128,15 @@ import MVMCore super.layoutSubviews() drawCheck() layer.cornerRadius = hasRoundedCorners ? cornerRadiusValue : 0 - backgroundColor = unCheckedBackgroundColor ?? .white + backgroundColor = unCheckedBackgroundColor + layer.borderWidth = borderWidth + layer.borderColor = borderColor.cgColor } public func setupView() { isUserInteractionEnabled = true translatesAutoresizingMaskIntoConstraints = false - layer.borderWidth = 1 - layer.borderColor = UIColor.black.cgColor } //-------------------------------------------------- From 77a3eb882781d9814e1268d9f1ecce85152ee6d1 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 2 Oct 2019 15:22:12 -0400 Subject: [PATCH 10/14] Added a new extension file for CATransaction. Implementation of Checkbox and CheckboxWithLabel. --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + MVMCoreUI/Atoms/Views/Checkbox.swift | 323 +++++++++--------- .../Atoms/Views/CheckboxWithLabelView.swift | 204 +++++++---- .../Utility/CATransaction+Extension.swift | 21 ++ 4 files changed, 321 insertions(+), 231 deletions(-) create mode 100644 MVMCoreUI/Utility/CATransaction+Extension.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 9692652c..253371bc 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; }; 0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */; }; + 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.swift */; }; @@ -205,6 +206,7 @@ 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = ""; }; 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = ""; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = ""; }; + 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.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 = ""; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = ""; }; @@ -654,6 +656,7 @@ D29DF2A021E7AF4E003B2FB9 /* MVMCoreUIUtility.m */, D29DF2A721E7B2F9003B2FB9 /* MVMCoreUIConstants.h */, D29DF2A821E7B2F9003B2FB9 /* MVMCoreUIConstants.m */, + 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */, ); path = Utility; sourceTree = ""; @@ -1095,6 +1098,7 @@ D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, + 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */, diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index d0599043..9fac4910 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -8,6 +8,22 @@ import MVMCore +/* + !!! -- DO NOT REMOVE -- !!! + (Unless Design changes the appearance of the checkmark). + + // Offsets based on the 124x124 example checkmark + let startXOffset: Float = 42.0 / 124.0 ~~ 0.33871 + let startYOffset: Float = 66.0 / 124.0 ~~ 0.53225 + let pivotXOffset: Float = 58.0 / 124.0 ~~ 0.46774 + let pivotYOffset: Float = 80.0 / 124.0 ~~ 0.64516 + let endXOffset: Float = 83.0 / 124.0 ~~ 0.66935 + let endYOffset: Float = 46.0 / 124.0 ~~ 0.37097 + let pivotPercentage: Float = 0.34 + let endPercentage = 1.0 - pivotPercentage + let animationInterval: Float = 0.01 + */ + /** This class expects its height and width to be equal. */ @@ -16,85 +32,82 @@ import MVMCore // MARK: - Properties //-------------------------------------------------- + // Form Validation + var isRequired = false + var fieldKey: String? + var delegateObject: DelegateObject? + public static let defaultHeightWidth: CGFloat = 18.0 - /// The color of the background when checked. - public var checkedBackgroundColor: UIColor? + /// If true the border of this checkbox will be circular. + public var isRound: Bool = false - /// The color of the background when unChecked. Will change of the view's background color. - public var unCheckedBackgroundColor: UIColor = .white { - didSet (newColor) { - backgroundColor = newColor + /// Determined if the checkbox's UI should animated when selected. + public var isAnimated: Bool = true + + /// Disables all selection logic when setting the value of isSelected, reducing it to a stored property. + public var updateSelectionOnly: Bool = false + + /// The color of the background when checked. + public var checkedBackgroundColor: UIColor = .white { + didSet { + if isSelected { + backgroundColor = checkedBackgroundColor + } } } - /// If true the border of this checkbox will be circular. - public var hasRoundedCorners = false - - // Action Block called when the switch is selected. - public var actionBlock: ActionBlock? - - // Internal values to manage the appearance of the checkbox. - private var shapeLayer: CAShapeLayer? + /// The color of the background when unChecked. + public var unCheckedBackgroundColor: UIColor = .white { + didSet { + if !isSelected { + backgroundColor = unCheckedBackgroundColor + } + } + } public var cornerRadiusValue: CGFloat { return bounds.size.height / 2 } - public var lineWidth: CGFloat = 2 - public var lineColor: UIColor = .black - public var borderColor: UIColor = .black + /// Action Block called when the switch is selected. + public var actionBlock: ActionBlock? + + /// Manages the appearance of the checkbox. + private var shapeLayer: CAShapeLayer? + + public var checkWidth: CGFloat = 2 + public var checkColor: UIColor = .black public var borderWidth: CGFloat = 1 + public var borderColor: UIColor = .black override open var isSelected: Bool { didSet { - shapeLayer?.removeAllAnimations() - if isSelected { - if checkedBackgroundColor != nil { - UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { - self.backgroundColor = self.checkedBackgroundColor - }) + if !updateSelectionOnly { + layoutIfNeeded() + shapeLayer?.removeAllAnimations() + + updateCheckboxUI(selection: isSelected, isAnimated: isAnimated) + + if let delegate = delegateObject as? FormValidationProtocol { + delegate.formValidatorModel?()?.enableByValidation() } - shapeLayer?.add(checkedAnimation, forKey: "strokeEnd") - } else { - if checkedBackgroundColor != nil { - UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { - self.backgroundColor = self.unCheckedBackgroundColor - }) - } - shapeLayer?.add(uncheckedAnimation, forKey: "strokeEnd") + + accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") } } } - lazy private var checkedAnimation: CABasicAnimation = { - let check = CABasicAnimation(keyPath: "strokeEnd") - check.timingFunction = CAMediaTimingFunction(name: .linear) - check.isRemovedOnCompletion = false - check.fillMode = .both - check.duration = 0.3 - check.fromValue = 0 - check.toValue = 1 - return check - }() - - lazy private var uncheckedAnimation: CABasicAnimation = { - let unCheck = CABasicAnimation(keyPath: "strokeEnd") - unCheck.timingFunction = CAMediaTimingFunction(name: .linear) - unCheck.isRemovedOnCompletion = false - unCheck.fillMode = .both - unCheck.duration = 0.3 - unCheck.fromValue = 1 - unCheck.toValue = 0 - return unCheck - }() - //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- override public init(frame: CGRect) { super.init(frame: frame) + + accessibilityTraits = .none + accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") + setupView() } @@ -108,16 +121,20 @@ import MVMCore self.init(frame:.zero) } - public convenience init(checkedColor: UIColor, unCheckedColor: UIColor, isChecked: Bool = false) { + public convenience init(isChecked: Bool) { self.init(frame: .zero) + updateSelectionOnly = true isSelected = isChecked - self.checkedBackgroundColor = checkedColor - self.unCheckedBackgroundColor = unCheckedColor + updateSelectionOnly = false } - public convenience init(isCircular: Bool) { + public convenience init(checkedBackgroundColor: UIColor, unCheckedBackgroundColor: UIColor, isChecked: Bool = false) { self.init(frame: .zero) - hasRoundedCorners = isCircular + updateSelectionOnly = true + isSelected = isChecked + updateSelectionOnly = false + self.checkedBackgroundColor = checkedBackgroundColor + self.unCheckedBackgroundColor = unCheckedBackgroundColor } //-------------------------------------------------- @@ -126,9 +143,9 @@ import MVMCore override open func layoutSubviews() { super.layoutSubviews() + drawCheck() - layer.cornerRadius = hasRoundedCorners ? cornerRadiusValue : 0 - backgroundColor = unCheckedBackgroundColor + layer.cornerRadius = isRound ? cornerRadiusValue : 0 layer.borderWidth = borderWidth layer.borderColor = borderColor.cgColor } @@ -137,6 +154,7 @@ import MVMCore isUserInteractionEnabled = true translatesAutoresizingMaskIntoConstraints = false + backgroundColor = .white } //-------------------------------------------------- @@ -161,6 +179,7 @@ import MVMCore // MARK: - Methods //-------------------------------------------------- + /// Creates the check mark used for the checkbox. private func drawCheck() { if shapeLayer == nil { @@ -169,36 +188,20 @@ import MVMCore self.shapeLayer = shapeLayer shapeLayer.frame = bounds layer.addSublayer(shapeLayer) - shapeLayer.strokeColor = lineColor.cgColor + shapeLayer.strokeColor = checkColor.cgColor shapeLayer.fillColor = UIColor.clear.cgColor - shapeLayer.path = checkMarkBezierPath.cgPath + shapeLayer.path = checkMarkPath.cgPath shapeLayer.lineJoin = .bevel - shapeLayer.lineWidth = lineWidth + shapeLayer.lineWidth = checkWidth CATransaction.withDisabledAnimations { - shapeLayer.strokeEnd = 0.0 + shapeLayer.strokeEnd = isSelected ? 1 : 0 } } } - /* - !!! -- DO NOT REMOVE -- !!! - (Unless Design changes the appearance of the checkmark). - - // Offsets based on the 124x124 example checkmark - let startXOffset: Float = 42.0 / 124.0 ~~ 0.33871 - let startYOffset: Float = 66.0 / 124.0 ~~ 0.53225 - let pivotXOffset: Float = 58.0 / 124.0 ~~ 0.46774 - let pivotYOffset: Float = 80.0 / 124.0 ~~ 0.64516 - let endXOffset: Float = 83.0 / 124.0 ~~ 0.66935 - let endYOffset: Float = 46.0 / 124.0 ~~ 0.37097 - let pivotPercentage: Float = 0.34 - let endPercentage = 1.0 - pivotPercentage - let animationInterval: Float = 0.01 - */ - /// Returns a UIBezierPath detailing the path of a checkmark - var checkMarkBezierPath: UIBezierPath { + var checkMarkPath: UIBezierPath { let sideLength = bounds.size.height let startPoint = CGPoint(x: 0.33871 * sideLength, y: 0.53225 * sideLength) @@ -213,38 +216,43 @@ import MVMCore return path } - public func updateSelection(_ selected: Bool, animated: Bool) { - - // shapeLayer?.removeFromSuperlayer() - // shapeLayer = nil + /// Programmatic means to check/uncheck the box. + /// - parameter selected: state of the check box: true = checked OR false = unchecked. + /// - parameter animated: allows the state of the checkbox to change with or without animation. + public func updateSelection(to selected: Bool, animated: Bool) { DispatchQueue.main.async { + self.updateSelectionOnly = true self.isSelected = selected + self.updateSelectionOnly = false self.drawCheck() + self.shapeLayer?.removeAllAnimations() + self.updateCheckboxUI(selection: selected, isAnimated: animated) + } + } + + func updateCheckboxUI(selection: Bool, isAnimated: Bool) { + + if isAnimated { + let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") + animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) + animateStrokeEnd.duration = 0.3 + animateStrokeEnd.fillMode = .both + animateStrokeEnd.isRemovedOnCompletion = false + animateStrokeEnd.fromValue = !selection ? 1 : 0 + animateStrokeEnd.toValue = selection ? 1 : 0 + self.shapeLayer?.add(animateStrokeEnd, forKey: "strokeEnd") - var layer: CAShapeLayer? - if let presentation = self.shapeLayer?.presentation(), animated { - layer = presentation - } else { - layer = self.shapeLayer + UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { + self.backgroundColor = selection ? self.checkedBackgroundColor : self.unCheckedBackgroundColor + }) + } else { + CATransaction.withDisabledAnimations { + self.shapeLayer?.strokeEnd = selection ? 1 : 0 } - if animated { - let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") - animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) - animateStrokeEnd.duration = 0.33 - animateStrokeEnd.fillMode = .both - animateStrokeEnd.isRemovedOnCompletion = false - animateStrokeEnd.fromValue = layer?.strokeEnd ?? 0 - animateStrokeEnd.toValue = selected ? 1 : 0 - layer?.add(animateStrokeEnd, forKey: "strokeEndAnimation") - } else { - layer?.removeAllAnimations() - CATransaction.withDisabledAnimations { - layer?.strokeEnd = selected ? 1 : 0 - } - } + self.backgroundColor = selection ? self.checkedBackgroundColor : self.unCheckedBackgroundColor } } @@ -254,11 +262,7 @@ import MVMCore open override func touchesEnded(_ touches: Set, with event: UIEvent?) { - if touchIsAcceptablyOutside(touches.first) { - sendActions(for: .touchUpOutside) - } else { - sendActions(for: .touchUpInside) - } + sendActions(for: touchIsAcceptablyOutside(touches.first) ? .touchUpOutside : .touchUpInside) } func touchIsAcceptablyOutside(_ touch: UITouch?) -> Bool { @@ -272,40 +276,6 @@ import MVMCore return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt } - // if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { - // let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator - // formValidator?.enableByValidation() - // } - - - /* - - - (void)setSelected:(BOOL)selected animated:(BOOL)animated runBlock:(BOOL)runBlock { - [self addAccessibilityLabel:selected]; - - self.isSelected = selected; - if (self.switchSelected && runBlock) { - self.switchSelected(selected); - } - if (selected) { - [UIView animateWithDuration:0.2 delay:0.1 options:UIViewAnimationOptionCurveEaseOut animations:^{ - self.checkedSquare.backgroundColor = self.checkedColor; - } completion:nil]; - [self.checkMark updateCheckSelected:YES animated:animated]; - } else { - [UIView animateWithDuration:0.2 delay:0.1 options:UIViewAnimationOptionCurveEaseOut animations:^{ - self.checkedSquare.backgroundColor = self.unCheckedColor; - } completion:nil]; - [self.checkMark updateCheckSelected:NO animated:animated]; - } - - if (self.delegate && [self.delegate respondsToSelector:@selector(formValidationProtocol)] && [[self.delegate performSelector:@selector(formValidationProtocol)] respondsToSelector:@selector(formValidatorModel)]) { - FormValidator *formValidator = [[self.delegate performSelector:@selector(formValidationProtocol)] performSelector:@selector(formValidatorModel)]; - [formValidator enableByValidation]; - } - } - */ - //-------------------------------------------------- // MARK: - Molecular //-------------------------------------------------- @@ -322,15 +292,22 @@ import MVMCore setupView() } - public func updateView(_ size: CGFloat) { - - // TODO: Ensure the check logic is resized. - } + public func updateView(_ size: CGFloat) { } public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + self.delegateObject = delegateObject + FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) guard let dictionary = json else { return } + if let fieldKey = dictionary[KeyFieldKey] as? String { + self.fieldKey = fieldKey + } + + if let isRequired = dictionary[KeyRequired] as? Bool { + self.isRequired = isRequired + } + if let borderColorHex = dictionary["borderColor"] as? String { layer.borderColor = UIColor.mfGet(forHex: borderColorHex).cgColor } @@ -339,36 +316,50 @@ import MVMCore layer.borderWidth = borderWidth } - if let checkColorHex = dictionary["lineColor"] as? String { - lineColor = UIColor.mfGet(forHex: checkColorHex) + if let isChecked = dictionary["isChecked"] as? Bool, isChecked { + updateSelectionOnly = true + isSelected = isChecked + updateSelectionOnly = false } - if let checkColorHex = dictionary["checkedColor"] as? String { - checkedBackgroundColor = UIColor.mfGet(forHex: checkColorHex) + if let checkColorHex = dictionary["checkColor"] as? String { + checkColor = UIColor.mfGet(forHex: checkColorHex) } - if let unCheckedColorHex = dictionary["unCheckedColor"] as? String { - unCheckedBackgroundColor = UIColor.mfGet(forHex: unCheckedColorHex) + if let unCheckedBackgroundColorHex = dictionary["unCheckedBackgroundColor"] as? String { + unCheckedBackgroundColor = UIColor.mfGet(forHex: unCheckedBackgroundColorHex) + } + + if let checkedBackgroundColorHex = dictionary["checkedBackgroundColor"] as? String { + checkedBackgroundColor = UIColor.mfGet(forHex: checkedBackgroundColorHex) + } + + if let isAnimated = dictionary["isAnimated"] as? Bool { + self.isAnimated = isAnimated } if let isRound = dictionary["isRound"] as? Bool { - hasRoundedCorners = isRound + self.isRound = isRound } - // if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { - // actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } - // } + if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { + actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } + } } } -// TODO: Move to its own extension file. -extension CATransaction { +// MARK:- FormValidationProtocol +extension Checkbox: FormValidationProtocol { - /// Performs changes without activating animation actions. - static func withDisabledAnimations(_ actionBlock: ActionBlock) { - CATransaction.begin() - CATransaction.setDisableActions(true) - actionBlock() - CATransaction.commit() + public func isValidField() -> Bool { + return isRequired ? isSelected : true + } + + public func formFieldName() -> String? { + return fieldKey + } + + public func formFieldValue() -> Any? { + return NSNumber(value: isSelected) } } diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index db551800..bf0288ae 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -12,8 +12,8 @@ // MARK: - Outlets //-------------------------------------------------- - let checkbox = Checkbox() - let label = Label.commonLabelB2(true) + public let checkbox = Checkbox() + public let label = Label.commonLabelB2(true) //-------------------------------------------------- // MARK: - Properties @@ -21,17 +21,34 @@ var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0) - var isRequired = false - var fieldKey: String? - var delegate: DelegateObject? + var checkboxPosition: CheckboxPosition = .centerLeft + + public enum CheckboxPosition: String { + case centerLeft + case topLeft + case bottomLeft + } //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- - + var checkboxWidthConstraint: NSLayoutConstraint? var checkboxHeightConstraint: NSLayoutConstraint? + var checkboxLeadingConstraint: NSLayoutConstraint? + var checkboxTrailingConstraint: NSLayoutConstraint? + var checkboxTopConstraint: NSLayoutConstraint? + var checkboxBottomConstraint: NSLayoutConstraint? + var checkboxCenterYConstraint: NSLayoutConstraint? + + var checkboxLabelConstraint: NSLayoutConstraint? + + var labelLeadingConstraint: NSLayoutConstraint? + var labelTrailingConstraint: NSLayoutConstraint? + var labelTopConstraint: NSLayoutConstraint? + var labelBottomConstraint: NSLayoutConstraint? + //-------------------------------------------------- // MARK: - Life Cycle //-------------------------------------------------- @@ -46,23 +63,24 @@ addSubview(checkbox) addSubview(label) + label.setContentCompressionResistancePriority(.required, for: .vertical) + let dimension = sizeObject?.getValueBasedOnApplicationWidth() ?? Checkbox.defaultHeightWidth checkboxWidthConstraint = checkbox.heightAnchor.constraint(equalToConstant: dimension) checkboxWidthConstraint?.isActive = true checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension) checkboxHeightConstraint?.isActive = true - checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor).isActive = true - checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true - checkbox.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - checkbox.checkedBackgroundColor = .yellow + alignSubviews(by: .centerLeft) - label.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true - label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true - label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo).isActive = true + label.isUserInteractionEnabled = true + let tap = UITapGestureRecognizer(target: self, action: #selector(tapped)) + tap.numberOfTapsRequired = 1 + label.addGestureRecognizer(tap) + } + + @objc func tapped() { + checkbox.updateSelection(to: !checkbox.isSelected, animated: true) } //-------------------------------------------------- @@ -77,13 +95,18 @@ override public init(frame: CGRect) { super.init(frame: frame) setupView() -// addAccessibleProperties() } public convenience init() { self.init(frame: .zero) } + public convenience init(position: CheckboxPosition) { + self.init(frame: .zero) + + alignSubviews(by: position) + } + public convenience init(checkedColor: UIColor, unCheckedColor: UIColor, text: String?, isChecked: Bool = false) { self.init(frame: .zero) @@ -99,79 +122,130 @@ checkbox.unCheckedBackgroundColor = unCheckedColor label.attributedText = attributedText } + + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- + + /// Aligns Checkbox and Label relative to the desired position of the Checkbox. + private func alignSubviews(by position: CheckboxPosition) { + checkboxPosition = position + + label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + + switch position { + case .centerLeft: + checkboxTopConstraint = checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor) + checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor) + checkboxLeadingConstraint = checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + checkboxCenterYConstraint = checkbox.centerYAnchor.constraint(equalTo: centerYAnchor) + + labelTopConstraint = label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) + labelTrailingConstraint = layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor) + checkboxLabelConstraint = label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo) + + case .topLeft: + checkboxTopConstraint = checkbox.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) + checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor) + checkboxLeadingConstraint = checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + + labelTopConstraint = label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) + labelTrailingConstraint = layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor) + checkboxLabelConstraint = label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo) + + case .bottomLeft: + checkboxTopConstraint = checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor) + checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: checkbox.bottomAnchor) + checkboxLeadingConstraint = checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) + + labelTopConstraint = label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) + labelTrailingConstraint = layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor) + checkboxLabelConstraint = label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo) + labelBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor) + } + + if position != .bottomLeft { + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true + let labelBottom = label.bottomAnchor.constraint(equalTo: bottomAnchor) + labelBottomConstraint = labelBottom + labelBottom.priority = UILayoutPriority(249) + labelBottom.isActive = true + } + + toggleSubviewConstraints(isActive: true) + } + + func toggleSubviewConstraints(isActive state: Bool) { + + checkboxLeadingConstraint?.isActive = state + checkboxTrailingConstraint?.isActive = state + checkboxTopConstraint?.isActive = state + checkboxBottomConstraint?.isActive = state + checkboxCenterYConstraint?.isActive = state + labelLeadingConstraint?.isActive = state + labelTrailingConstraint?.isActive = state + labelTopConstraint?.isActive = state + labelBottomConstraint?.isActive = state + checkboxLabelConstraint?.isActive = state + + if !state { + checkboxLeadingConstraint = nil + checkboxTrailingConstraint = nil + checkboxTopConstraint = nil + checkboxBottomConstraint = nil + checkboxCenterYConstraint = nil + labelLeadingConstraint = nil + labelTrailingConstraint = nil + labelTopConstraint = nil + labelBottomConstraint = nil + checkboxLabelConstraint = nil + } + } } -// MARK: - Molecular +/// MARK: - Molecular extension CheckboxWithLabelView { - override open class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + override open class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { return CGFloat(Checkbox.defaultHeightWidth) } @objc override open func updateView(_ size: CGFloat) { - DispatchQueue.main.async { - self.label.updateView(size) - if self.checkbox.responds(to: #selector(self.updateView(_:))) { - if let dimension = self.sizeObject?.getValueBased(onSize: size) { - self.checkboxWidthConstraint?.constant = dimension - self.checkboxHeightConstraint?.constant = dimension - self.checkbox.updateView(size) - } + label.updateView(size) + + if self.checkbox.responds(to: #selector(self.updateView(_:))) { + if let dimension = sizeObject?.getValueBased(onSize: size) { + checkboxWidthConstraint?.constant = dimension + checkboxHeightConstraint?.constant = dimension + checkbox.updateView(size) } } + + layoutIfNeeded() } override open func alignment() -> UIStackView.Alignment { return .leading } + open override func resetConstraints() { + super.resetConstraints() + + toggleSubviewConstraints(isActive: false) + } + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) - delegate = delegateObject - FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) guard let dictionary = json else { return } - if let fieldKey = dictionary[KeyFieldKey] as? String { - self.fieldKey = fieldKey - } - - if let isRequired = dictionary[KeyRequired] as? Bool { - self.isRequired = isRequired + if let checkboxAlignment = dictionary["checkboxAlignment"] as? String, let position = CheckboxPosition(rawValue: checkboxAlignment) { + toggleSubviewConstraints(isActive: false) + alignSubviews(by: position) } checkbox.setWithJSON(dictionary.dictionaryForKey("checkbox"), delegateObject: delegateObject, additionalData: additionalData) label.setWithJSON(dictionary.dictionaryForKey("label"), delegateObject: delegateObject, additionalData: additionalData) } } - -// MARK:- FormValidationProtocol -extension CheckboxWithLabelView: FormValidationProtocol { - - public func isValidField() -> Bool { - return isRequired ? checkbox.isSelected : true - } - - public func formFieldName() -> String? { - return fieldKey - } - - public func formFieldValue() -> Any? { - return NSNumber(value: checkbox.isSelected) - } -} - -// MARK:- Accessibility -extension CheckboxWithLabelView { - - func addAccessibleProperties() { -// accessibilityTraits = .none -// accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") - } - - func addAccessibilityLabel(_ selected: Bool) { -// let state = selected ? MVMCoreUIUtility.hardcodedString(withKey: "checkbox_checked_state") : MVMCoreUIUtility.hardcodedString(withKey: "checkbox_unchecked_state") -// accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state"), label.text ?? "", state) - } -} diff --git a/MVMCoreUI/Utility/CATransaction+Extension.swift b/MVMCoreUI/Utility/CATransaction+Extension.swift new file mode 100644 index 00000000..66bb33d3 --- /dev/null +++ b/MVMCoreUI/Utility/CATransaction+Extension.swift @@ -0,0 +1,21 @@ +// +// CATransaction+Extension.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 10/2/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import Foundation + + +extension CATransaction { + + /// Performs changes without activating animation actions. + static func withDisabledAnimations(_ actionBlock: ActionBlock) { + CATransaction.begin() + CATransaction.setDisableActions(true) + actionBlock() + CATransaction.commit() + } +} From 61fcd981c1312d2c27de069210b4a43ba092ccb1 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 2 Oct 2019 15:27:32 -0400 Subject: [PATCH 11/14] setting up the rest. --- .../Atoms/Views/CheckboxWithLabelView.swift | 27 ++----------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index bf0288ae..40b91f87 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -72,15 +72,6 @@ checkboxHeightConstraint?.isActive = true alignSubviews(by: .centerLeft) - - label.isUserInteractionEnabled = true - let tap = UITapGestureRecognizer(target: self, action: #selector(tapped)) - tap.numberOfTapsRequired = 1 - label.addGestureRecognizer(tap) - } - - @objc func tapped() { - checkbox.updateSelection(to: !checkbox.isSelected, animated: true) } //-------------------------------------------------- @@ -104,25 +95,10 @@ public convenience init(position: CheckboxPosition) { self.init(frame: .zero) + toggleSubviewConstraints(isActive: false) alignSubviews(by: position) } - public convenience init(checkedColor: UIColor, unCheckedColor: UIColor, text: String?, isChecked: Bool = false) { - self.init(frame: .zero) - - checkbox.checkedBackgroundColor = checkedColor - checkbox.unCheckedBackgroundColor = unCheckedColor - label.text = text - } - - public convenience init(checkedColor: UIColor, unCheck unCheckedColor: UIColor, attributedText: NSAttributedString, isChecked: Bool = false) { - self.init(frame: .zero) - - checkbox.checkedBackgroundColor = checkedColor - checkbox.unCheckedBackgroundColor = unCheckedColor - label.attributedText = attributedText - } - //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -211,6 +187,7 @@ extension CheckboxWithLabelView { } @objc override open func updateView(_ size: CGFloat) { + super.updateView(size) label.updateView(size) From 5d1c87819d23d89a7df4aa88404cf014e56b4c1a Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 3 Oct 2019 11:14:16 -0400 Subject: [PATCH 12/14] making changes to align with comments. --- MVMCoreUI/Atoms/Views/Checkbox.swift | 31 ++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index 9fac4910..fd1fa48a 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -76,8 +76,21 @@ import MVMCore /// Manages the appearance of the checkbox. private var shapeLayer: CAShapeLayer? - public var checkWidth: CGFloat = 2 - public var checkColor: UIColor = .black + public var checkWidth: CGFloat = 2 { + didSet { + CATransaction.withDisabledAnimations { + shapeLayer?.lineWidth = checkWidth + } + } + } + public var checkColor: UIColor = .black { + didSet { + CATransaction.withDisabledAnimations { + shapeLayer?.strokeColor = checkColor.cgColor + } + } + } + public var borderWidth: CGFloat = 1 public var borderColor: UIColor = .black @@ -93,7 +106,9 @@ import MVMCore delegate.formValidatorModel?()?.enableByValidation() } - accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") + if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") { + accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state") ?? "%@", state) + } } } } @@ -105,8 +120,11 @@ import MVMCore override public init(frame: CGRect) { super.init(frame: frame) - accessibilityTraits = .none + accessibilityTraits = .button accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") + if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") { + accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state") ?? "%@", state) + } setupView() } @@ -276,6 +294,11 @@ import MVMCore return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt } + override open func accessibilityActivate() -> Bool { + sendActions(for: .touchUpInside) + return true + } + //-------------------------------------------------- // MARK: - Molecular //-------------------------------------------------- From 44e420218db627b5de1e6eab3311b4a47624fdba Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 3 Oct 2019 13:22:15 -0400 Subject: [PATCH 13/14] Constraints mended. more commenting. --- MVMCoreUI/Atoms/Views/Checkbox.swift | 79 ++++++++---- .../Atoms/Views/CheckboxWithLabelView.swift | 121 ++++++------------ 2 files changed, 90 insertions(+), 110 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index fd1fa48a..77a1f342 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -66,6 +66,7 @@ import MVMCore } } + /// Retrieves ideeal radius value to curve square into a circle. public var cornerRadiusValue: CGFloat { return bounds.size.height / 2 } @@ -76,6 +77,7 @@ import MVMCore /// Manages the appearance of the checkbox. private var shapeLayer: CAShapeLayer? + /// Width of the check mark. public var checkWidth: CGFloat = 2 { didSet { CATransaction.withDisabledAnimations { @@ -83,6 +85,8 @@ import MVMCore } } } + + /// Color of the check mark. public var checkColor: UIColor = .black { didSet { CATransaction.withDisabledAnimations { @@ -91,24 +95,35 @@ import MVMCore } } - public var borderWidth: CGFloat = 1 - public var borderColor: UIColor = .black + /// Border width of the checkbox + public var borderWidth: CGFloat = 1 { + didSet { + layer.borderWidth = borderWidth + } + } + /// border color of the Checkbox + public var borderColor: UIColor = .black { + didSet { + layer.borderColor = borderColor.cgColor + } + } + + /// The represented state of the Checkbox. + /// If updateSelectionOnly = true, then this will behave only as a stored property. override open var isSelected: Bool { didSet { if !updateSelectionOnly { layoutIfNeeded() shapeLayer?.removeAllAnimations() - updateCheckboxUI(selection: isSelected, isAnimated: isAnimated) + updateCheckboxUI(isSelected: isSelected, isAnimated: isAnimated) if let delegate = delegateObject as? FormValidationProtocol { delegate.formValidatorModel?()?.enableByValidation() } - if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") { - accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state") ?? "%@", state) - } + updateAccessibilityLabel() } } } @@ -122,9 +137,7 @@ import MVMCore accessibilityTraits = .button accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") - if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") { - accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state") ?? "%@", state) - } + updateAccessibilityLabel() setupView() } @@ -181,13 +194,16 @@ import MVMCore open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { super.sendAction(action, to: target, for: event) - - isSelected.toggle() - actionBlock?() + toggleAndAction() } open override func sendActions(for controlEvents: UIControl.Event) { super.sendActions(for: controlEvents) + toggleAndAction() + } + + /// This will toggle the state of the Checkbox and execute the actionBlock if provided. + public func toggleAndAction() { isSelected.toggle() actionBlock?() @@ -208,7 +224,7 @@ import MVMCore layer.addSublayer(shapeLayer) shapeLayer.strokeColor = checkColor.cgColor shapeLayer.fillColor = UIColor.clear.cgColor - shapeLayer.path = checkMarkPath.cgPath + shapeLayer.path = checkMarkPath() shapeLayer.lineJoin = .bevel shapeLayer.lineWidth = checkWidth @@ -219,19 +235,19 @@ import MVMCore } /// Returns a UIBezierPath detailing the path of a checkmark - var checkMarkPath: UIBezierPath { + func checkMarkPath() -> CGPath { let sideLength = bounds.size.height let startPoint = CGPoint(x: 0.33871 * sideLength, y: 0.53225 * sideLength) let pivotOffSet = CGPoint(x: 0.46774 * sideLength, y: 0.64516 * sideLength) let endOffset = CGPoint(x: 0.66935 * sideLength , y: 0.37097 * sideLength) - let path = UIBezierPath() - path.move(to: startPoint) - path.addLine(to: pivotOffSet) - path.addLine(to: endOffset) + let bezierPath = UIBezierPath() + bezierPath.move(to: startPoint) + bezierPath.addLine(to: pivotOffSet) + bezierPath.addLine(to: endOffset) - return path + return bezierPath.cgPath } /// Programmatic means to check/uncheck the box. @@ -246,11 +262,14 @@ import MVMCore self.updateSelectionOnly = false self.drawCheck() self.shapeLayer?.removeAllAnimations() - self.updateCheckboxUI(selection: selected, isAnimated: animated) + self.updateCheckboxUI(isSelected: selected, isAnimated: animated) } } - func updateCheckboxUI(selection: Bool, isAnimated: Bool) { + /// updates the visuals of the check mark and background. + /// - parameter isSelection: the check state of the checkbox. + /// - parameter isAnimated: determines of the changes should animate or immediately refelect. + public func updateCheckboxUI(isSelected: Bool, isAnimated: Bool) { if isAnimated { let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") @@ -258,19 +277,27 @@ import MVMCore animateStrokeEnd.duration = 0.3 animateStrokeEnd.fillMode = .both animateStrokeEnd.isRemovedOnCompletion = false - animateStrokeEnd.fromValue = !selection ? 1 : 0 - animateStrokeEnd.toValue = selection ? 1 : 0 + animateStrokeEnd.fromValue = !isSelected ? 1 : 0 + animateStrokeEnd.toValue = isSelected ? 1 : 0 self.shapeLayer?.add(animateStrokeEnd, forKey: "strokeEnd") UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { - self.backgroundColor = selection ? self.checkedBackgroundColor : self.unCheckedBackgroundColor + self.backgroundColor = isSelected ? self.checkedBackgroundColor : self.unCheckedBackgroundColor }) } else { CATransaction.withDisabledAnimations { - self.shapeLayer?.strokeEnd = selection ? 1 : 0 + self.shapeLayer?.strokeEnd = isSelected ? 1 : 0 } - self.backgroundColor = selection ? self.checkedBackgroundColor : self.unCheckedBackgroundColor + self.backgroundColor = isSelected ? self.checkedBackgroundColor : self.unCheckedBackgroundColor + } + } + + /// Adjust accessibility label based on state of Checkbox. + func updateAccessibilityLabel() { + // Attention: This needs to be addressed with the accessibility team. + if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") { + accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state") ?? "%@", state) } } diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index 40b91f87..e9aa0d0e 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -21,12 +21,12 @@ var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0) - var checkboxPosition: CheckboxPosition = .centerLeft + var checkboxPosition: CheckboxPosition = .center public enum CheckboxPosition: String { - case centerLeft - case topLeft - case bottomLeft + case center + case top + case bottom } //-------------------------------------------------- @@ -35,20 +35,10 @@ var checkboxWidthConstraint: NSLayoutConstraint? var checkboxHeightConstraint: NSLayoutConstraint? - - var checkboxLeadingConstraint: NSLayoutConstraint? - var checkboxTrailingConstraint: NSLayoutConstraint? var checkboxTopConstraint: NSLayoutConstraint? var checkboxBottomConstraint: NSLayoutConstraint? var checkboxCenterYConstraint: NSLayoutConstraint? - var checkboxLabelConstraint: NSLayoutConstraint? - - var labelLeadingConstraint: NSLayoutConstraint? - var labelTrailingConstraint: NSLayoutConstraint? - var labelTopConstraint: NSLayoutConstraint? - var labelBottomConstraint: NSLayoutConstraint? - //-------------------------------------------------- // MARK: - Life Cycle //-------------------------------------------------- @@ -71,7 +61,27 @@ checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension) checkboxHeightConstraint?.isActive = true - alignSubviews(by: .centerLeft) + checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true + + let generalTop = checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor) + generalTop.priority = UILayoutPriority(rawValue: 750) + generalTop.isActive = true + + let generalBottom = checkbox.bottomAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.bottomAnchor) + generalBottom.priority = UILayoutPriority(rawValue: 750) + generalBottom.isActive = true + + // Allows various positions of checkbox. + checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: checkbox.bottomAnchor) + checkboxTopConstraint = checkbox.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) + checkboxCenterYConstraint = checkbox.centerYAnchor.constraint(equalTo: centerYAnchor) + + label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true + layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true + label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo).isActive = true + layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true + + alignSubviews(by: .center) } //-------------------------------------------------- @@ -95,7 +105,6 @@ public convenience init(position: CheckboxPosition) { self.init(frame: .zero) - toggleSubviewConstraints(isActive: false) alignSubviews(by: position) } @@ -107,74 +116,21 @@ private func alignSubviews(by position: CheckboxPosition) { checkboxPosition = position - label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true - switch position { - case .centerLeft: - checkboxTopConstraint = checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor) - checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor) - checkboxLeadingConstraint = checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - checkboxCenterYConstraint = checkbox.centerYAnchor.constraint(equalTo: centerYAnchor) + case .center: + checkboxCenterYConstraint?.isActive = true + checkboxBottomConstraint?.isActive = false + checkboxTopConstraint?.isActive = false - labelTopConstraint = label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) - labelTrailingConstraint = layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor) - checkboxLabelConstraint = label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo) + case .top: + checkboxBottomConstraint?.isActive = false + checkboxTopConstraint?.isActive = true + checkboxCenterYConstraint?.isActive = false - case .topLeft: - checkboxTopConstraint = checkbox.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) - checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor) - checkboxLeadingConstraint = checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - - labelTopConstraint = label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) - labelTrailingConstraint = layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor) - checkboxLabelConstraint = label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo) - - case .bottomLeft: - checkboxTopConstraint = checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor) - checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: checkbox.bottomAnchor) - checkboxLeadingConstraint = checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) - - labelTopConstraint = label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) - labelTrailingConstraint = layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor) - checkboxLabelConstraint = label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo) - labelBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: label.bottomAnchor) - } - - if position != .bottomLeft { - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true - let labelBottom = label.bottomAnchor.constraint(equalTo: bottomAnchor) - labelBottomConstraint = labelBottom - labelBottom.priority = UILayoutPriority(249) - labelBottom.isActive = true - } - - toggleSubviewConstraints(isActive: true) - } - - func toggleSubviewConstraints(isActive state: Bool) { - - checkboxLeadingConstraint?.isActive = state - checkboxTrailingConstraint?.isActive = state - checkboxTopConstraint?.isActive = state - checkboxBottomConstraint?.isActive = state - checkboxCenterYConstraint?.isActive = state - labelLeadingConstraint?.isActive = state - labelTrailingConstraint?.isActive = state - labelTopConstraint?.isActive = state - labelBottomConstraint?.isActive = state - checkboxLabelConstraint?.isActive = state - - if !state { - checkboxLeadingConstraint = nil - checkboxTrailingConstraint = nil - checkboxTopConstraint = nil - checkboxBottomConstraint = nil - checkboxCenterYConstraint = nil - labelLeadingConstraint = nil - labelTrailingConstraint = nil - labelTopConstraint = nil - labelBottomConstraint = nil - checkboxLabelConstraint = nil + case .bottom: + checkboxBottomConstraint?.isActive = true + checkboxTopConstraint?.isActive = false + checkboxCenterYConstraint?.isActive = false } } } @@ -208,8 +164,6 @@ extension CheckboxWithLabelView { open override func resetConstraints() { super.resetConstraints() - - toggleSubviewConstraints(isActive: false) } override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { @@ -218,7 +172,6 @@ extension CheckboxWithLabelView { guard let dictionary = json else { return } if let checkboxAlignment = dictionary["checkboxAlignment"] as? String, let position = CheckboxPosition(rawValue: checkboxAlignment) { - toggleSubviewConstraints(isActive: false) alignSubviews(by: position) } From 5043964c290074d31cc47c9043a8e36510358e3e Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 3 Oct 2019 16:43:58 -0400 Subject: [PATCH 14/14] revised alignment for case when no label. --- MVMCoreUI/Atoms/Views/Checkbox.swift | 5 ++- .../Atoms/Views/CheckboxWithLabelView.swift | 35 ++++++++++++++----- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Checkbox.swift b/MVMCoreUI/Atoms/Views/Checkbox.swift index 77a1f342..bf8407be 100644 --- a/MVMCoreUI/Atoms/Views/Checkbox.swift +++ b/MVMCoreUI/Atoms/Views/Checkbox.swift @@ -335,7 +335,10 @@ import MVMCore } open func reset() { - setupView() + + backgroundColor = nil + shapeLayer = nil + isSelected = false } open func setAsMolecule() { diff --git a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift index e9aa0d0e..55339e61 100644 --- a/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift +++ b/MVMCoreUI/Atoms/Views/CheckboxWithLabelView.swift @@ -53,7 +53,7 @@ addSubview(checkbox) addSubview(label) - label.setContentCompressionResistancePriority(.required, for: .vertical) + label.text = "" let dimension = sizeObject?.getValueBasedOnApplicationWidth() ?? Checkbox.defaultHeightWidth checkboxWidthConstraint = checkbox.heightAnchor.constraint(equalToConstant: dimension) @@ -64,13 +64,17 @@ checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor).isActive = true let generalTop = checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor) - generalTop.priority = UILayoutPriority(rawValue: 750) + generalTop.priority = UILayoutPriority(800) generalTop.isActive = true let generalBottom = checkbox.bottomAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.bottomAnchor) - generalBottom.priority = UILayoutPriority(rawValue: 750) + generalBottom.priority = UILayoutPriority(800) generalBottom.isActive = true + let checboxBottom = checkbox.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) + checboxBottom.priority = UILayoutPriority(249) + checboxBottom.isActive = true + // Allows various positions of checkbox. checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(equalTo: checkbox.bottomAnchor) checkboxTopConstraint = checkbox.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) @@ -79,9 +83,17 @@ label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo).isActive = true - layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true - alignSubviews(by: .center) + let bottomLabelConstraint = layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor) + bottomLabelConstraint.priority = UILayoutPriority(500) + bottomLabelConstraint.isActive = true + + // layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true + // let labelBottom = label.bottomAnchor.constraint(equalTo: layoutMarginsGuide.bottomAnchor) + // labelBottom.priority = UILayoutPriority(249) + // labelBottom.isActive = true + + alignCheckbox(.center) } //-------------------------------------------------- @@ -105,7 +117,7 @@ public convenience init(position: CheckboxPosition) { self.init(frame: .zero) - alignSubviews(by: position) + alignCheckbox(position) } //-------------------------------------------------- @@ -113,7 +125,7 @@ //-------------------------------------------------- /// Aligns Checkbox and Label relative to the desired position of the Checkbox. - private func alignSubviews(by position: CheckboxPosition) { + private func alignCheckbox(_ position: CheckboxPosition) { checkboxPosition = position switch position { @@ -166,13 +178,20 @@ extension CheckboxWithLabelView { super.resetConstraints() } + open override func reset() { + super.reset() + + label.text = "" + checkbox.reset() + } + override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) guard let dictionary = json else { return } if let checkboxAlignment = dictionary["checkboxAlignment"] as? String, let position = CheckboxPosition(rawValue: checkboxAlignment) { - alignSubviews(by: position) + alignCheckbox(position) } checkbox.setWithJSON(dictionary.dictionaryForKey("checkbox"), delegateObject: delegateObject, additionalData: additionalData)