Added a new extension file for CATransaction. Implementation of Checkbox and CheckboxWithLabel.

This commit is contained in:
Kevin G Christiano 2019-10-02 15:22:12 -04:00
parent 5f8780614f
commit 77a3eb8827
4 changed files with 321 additions and 231 deletions

View File

@ -19,6 +19,7 @@
01E569D3223FFFA500327251 /* ThreeLayerViewController.swift in Headers */ = {isa = PBXBuildFile; fileRef = D2A5146A2214905000345BFB /* ThreeLayerViewController.swift */; settings = {ATTRIBUTES = (Public, ); }; }; 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 */; }; 0A1214A022C11A18007C7030 /* ActionDetailWithImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */; };
0A1B4A96233BB18F005B3FB4 /* CheckboxWithLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.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 */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; };
948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; }; 948DB67E2326DCD90011F916 /* MultiProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = 948DB67D2326DCD90011F916 /* MultiProgress.swift */; };
B8200E152280C4CF007245F4 /* ProgressBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8200E142280C4CF007245F4 /* ProgressBar.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 = "<group>"; }; 01DF55DF21F8FAA800CC099B /* MFTextFieldListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MFTextFieldListView.swift; sourceTree = "<group>"; };
01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = "<group>"; }; 01DF566F21FA5AB300CC099B /* TextFieldListFormViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldListFormViewController.swift; sourceTree = "<group>"; };
0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = "<group>"; }; 0A12149F22C11A17007C7030 /* ActionDetailWithImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionDetailWithImage.swift; sourceTree = "<group>"; };
0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CATransaction+Extension.swift"; sourceTree = "<group>"; };
0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = "<group>"; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = "<group>"; };
0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = "<group>"; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = "<group>"; };
948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = "<group>"; }; 948DB67D2326DCD90011F916 /* MultiProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiProgress.swift; sourceTree = "<group>"; };
@ -654,6 +656,7 @@
D29DF2A021E7AF4E003B2FB9 /* MVMCoreUIUtility.m */, D29DF2A021E7AF4E003B2FB9 /* MVMCoreUIUtility.m */,
D29DF2A721E7B2F9003B2FB9 /* MVMCoreUIConstants.h */, D29DF2A721E7B2F9003B2FB9 /* MVMCoreUIConstants.h */,
D29DF2A821E7B2F9003B2FB9 /* MVMCoreUIConstants.m */, D29DF2A821E7B2F9003B2FB9 /* MVMCoreUIConstants.m */,
0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */,
); );
path = Utility; path = Utility;
sourceTree = "<group>"; sourceTree = "<group>";
@ -1095,6 +1098,7 @@
D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */, D29DF25121E6A177003B2FB9 /* MFDigitTextBox.m in Sources */,
DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */, DBC4391B224421A0001AB423 /* CaretButton.swift in Sources */,
0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */, 0198F7A82256A80B0066C936 /* MFRadioButton.m in Sources */,
0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */,
D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */, D29DF13221E6851E003B2FB9 /* MVMCoreUITopAlertBaseView.m in Sources */,
D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */, D29DF29C21E7ADB9003B2FB9 /* MFProgrammaticTableViewController.m in Sources */,
0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */, 0105618E224BBE7700E1557D /* FormValidator+TextFields.swift in Sources */,

View File

