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, ); }; };
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 = "<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>"; };
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>"; };
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>"; };
@ -654,6 +656,7 @@
D29DF2A021E7AF4E003B2FB9 /* MVMCoreUIUtility.m */,
D29DF2A721E7B2F9003B2FB9 /* MVMCoreUIConstants.h */,
D29DF2A821E7B2F9003B2FB9 /* MVMCoreUIConstants.m */,
0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */,
);
path = Utility;
sourceTree = "<group>";
@ -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 */,

View File

@ -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<UITouch>, 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<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
//--------------------------------------------------
@ -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)
}
}

View File

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

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