Constraints mended. more commenting.

This commit is contained in:
Kevin G Christiano 2019-10-03 13:22:15 -04:00
parent 5d1c87819d
commit 44e420218d
2 changed files with 90 additions and 110 deletions

View File

@ -66,6 +66,7 @@ import MVMCore
} }
} }
/// Retrieves ideeal radius value to curve square into a circle.
public var cornerRadiusValue: CGFloat { public var cornerRadiusValue: CGFloat {
return bounds.size.height / 2 return bounds.size.height / 2
} }
@ -76,6 +77,7 @@ import MVMCore
/// Manages the appearance of the checkbox. /// Manages the appearance of the checkbox.
private var shapeLayer: CAShapeLayer? private var shapeLayer: CAShapeLayer?
/// Width of the check mark.
public var checkWidth: CGFloat = 2 { public var checkWidth: CGFloat = 2 {
didSet { didSet {
CATransaction.withDisabledAnimations { CATransaction.withDisabledAnimations {
@ -83,6 +85,8 @@ import MVMCore
} }
} }
} }
/// Color of the check mark.
public var checkColor: UIColor = .black { public var checkColor: UIColor = .black {
didSet { didSet {
CATransaction.withDisabledAnimations { CATransaction.withDisabledAnimations {
@ -91,24 +95,35 @@ import MVMCore
} }
} }
public var borderWidth: CGFloat = 1 /// Border width of the checkbox
public var borderColor: UIColor = .black 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 { override open var isSelected: Bool {
didSet { didSet {
if !updateSelectionOnly { if !updateSelectionOnly {
layoutIfNeeded() layoutIfNeeded()
shapeLayer?.removeAllAnimations() shapeLayer?.removeAllAnimations()
updateCheckboxUI(selection: isSelected, isAnimated: isAnimated) updateCheckboxUI(isSelected: isSelected, isAnimated: isAnimated)
if let delegate = delegateObject as? FormValidationProtocol { if let delegate = delegateObject as? FormValidationProtocol {
delegate.formValidatorModel?()?.enableByValidation() delegate.formValidatorModel?()?.enableByValidation()
} }
if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") { updateAccessibilityLabel()
accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state") ?? "%@", state)
}
} }
} }
} }
@ -122,9 +137,7 @@ import MVMCore
accessibilityTraits = .button accessibilityTraits = .button
accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint") accessibilityHint = MVMCoreUIUtility.hardcodedString(withKey: "checkbox_action_hint")
if let state = MVMCoreUIUtility.hardcodedString(withKey: isSelected ? "checkbox_checked_state" : "checkbox_unchecked_state") { updateAccessibilityLabel()
accessibilityLabel = String(format: MVMCoreUIUtility.hardcodedString(withKey: "checkbox_desc_state") ?? "%@", state)
}
setupView() setupView()
} }
@ -181,13 +194,16 @@ import MVMCore
open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
super.sendAction(action, to: target, for: event) super.sendAction(action, to: target, for: event)
toggleAndAction()
isSelected.toggle()
actionBlock?()
} }
open override func sendActions(for controlEvents: UIControl.Event) { open override func sendActions(for controlEvents: UIControl.Event) {
super.sendActions(for: controlEvents) super.sendActions(for: controlEvents)
toggleAndAction()
}
/// This will toggle the state of the Checkbox and execute the actionBlock if provided.
public func toggleAndAction() {
isSelected.toggle() isSelected.toggle()
actionBlock?() actionBlock?()
@ -208,7 +224,7 @@ import MVMCore
layer.addSublayer(shapeLayer) layer.addSublayer(shapeLayer)
shapeLayer.strokeColor = checkColor.cgColor shapeLayer.strokeColor = checkColor.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.fillColor = UIColor.clear.cgColor
shapeLayer.path = checkMarkPath.cgPath shapeLayer.path = checkMarkPath()
shapeLayer.lineJoin = .bevel shapeLayer.lineJoin = .bevel
shapeLayer.lineWidth = checkWidth shapeLayer.lineWidth = checkWidth
@ -219,19 +235,19 @@ import MVMCore
} }
/// Returns a UIBezierPath detailing the path of a checkmark /// Returns a UIBezierPath detailing the path of a checkmark
var checkMarkPath: UIBezierPath { func checkMarkPath() -> CGPath {
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)
let pivotOffSet = CGPoint(x: 0.46774 * sideLength, y: 0.64516 * sideLength) let pivotOffSet = CGPoint(x: 0.46774 * sideLength, y: 0.64516 * sideLength)
let endOffset = CGPoint(x: 0.66935 * sideLength , y: 0.37097 * sideLength) let endOffset = CGPoint(x: 0.66935 * sideLength , y: 0.37097 * sideLength)
let path = UIBezierPath() let bezierPath = UIBezierPath()
path.move(to: startPoint) bezierPath.move(to: startPoint)
path.addLine(to: pivotOffSet) bezierPath.addLine(to: pivotOffSet)
path.addLine(to: endOffset) bezierPath.addLine(to: endOffset)
return path return bezierPath.cgPath
} }
/// Programmatic means to check/uncheck the box. /// Programmatic means to check/uncheck the box.
@ -246,11 +262,14 @@ import MVMCore
self.updateSelectionOnly = false self.updateSelectionOnly = false
self.drawCheck() self.drawCheck()
self.shapeLayer?.removeAllAnimations() 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 { if isAnimated {
let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd")
@ -258,19 +277,27 @@ import MVMCore
animateStrokeEnd.duration = 0.3 animateStrokeEnd.duration = 0.3
animateStrokeEnd.fillMode = .both animateStrokeEnd.fillMode = .both
animateStrokeEnd.isRemovedOnCompletion = false animateStrokeEnd.isRemovedOnCompletion = false
animateStrokeEnd.fromValue = !selection ? 1 : 0 animateStrokeEnd.fromValue = !isSelected ? 1 : 0
animateStrokeEnd.toValue = selection ? 1 : 0 animateStrokeEnd.toValue = isSelected ? 1 : 0
self.shapeLayer?.add(animateStrokeEnd, forKey: "strokeEnd") self.shapeLayer?.add(animateStrokeEnd, forKey: "strokeEnd")
UIView.animate(withDuration: 0.2, delay: 0.1, options: .curveEaseOut, animations: { 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 { } else {
CATransaction.withDisabledAnimations { 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)
} }
} }

View File

@ -21,12 +21,12 @@
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 checkboxPosition: CheckboxPosition = .centerLeft var checkboxPosition: CheckboxPosition = .center
public enum CheckboxPosition: String { public enum CheckboxPosition: String {
case centerLeft case center
case topLeft case top
case bottomLeft case bottom
} }
//-------------------------------------------------- //--------------------------------------------------
@ -35,20 +35,10 @@
var checkboxWidthConstraint: NSLayoutConstraint? var checkboxWidthConstraint: NSLayoutConstraint?
var checkboxHeightConstraint: NSLayoutConstraint? var checkboxHeightConstraint: NSLayoutConstraint?
var checkboxLeadingConstraint: NSLayoutConstraint?
var checkboxTrailingConstraint: NSLayoutConstraint?
var checkboxTopConstraint: NSLayoutConstraint? var checkboxTopConstraint: NSLayoutConstraint?
var checkboxBottomConstraint: NSLayoutConstraint? var checkboxBottomConstraint: NSLayoutConstraint?
var checkboxCenterYConstraint: 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
//-------------------------------------------------- //--------------------------------------------------
@ -71,7 +61,27 @@
checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension) checkboxHeightConstraint = checkbox.widthAnchor.constraint(equalToConstant: dimension)
checkboxHeightConstraint?.isActive = true 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) { public convenience init(position: CheckboxPosition) {
self.init(frame: .zero) self.init(frame: .zero)
toggleSubviewConstraints(isActive: false)
alignSubviews(by: position) alignSubviews(by: position)
} }
@ -107,74 +116,21 @@
private func alignSubviews(by position: CheckboxPosition) { private func alignSubviews(by position: CheckboxPosition) {
checkboxPosition = position checkboxPosition = position
label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor).isActive = true
switch position { switch position {
case .centerLeft: case .center:
checkboxTopConstraint = checkbox.topAnchor.constraint(greaterThanOrEqualTo: layoutMarginsGuide.topAnchor) checkboxCenterYConstraint?.isActive = true
checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor) checkboxBottomConstraint?.isActive = false
checkboxLeadingConstraint = checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) checkboxTopConstraint?.isActive = false
checkboxCenterYConstraint = checkbox.centerYAnchor.constraint(equalTo: centerYAnchor)
labelTopConstraint = label.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) case .top:
labelTrailingConstraint = layoutMarginsGuide.trailingAnchor.constraint(equalTo: label.trailingAnchor) checkboxBottomConstraint?.isActive = false
checkboxLabelConstraint = label.leadingAnchor.constraint(equalTo: checkbox.trailingAnchor, constant: PaddingTwo) checkboxTopConstraint?.isActive = true
checkboxCenterYConstraint?.isActive = false
case .topLeft: case .bottom:
checkboxTopConstraint = checkbox.topAnchor.constraint(equalTo: layoutMarginsGuide.topAnchor) checkboxBottomConstraint?.isActive = true
checkboxBottomConstraint = layoutMarginsGuide.bottomAnchor.constraint(greaterThanOrEqualTo: checkbox.bottomAnchor) checkboxTopConstraint?.isActive = false
checkboxLeadingConstraint = checkbox.leadingAnchor.constraint(equalTo: layoutMarginsGuide.leadingAnchor) checkboxCenterYConstraint?.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 .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
} }
} }
} }
@ -208,8 +164,6 @@ extension CheckboxWithLabelView {
open override func resetConstraints() { open override func resetConstraints() {
super.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]?) {
@ -218,7 +172,6 @@ extension CheckboxWithLabelView {
guard let dictionary = json else { return } guard let dictionary = json else { return }
if let checkboxAlignment = dictionary["checkboxAlignment"] as? String, let position = CheckboxPosition(rawValue: checkboxAlignment) { if let checkboxAlignment = dictionary["checkboxAlignment"] as? String, let position = CheckboxPosition(rawValue: checkboxAlignment) {
toggleSubviewConstraints(isActive: false)
alignSubviews(by: position) alignSubviews(by: position)
} }