@ -8,6 +8,22 @@
import MVMCore 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. This class expects its height and width to be equal.
*/ */
@ -16,85 +32,82 @@ import MVMCore
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
// Form Validation
var isRequired = false
var fieldKey: String?
var delegateObject: DelegateObject?
public static let defaultHeightWidth: CGFloat = 18.0 public static let defaultHeightWidth: CGFloat = 18.0
/// The color of the background when checked. /// If true the border of this checkbox will be circular.
public var checkedBackgroundColor: UIColor? public var isRound: Bool = false
/// The color of the background when unChecked. Will change of the view's background color. /// Determined if the checkbox's UI should animated when selected.
public var unCheckedBackgroundColor: UIColor = .white { public var isAnimated: Bool = true
didSet (newColor) {
backgroundColor = newColor /// 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. /// The color of the background when unChecked.
public var hasRoundedCorners = false public var unCheckedBackgroundColor: UIColor = .white {
didSet {
// Action Block called when the switch is selected. if !isSelected {
public var actionBlock: ActionBlock? backgroundColor = unCheckedBackgroundColor
}
// Internal values to manage the appearance of the checkbox. }
private var shapeLayer: CAShapeLayer? }
public var cornerRadiusValue: CGFloat { public var cornerRadiusValue: CGFloat {
return bounds.size.height / 2 return bounds.size.height / 2
} }
public var lineWidth: CGFloat = 2 /// Action Block called when the switch is selected.
public var lineColor: UIColor = .black public var actionBlock: ActionBlock?
public var borderColor: UIColor = .black
/// 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 borderWidth: CGFloat = 1
public var borderColor: UIColor = .black
override open var isSelected: Bool { override open var isSelected: Bool {
didSet { didSet {
shapeLayer?.removeAllAnimations() if !updateSelectionOnly {
if isSelected { layoutIfNeeded()
if checkedBackgroundColor != nil { shapeLayer?.removeAllAnimations()
UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: {
self.backgroundColor = self.checkedBackgroundColor updateCheckboxUI(selection: isSelected, isAnimated: isAnimated)
})
if let delegate = delegateObject as? FormValidationProtocol {
delegate.formValidatorModel?()?.enableByValidation()
} }
shapeLayer?.add(checkedAnimation, forKey: "strokeEnd")
} else { accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state")
if checkedBackgroundColor != nil {
UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: {
self.backgroundColor = self.unCheckedBackgroundColor
})
}
shapeLayer?.add(uncheckedAnimation, forKey: "strokeEnd")
} }
} }
} }
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 // MARK: - Initializers
//-------------------------------------------------- //--------------------------------------------------
override public init(frame: CGRect) { override public init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
accessibilityTraits = .none
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint")
setupView() setupView()
} }
@ -108,16 +121,20 @@ import MVMCore
self.init(frame:.zero) self.init(frame:.zero)
} }
public convenience init(checkedColor: UIColor, unCheckedColor: UIColor, isChecked: Bool = false) { public convenience init(isChecked: Bool) {
self.init(frame: .zero) self.init(frame: .zero)
updateSelectionOnly = true
isSelected = isChecked isSelected = isChecked
self.checkedBackgroundColor = checkedColor updateSelectionOnly = false
self.unCheckedBackgroundColor = unCheckedColor
} }
public convenience init(isCircular: Bool) { public convenience init(checkedBackgroundColor: UIColor, unCheckedBackgroundColor: UIColor, isChecked: Bool = false) {
self.init(frame: .zero) 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() { override open func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
drawCheck() drawCheck()
layer.cornerRadius = hasRoundedCorners ? cornerRadiusValue : 0 layer.cornerRadius = isRound ? cornerRadiusValue : 0
backgroundColor = unCheckedBackgroundColor
layer.borderWidth = borderWidth layer.borderWidth = borderWidth
layer.borderColor = borderColor.cgColor layer.borderColor = borderColor.cgColor
} }
@ -137,6 +154,7 @@ import MVMCore
isUserInteractionEnabled = true isUserInteractionEnabled = true
translatesAutoresizingMaskIntoConstraints = false translatesAutoresizingMaskIntoConstraints = false
backgroundColor = .white
} }
//-------------------------------------------------- //--------------------------------------------------
@ -161,6 +179,7 @@ import MVMCore
// MARK: - Methods // MARK: - Methods
//-------------------------------------------------- //--------------------------------------------------
/// Creates the check mark used for the checkbox.
private func drawCheck() { private func drawCheck() {
if shapeLayer == nil { if shapeLayer == nil {
@ -169,36 +188,20 @@ import MVMCore
self.shapeLayer = shapeLayer self.shapeLayer = shapeLayer
shapeLayer.frame = bounds shapeLayer.frame = bounds
layer.addSublayer(shapeLayer) layer.addSublayer(shapeLayer)
shapeLayer.strokeColor = lineColor.cgColor shapeLayer.strokeColor = checkColor.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.path = checkMarkBezierPath.cgPath shapeLayer.path = checkMarkPath.cgPath
shapeLayer.lineJoin = .bevel shapeLayer.lineJoin = .bevel
shapeLayer.lineWidth = lineWidth shapeLayer.lineWidth = checkWidth
CATransaction.withDisabledAnimations { 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 /// Returns a UIBezierPath detailing the path of a checkmark
var checkMarkBezierPath: UIBezierPath { var checkMarkPath: UIBezierPath {
let sideLength = bounds.size.height let sideLength = bounds.size.height
let startPoint = CGPoint(x: 0.33871 * sideLength, y: 0.53225 * sideLength) let startPoint = CGPoint(x: 0.33871 * sideLength, y: 0.53225 * sideLength)
@ -213,38 +216,43 @@ import MVMCore
return path return path
} }
public func updateSelection(_ selected: Bool, animated: Bool) { /// Programmatic means to check/uncheck the box.
/// - parameter selected: state of the check box: true = checked OR false = unchecked.
// shapeLayer?.removeFromSuperlayer() /// - parameter animated: allows the state of the checkbox to change with or without animation.
// shapeLayer = nil public func updateSelection(to selected: Bool, animated: Bool) {
DispatchQueue.main.async { DispatchQueue.main.async {
self.updateSelectionOnly = true
self.isSelected = selected self.isSelected = selected
self.updateSelectionOnly = false
self.drawCheck() 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? UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: {
if let presentation = self.shapeLayer?.presentation(), animated { self.backgroundColor = selection ? self.checkedBackgroundColor : self.unCheckedBackgroundColor
layer = presentation })
} else { } else {
layer = self.shapeLayer CATransaction.withDisabledAnimations {
self.shapeLayer?.strokeEnd = selection ? 1 : 0
} }
if animated { self.backgroundColor = selection ? self.checkedBackgroundColor : self.unCheckedBackgroundColor
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
}
}
} }
} }
@ -254,11 +262,7 @@ import MVMCore
open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { open override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if touchIsAcceptablyOutside(touches.first) { sendActions(for: touchIsAcceptablyOutside(touches.first) ? .touchUpOutside : .touchUpInside)
sendActions(for: .touchUpOutside)
} else {
sendActions(for: .touchUpInside)
}
} }
func touchIsAcceptablyOutside(_ touch: UITouch?) -> Bool { func touchIsAcceptablyOutside(_ touch: UITouch?) -> Bool {
@ -272,40 +276,6 @@ import MVMCore
return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt return x < -faultTolerance || y < -faultTolerance || x > widthLimit || y > heightLimt
} }
// if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged<AnyObject>.formValidatorModel)) {
// let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged<AnyObject>.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 // MARK: - Molecular
//-------------------------------------------------- //--------------------------------------------------
@ -322,15 +292,22 @@ import MVMCore
setupView() setupView()
} }
public func updateView(_ size: CGFloat) { public func updateView(_ size: CGFloat) { }
// TODO: Ensure the check logic is resized.
}
public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { 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 } 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 { if let borderColorHex = dictionary["borderColor"] as? String {
layer.borderColor = UIColor.mfGet(forHex: borderColorHex).cgColor layer.borderColor = UIColor.mfGet(forHex: borderColorHex).cgColor
} }
@ -339,36 +316,50 @@ import MVMCore
layer.borderWidth = borderWidth layer.borderWidth = borderWidth
} }
if let checkColorHex = dictionary["lineColor"] as? String { if let isChecked = dictionary["isChecked"] as? Bool, isChecked {
lineColor = UIColor.mfGet(forHex: checkColorHex) updateSelectionOnly = true
isSelected = isChecked
updateSelectionOnly = false
} }
if let checkColorHex = dictionary["checkedColor"] as? String { if let checkColorHex = dictionary["checkColor"] as? String {
checkedBackgroundColor = UIColor.mfGet(forHex: checkColorHex) checkColor = UIColor.mfGet(forHex: checkColorHex)
} }
if let unCheckedColorHex = dictionary["unCheckedColor"] as? String { if let unCheckedBackgroundColorHex = dictionary["unCheckedBackgroundColor"] as? String {
unCheckedBackgroundColor = UIColor.mfGet(forHex: unCheckedColorHex) 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 { if let isRound = dictionary["isRound"] as? Bool {
hasRoundedCorners = isRound self.isRound = isRound
} }
// if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { if let actionMap = dictionary.optionalDictionaryForKey("actionMap") {
// actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) }
// } }
} }
} }
// TODO: Move to its own extension file. // MARK:- FormValidationProtocol
extension CATransaction { extension Checkbox: FormValidationProtocol {
/// Performs changes without activating animation actions. public func isValidField() -> Bool {
static func withDisabledAnimations(_ actionBlock: ActionBlock) { return isRequired ? isSelected : true
CATransaction.begin() }
CATransaction.setDisableActions(true)
actionBlock() public func formFieldName() -> String? {
CATransaction.commit() return fieldKey
}
public func formFieldValue() -> Any? {
return NSNumber(value: isSelected)
} }
} }

