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