View File

@ -12,8 +12,8 @@
// MARK: - Outlets // MARK: - Outlets
//-------------------------------------------------- //--------------------------------------------------
let checkbox = Checkbox() public let checkbox = Checkbox()
let label = Label.commonLabelB2(true) public let label = Label.commonLabelB2(true)
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
@ -21,17 +21,34 @@
var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0) var sizeObject: MFSizeObject? = MFSizeObject(standardSize: Checkbox.defaultHeightWidth, standardiPadPortraitSize: Checkbox.defaultHeightWidth + 6.0)
var isRequired = false var checkboxPosition: CheckboxPosition = .centerLeft
var fieldKey: String?
var delegate: DelegateObject? public enum CheckboxPosition: String {
case centerLeft
case topLeft
case bottomLeft
}
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Constraints // MARK: - Constraints
//-------------------------------------------------- //--------------------------------------------------
var checkboxWidthConstraint: NSLayoutConstraint? var checkboxWidthConstraint: NSLayoutConstraint?
var checkboxHeightConstraint: 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 // MARK: - Life Cycle
//-------------------------------------------------- //--------------------------------------------------
@ -46,23 +63,24 @@
addSubview(checkbox) addSubview(checkbox)
addSubview(label) addSubview(label)
label.setContentCompressionResistancePriority(.required, for: .vertical)
let dimension = sizeObject?.getValueBasedOnApplicationWidth() ?? Checkbox.defaultHeightWidth let dimension = sizeObject?.getValueBasedOnApplicationWidth() ?? Checkbox.defaultHeightWidth
checkboxWidthConstraint = checkbox.heightAnchor.constraint(equalToConstant: dimension) checkboxWidthConstraint = checkbox.heightAnchor.constraint(equalToConstant: dimension)
checkboxWidthConstraint?.isActive = true checkboxWidthConstraint?.isActive = true
checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension) checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension)
checkboxHeightConstraint?.isActive = true checkboxHeightConstraint?.isActive = true
checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor).isActive = true alignSubviews(by: .centerLeft)
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 label.isUserInteractionEnabled = true
layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: label.bottomAnchor).isActive = true let tap = UITapGestureRecognizer(target: self, action: #selector(tapped))
label.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true tap.numberOfTapsRequired = 1
layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor).isActive = true label.addGestureRecognizer(tap)
label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo).isActive = true }
@objc func tapped() {
checkbox.updateSelection(to: !checkbox.isSelected, animated: true)
} }
//-------------------------------------------------- //--------------------------------------------------
@ -77,13 +95,18 @@
override public init(frame: CGRect) { override public init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
setupView() setupView()
// addAccessibleProperties()
} }
public convenience init() { public convenience init() {
self.init(frame: .zero) 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) { public convenience init(checkedColor: UIColor, unCheckedColor: UIColor, text: String?, isChecked: Bool = false) {
self.init(frame: .zero) self.init(frame: .zero)
@ -99,79 +122,130 @@
checkbox.unCheckedBackgroundColor = unCheckedColor checkbox.unCheckedBackgroundColor = unCheckedColor
label.attributedText = attributedText 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 { 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) return CGFloat(Checkbox.defaultHeightWidth)
} }
@objc override open func updateView(_ size: CGFloat) { @objc override open func updateView(_ size: CGFloat) {
DispatchQueue.main.async { label.updateView(size)
self.label.updateView(size)
if self.checkbox.responds(to: #selector(self.updateView(_:))) { if self.checkbox.responds(to: #selector(self.updateView(_:))) {
if let dimension = self.sizeObject?.getValueBased(onSize: size) { if let dimension = sizeObject?.getValueBased(onSize: size) {
self.checkboxWidthConstraint?.constant = dimension checkboxWidthConstraint?.constant = dimension
self.checkboxHeightConstraint?.constant = dimension checkboxHeightConstraint?.constant = dimension
self.checkbox.updateView(size) checkbox.updateView(size)
}
} }
} }
layoutIfNeeded()
} }
override open func alignment() -> UIStackView.Alignment { override open func alignment() -> UIStackView.Alignment {
return .leading return .leading
} }
open override func resetConstraints() {
super.resetConstraints()
toggleSubviewConstraints(isActive: false)
}
override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { override open func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) {
super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData)
delegate = delegateObject
FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol)
guard let dictionary = json else { return } guard let dictionary = json else { return }
if let fieldKey = dictionary[KeyFieldKey] as? String { if let checkboxAlignment = dictionary["checkboxAlignment"] as? String, let position = CheckboxPosition(rawValue: checkboxAlignment) {
self.fieldKey = fieldKey toggleSubviewConstraints(isActive: false)
} alignSubviews(by: position)
if let isRequired = dictionary[KeyRequired] as? Bool {
self.isRequired = isRequired
} }
checkbox.setWithJSON(dictionary.dictionaryForKey("checkbox"), delegateObject: delegateObject, additionalData: additionalData) checkbox.setWithJSON(dictionary.dictionaryForKey("checkbox"), delegateObject: delegateObject, additionalData: additionalData)
label.setWithJSON(dictionary.dictionaryForKey("label"), 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)
}
}

View File

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