From 8a796467e66ea0d3c90bbe0236e7e200b1d10602 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 4 Dec 2019 15:50:08 -0500 Subject: [PATCH 01/18] Initial state of Swiftified Switch. --- MVMCoreUI.xcodeproj/project.pbxproj | 4 + MVMCoreUI/Atoms/Views/Switch.swift | 376 ++++++++++++++++++++++++++++ 2 files changed, 380 insertions(+) create mode 100644 MVMCoreUI/Atoms/Views/Switch.swift diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 900edec2..33529648 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; + 0AA33B3A2398524F0067DD0F /* Switch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B392398524F0067DD0F /* Switch.swift */; }; 943784F5236B77BB006A1E82 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* GraphView.swift */; }; 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; @@ -226,6 +227,7 @@ 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; + 0AA33B392398524F0067DD0F /* Switch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Switch.swift; sourceTree = ""; }; 943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = ""; }; 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.swift; sourceTree = ""; }; 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; @@ -779,6 +781,7 @@ 01004F2F22721C3800991ECC /* RadioButton.swift */, 943784F3236B77BB006A1E82 /* GraphView.swift */, 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */, + 0AA33B392398524F0067DD0F /* Switch.swift */, ); path = Views; sourceTree = ""; @@ -1082,6 +1085,7 @@ D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */, D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */, D2B18B7F2360913400A9AEDC /* Control.swift in Sources */, + 0AA33B3A2398524F0067DD0F /* Switch.swift in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, DBC4392122491730001AB423 /* LabelWithInternalButton.swift in Sources */, D224798C231450C8003FCCF9 /* HeadlineBodySwitch.swift in Sources */, diff --git a/MVMCoreUI/Atoms/Views/Switch.swift b/MVMCoreUI/Atoms/Views/Switch.swift new file mode 100644 index 00000000..d7e9b25e --- /dev/null +++ b/MVMCoreUI/Atoms/Views/Switch.swift @@ -0,0 +1,376 @@ +// +// Switch.swift +// MVMCoreUI +// +// Created by Kevin Christiano on 12/4/19. +// Copyright © 2019 Verizon Wireless. All rights reserved. +// + +import MVMCore +import MVMCoreUI +import UIKit + +typealias ValueChangeBlock = () -> Void + +open class Switch: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol { + var on = false + var onTintColor: UIColor? + var offTintColor: UIColor? + var onKnobTintColor: UIColor? + var offKnobTintColor: UIColor? + var shouldTouchToSwitch = false + var valueChangedBlock: ValueChangeBlock? + var actionBlock: ValueChangeBlock? + + let SwitchWidth: CGFloat = 42 + let SwitchHeight: CGFloat = 22 + let SwitchKnobWidth: CGFloat = 20 + let SwitchKnobHeight: CGFloat = 20 + let SwitchShakeIntensity: CGFloat = 2 + + private weak var baseView: UIView? + private weak var knobView: UIView? + private var knobLeftConstraint: NSLayoutConstraint? + private var knobRightConstraint: NSLayoutConstraint? + private var knobHeightConstraint: NSLayoutConstraint? + private var knobWidthConstraint: NSLayoutConstraint? + private weak var height: NSLayoutConstraint? + private weak var width: NSLayoutConstraint? + private var valueShouldChange = false + private var canChangeValue = false + private var json: [AnyHashable: Any]? + private var delegate: DelegateObject? + + public func updateView(_ size: CGFloat) { + height?.constant = MVMCoreUISwitch.getHeight() + width?.constant = MVMCoreUISwitch.getWidth() + baseView?.layer.cornerRadius = MVMCoreUISwitch.getHeight() / 2.0 + knobView?.layer.cornerRadius = MVMCoreUISwitch.getKnobHeight() * 0.5 + knobHeightConstraint?.constant = MVMCoreUISwitch.getKnobHeight() + knobWidthConstraint?.constant = MVMCoreUISwitch.getKnobWidth() + } + + public func setupView() { + onTintColor = UIColor.mfSwitchOnTint() + offTintColor = UIColor.mfSwitchOffTint() + canChangeValue = true + shouldTouchToSwitch = true + setUpViewWithState(on) + accessibilityLabel() = MVMCoreUIUtility.hardcodedString(withKey: "MVMCoreUISwitch_buttonlabel") + } + + public required init?(coder: NSCoder) { + if super.init(coder: coder) != nil { + setupView() + } + } + + public init(frame: CGRect) { + if super.init(frame: frame) != nil { + setupView() + } + } + + public init() { + if super.init() { + setupView() + } + } + + public class func mvmSwitchDefault() -> Self { + let mySwitch = self.init(frame: CGRect.zero) + mySwitch?.translatesAutoresizingMaskIntoConstraints = false + return mySwitch + } + + public class func mvmSwitchDefault(with block: ValueChangeBlock?) -> Self { + let mySwitch = self.init(frame: CGRect.zero) + mySwitch?.valueChangedBlock = block + return mySwitch + } + + public func setUpViewWithState(_ on: Bool) { + if !self.baseView { + isUserInteractionEnabled = true + valueShouldChange = true + + let baseView = MVMCoreUICommonViewsUtility.commonView() + if let baseView = baseView { + addSubview(baseView) + } + NSLayoutConstraint.constraintPinSubview(toSuperview: baseView) + let constraints = NSLayoutConstraint.constraintPinView(baseView, heightConstraint: true, heightConstant: MVMCoreUISwitch.getHeight(), widthConstraint: true, widthConstant: MVMCoreUISwitch.getWidth()) + self.height = constraints.object(forKey: ConstraintHeight, ofType: NSLayoutConstraint.self) + self.width = constraints.object(forKey: ConstraintWidth, ofType: NSLayoutConstraint.self) + baseView?.layer.cornerRadius = MVMCoreUISwitch.getHeight() / 2.0 + + onKnobTintColor = UIColor.white + offKnobTintColor = UIColor.white + + let knobView = MVMCoreUICommonViewsUtility.commonView() + knobView?.backgroundColor = UIColor.white + knobView?.layer.cornerRadius = MVMCoreUISwitch.getKnobHeight() * 0.5 + if let knobView = knobView { + baseView?.addSubview(knobView) + } + let heightWidth = NSLayoutConstraint.constraintPinView(knobView, heightConstraint: true, heightConstant: MVMCoreUISwitch.getKnobHeight(), widthConstraint: true, widthConstant: MVMCoreUISwitch.getKnobWidth()) + let height = heightWidth.object(forKey: ConstraintHeight, ofType: NSLayoutConstraint.self) + let width = heightWidth.object(forKey: ConstraintWidth, ofType: NSLayoutConstraint.self) + knobHeightConstraint = height + knobWidthConstraint = width + let leadingTrailingDic = NSLayoutConstraint.constraintPinSubview(knobView, pinTop: false, topConstant: 0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 1, pinRight: true, rightConstant: 1) + + let leadingTrailingDic = NSLayoutConstraint.constraintPinSubview(knobView, pinTop: false, topConstant: 0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 1, pinRight: true, rightConstant: 1) + let `left` = leadingTrailingDic.object(forKey: ConstraintLeading, ofType: NSLayoutConstraint.self) + let `right` = leadingTrailingDic.object(forKey: ConstraintTrailing, ofType: NSLayoutConstraint.self) + NSLayoutConstraint.constraintPinSubview(knobView, pinCenterX: false, centerXConstant: 0, pinCenterY: true, centerYConstant: 0) + `right`?.constant = 15 + knobLeftConstraint = `left` + knobRightConstraint = `right` + baseView.bringSubviewToFront(knobView) + + //baseView = baseView // Skipping redundant initializing to itself + //knobView = knobView // Skipping redundant initializing to itself + + baseView.isUserInteractionEnabled = false + knobView.isUserInteractionEnabled = false + shouldTouchToSwitch = false + setState(on, animated: false) + shouldTouchToSwitch = true + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + self.json = json + delegate = delegateObject + + FormValidator.setupValidation(withMolecule: self, delegate: delegateObject?.formValidationProtocol) + + var color = json?.string("onTintColor") + if color != nil { + onTintColor = UIColor.mfGet(forHex: color) + } + + color = json?.string("offTintColor") + if color != nil { + offTintColor = UIColor.mfGet(forHex: color) + } + + color = json?.string("onKnobTintColor") + if color != nil { + onKnobTintColor = UIColor.mfGet(forHex: color) + } + + color = json?.string("offKnobTintColor") + if color != nil { + offKnobTintColor = UIColor.mfGet(forHex: color) + } + + setState(json?.bool(forKey: "state"), animated: false) + + let actionMap = json?.dict("actionMap") + if actionMap != nil { + addTarget(self, action: #selector(addCustomAction), for: .touchUpInside) + } + } + + public @objc func addCustomAction() { + if actionBlock { + actionBlock() + } + } + + public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + return self.getSwitchHeight() + } + + // MARK: - MVMCoreUIMoleculeViewProtocol + public func needsToBeConstrained() -> Bool { + return true + } + + public func alignment() -> UIStackView.Alignment { + return .trailing + } + + // MARK: - UIResponder overide + public func touchesBegan(_ touches: Set, with event: UIEvent) { + knobEnlargeAnimation() + sendActions(for: .touchDown) + } + + public func touchesEnded(_ touches: Set, with event: UIEvent) { + if shouldTouchToSwitch { + if valueShouldChange { + changeValue() + } + } + sendActions(for: .touchUpInside) + knobMoveAnitmationTo(on: isOn) + knobShakeAnitmationTo(on: isOn) + knobReformAnimation(true) + valueShouldChange = true + } + + public func touchesMoved(_ touches: Set, with event: UIEvent) { + if shouldTouchToSwitch { + if touchMoves(toLeft: touches) { + knobMoveAnitmationTo(on: false) + knobShakeAnitmationTo(on: false) + knobReformAnimation(true) + } else { + knobMoveAnitmationTo(on: true) + knobShakeAnitmationTo(on: true) + knobReformAnimation(true) + } + } + if touchIsOutSide(touches) { + sendActions(for: .touchDragOutside) + if !shouldTouchToSwitch { + knobReformAnimation(true) + } + } else { + sendActions(for: .touchDragInside) + } + } + + public func touchesCancelled(_ touches: Set, with event: UIEvent) { + if shouldTouchToSwitch { + knobReformAnimation(true) + canChangeValue = true + } + sendActions(for: .touchCancel) + } + + // MARK: - animation + public func knobEnlargeAnimation() { + UIView.animate(withDuration: 0.1, animations: { + self.knobWidthConstraint.constant += PaddingOne + self.layoutIfNeeded() + }) + } + + public func knobReformAnimation(_ animated: Bool) { + if animated { + UIView.animate(withDuration: 0.1, animations: { + self.knobWidthConstraint.constant = MVMCoreUISwitch.getKnobWidth() + self.layoutIfNeeded() + }) { finished in + } + } else { + knobWidthConstraint.constant = MVMCoreUISwitch.getKnobWidth() + layoutIfNeeded() + } + } + + public func knobMoveAnitmationTo(on toOn: Bool) { + UIView.animate(withDuration: 0.1, animations: { + if toOn { + self.knobLeftConstraint.priority = 1 + self.knobRightConstraint.priority = 999 + self.knobRightConstraint.constant = 1 + } else { + self.knobLeftConstraint.priority = 999 + self.knobRightConstraint.priority = 1 + self.knobLeftConstraint.constant = 1 + } + self.setBaseColorToOn(toOn, animated: true) + self.knobWidthConstraint.constant = MVMCoreUISwitch.getKnobWidth() + PaddingOne + self.valueShouldChange = toOn != self.on + self.layoutIfNeeded() + }) + } + + public func setBaseColorToOn(_ toOn: Bool, animated: Bool) { + if animated { + UIView.beginAnimations(nil, context: nil) + UIView.setAnimationDuration(0.2) + UIView.setAnimationCurve(.easeIn) + if toOn { + knobView.backgroundColor = onKnobTintColor + baseView.backgroundColor = onTintColor + } else { + knobView.backgroundColor = offKnobTintColor + baseView.backgroundColor = offTintColor + } + UIView.commitAnimations() + } else if on { + knobView.backgroundColor = onKnobTintColor + baseView.backgroundColor = onTintColor + } else { + knobView.backgroundColor = offKnobTintColor + baseView.backgroundColor = offTintColor + } + } + + //used after knob moving to match the behavior of default uiswitch + public func knobShakeAnitmationTo(on toOn: Bool) { + UIView.animate(withDuration: 0.1, delay: 0.1, options: .curveEaseIn, animations: { + if toOn { + self.knobRightConstraint.constant = SwitchShakeIntensity + } else { + self.knobLeftConstraint.constant = SwitchShakeIntensity + } + self.layoutIfNeeded() + }) { finished in + } + UIView.animate(withDuration: 0.2, delay: 0.1, options: [], animations: { + if toOn { + self.knobRightConstraint.constant = 1 + } else { + self.knobLeftConstraint.constant = 1 + } + self.layoutIfNeeded() + }) { finished in + self.valueShouldChange = true + } + } + + // MARK: - switch logic + public func setState(_ state: Bool, animated: Bool) { + setState(state, withoutBlockAnimated: animated) + if valueChangedBlock { + valueChangedBlock() + } + + if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { + let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator + formValidator?.enableByValidation() + } + } + + public func setState(_ state: Bool, withoutBlockAnimated animated: Bool) { + on = state + if !shouldTouchToSwitch { + knobEnlargeAnimation() + knobMoveAnitmationTo(on: on) + knobShakeAnitmationTo(on: on) + } + if on { + knobLeftConstraint.priority = 1 + knobRightConstraint.priority = 999 + knobRightConstraint.constant = 1 + } else { + knobRightConstraint.priority = 1 + knobLeftConstraint.priority = 999 + knobLeftConstraint.constant = 1 + } + + setBaseColorToOn(on, animated: animated) + knobReformAnimation(animated) + accessibilityValue = state ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") + setNeedsLayout() + layoutIfNeeded() + } + + public func changeValue() -> Bool { + on ^= true + shouldTouchToSwitch = false + setState(on, animated: true) + shouldTouchToSwitch = true + sendActions(for: .valueChanged) + return on + } + } +} + From a00fdbad6041289fef25710bb3f2b2535d7fed7a Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 5 Dec 2019 15:16:46 -0500 Subject: [PATCH 02/18] Grooming process in converting objc to swift with Switch. --- MVMCoreUI/Atoms/Views/Switch.swift | 709 ++++++++++++++++------------ MVMCoreUI/BaseClasses/Control.swift | 5 +- 2 files changed, 406 insertions(+), 308 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Switch.swift b/MVMCoreUI/Atoms/Views/Switch.swift index d7e9b25e..e12fd890 100644 --- a/MVMCoreUI/Atoms/Views/Switch.swift +++ b/MVMCoreUI/Atoms/Views/Switch.swift @@ -10,367 +10,464 @@ import MVMCore import MVMCoreUI import UIKit -typealias ValueChangeBlock = () -> Void +typealias ValueChangeBlock = () -> () -open class Switch: UIControl, MVMCoreViewProtocol, MVMCoreUIMoleculeViewProtocol, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol { - var on = false - var onTintColor: UIColor? - var offTintColor: UIColor? - var onKnobTintColor: UIColor? - var offKnobTintColor: UIColor? +open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- + + public var isOn = false + public var trackTintColor: (on: UIColor?, off: UIColor?)? + public var thumbTintColor: (on: UIColor?, off: UIColor?)? + var shouldTouchToSwitch = false var valueChangedBlock: ValueChangeBlock? var actionBlock: ValueChangeBlock? - let SwitchWidth: CGFloat = 42 - let SwitchHeight: CGFloat = 22 - let SwitchKnobWidth: CGFloat = 20 - let SwitchKnobHeight: CGFloat = 20 - let SwitchShakeIntensity: CGFloat = 2 + static let width: CGFloat = 42 + static let height: CGFloat = 22 + static let thumbWidth: CGFloat = 20 + static let thumbHeight: CGFloat = 20 + static let shakeIntensity: CGFloat = 2 private weak var baseView: UIView? - private weak var knobView: UIView? - private var knobLeftConstraint: NSLayoutConstraint? - private var knobRightConstraint: NSLayoutConstraint? - private var knobHeightConstraint: NSLayoutConstraint? - private var knobWidthConstraint: NSLayoutConstraint? - private weak var height: NSLayoutConstraint? - private weak var width: NSLayoutConstraint? + private weak var thumbView: UIView? + private var valueShouldChange = false private var canChangeValue = false - private var json: [AnyHashable: Any]? + + //-------------------------------------------------- + // MARK: - Delegate + //-------------------------------------------------- + private var delegate: DelegateObject? + //-------------------------------------------------- + // MARK: - Constraints + //-------------------------------------------------- + + private var thumbLeftConstraint: NSLayoutConstraint? + private var thumbRightConstraint: NSLayoutConstraint? + private var thumbHeightConstraint: NSLayoutConstraint? + private var thumbWidthConstraint: NSLayoutConstraint? + private weak var height: NSLayoutConstraint? + private weak var width: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + public func updateView(_ size: CGFloat) { + height?.constant = MVMCoreUISwitch.getHeight() width?.constant = MVMCoreUISwitch.getWidth() baseView?.layer.cornerRadius = MVMCoreUISwitch.getHeight() / 2.0 - knobView?.layer.cornerRadius = MVMCoreUISwitch.getKnobHeight() * 0.5 - knobHeightConstraint?.constant = MVMCoreUISwitch.getKnobHeight() - knobWidthConstraint?.constant = MVMCoreUISwitch.getKnobWidth() + thumbView?.layer.cornerRadius = MVMCoreUISwitch.getthumbHeight() * 0.5 + thumbHeightConstraint?.constant = MVMCoreUISwitch.getthumbHeight() + thumbWidthConstraint?.constant = MVMCoreUISwitch.getthumbWidth() } public func setupView() { - onTintColor = UIColor.mfSwitchOnTint() - offTintColor = UIColor.mfSwitchOffTint() + + trackTintColor?.on = .mfSwitchOnTint() + trackTintColor?.off = .mfSwitchOffTint() canChangeValue = true shouldTouchToSwitch = true - setUpViewWithState(on) - accessibilityLabel() = MVMCoreUIUtility.hardcodedString(withKey: "MVMCoreUISwitch_buttonlabel") + setUpViewWithState(isOn) + accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "MVMCoreUISwitch_buttonlabel") + } + + //-------------------------------------------------- + // MARK: - Initializers + //-------------------------------------------------- + + public override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + public convenience init() { + self.init(frame: frame) } public required init?(coder: NSCoder) { - if super.init(coder: coder) != nil { - setupView() - } + super.init(coder: coder) + fatalError("Switch does not support xib") } - public init(frame: CGRect) { - if super.init(frame: frame) != nil { - setupView() - } - } - - public init() { - if super.init() { - setupView() - } - } + //-------------------------------------------------- + // MARK: - Methods + //-------------------------------------------------- public class func mvmSwitchDefault() -> Self { - let mySwitch = self.init(frame: CGRect.zero) + + let mySwitch = self.init(frame: .zero) mySwitch?.translatesAutoresizingMaskIntoConstraints = false return mySwitch } public class func mvmSwitchDefault(with block: ValueChangeBlock?) -> Self { - let mySwitch = self.init(frame: CGRect.zero) + + let mySwitch = self.init(frame: .zero) mySwitch?.valueChangedBlock = block return mySwitch } public func setUpViewWithState(_ on: Bool) { - if !self.baseView { - isUserInteractionEnabled = true - valueShouldChange = true - - let baseView = MVMCoreUICommonViewsUtility.commonView() - if let baseView = baseView { - addSubview(baseView) - } - NSLayoutConstraint.constraintPinSubview(toSuperview: baseView) - let constraints = NSLayoutConstraint.constraintPinView(baseView, heightConstraint: true, heightConstant: MVMCoreUISwitch.getHeight(), widthConstraint: true, widthConstant: MVMCoreUISwitch.getWidth()) - self.height = constraints.object(forKey: ConstraintHeight, ofType: NSLayoutConstraint.self) - self.width = constraints.object(forKey: ConstraintWidth, ofType: NSLayoutConstraint.self) - baseView?.layer.cornerRadius = MVMCoreUISwitch.getHeight() / 2.0 - - onKnobTintColor = UIColor.white - offKnobTintColor = UIColor.white - - let knobView = MVMCoreUICommonViewsUtility.commonView() - knobView?.backgroundColor = UIColor.white - knobView?.layer.cornerRadius = MVMCoreUISwitch.getKnobHeight() * 0.5 - if let knobView = knobView { - baseView?.addSubview(knobView) - } - let heightWidth = NSLayoutConstraint.constraintPinView(knobView, heightConstraint: true, heightConstant: MVMCoreUISwitch.getKnobHeight(), widthConstraint: true, widthConstant: MVMCoreUISwitch.getKnobWidth()) - let height = heightWidth.object(forKey: ConstraintHeight, ofType: NSLayoutConstraint.self) - let width = heightWidth.object(forKey: ConstraintWidth, ofType: NSLayoutConstraint.self) - knobHeightConstraint = height - knobWidthConstraint = width - let leadingTrailingDic = NSLayoutConstraint.constraintPinSubview(knobView, pinTop: false, topConstant: 0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 1, pinRight: true, rightConstant: 1) - - let leadingTrailingDic = NSLayoutConstraint.constraintPinSubview(knobView, pinTop: false, topConstant: 0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 1, pinRight: true, rightConstant: 1) - let `left` = leadingTrailingDic.object(forKey: ConstraintLeading, ofType: NSLayoutConstraint.self) - let `right` = leadingTrailingDic.object(forKey: ConstraintTrailing, ofType: NSLayoutConstraint.self) - NSLayoutConstraint.constraintPinSubview(knobView, pinCenterX: false, centerXConstant: 0, pinCenterY: true, centerYConstant: 0) - `right`?.constant = 15 - knobLeftConstraint = `left` - knobRightConstraint = `right` - baseView.bringSubviewToFront(knobView) - - //baseView = baseView // Skipping redundant initializing to itself - //knobView = knobView // Skipping redundant initializing to itself - - baseView.isUserInteractionEnabled = false - knobView.isUserInteractionEnabled = false - shouldTouchToSwitch = false - setState(on, animated: false) - shouldTouchToSwitch = true + + guard baseView == nil else { return } + + isUserInteractionEnabled = true + valueShouldChange = true + + let baseView = MVMCoreUICommonViewsUtility.commonView() + + if let baseView = baseView { + addSubview(baseView) } - // MARK: - MVMCoreUIMoleculeViewProtocol - public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - self.json = json - delegate = delegateObject - - FormValidator.setupValidation(withMolecule: self, delegate: delegateObject?.formValidationProtocol) - - var color = json?.string("onTintColor") - if color != nil { - onTintColor = UIColor.mfGet(forHex: color) - } - - color = json?.string("offTintColor") - if color != nil { - offTintColor = UIColor.mfGet(forHex: color) - } - - color = json?.string("onKnobTintColor") - if color != nil { - onKnobTintColor = UIColor.mfGet(forHex: color) - } - - color = json?.string("offKnobTintColor") - if color != nil { - offKnobTintColor = UIColor.mfGet(forHex: color) - } - - setState(json?.bool(forKey: "state"), animated: false) - - let actionMap = json?.dict("actionMap") - if actionMap != nil { - addTarget(self, action: #selector(addCustomAction), for: .touchUpInside) - } + NSLayoutConstraint.constraintPinSubview(toSuperview: baseView) + let constraints = NSLayoutConstraint.constraintPinView(baseView, heightConstraint: true, heightConstant: MVMCoreUISwitch.getHeight(), widthConstraint: true, widthConstant: MVMCoreUISwitch.getWidth()) + self.height = constraints.object(forKey: ConstraintHeight, ofType: NSLayoutConstraint.self) + self.width = constraints.object(forKey: ConstraintWidth, ofType: NSLayoutConstraint.self) + baseView?.layer.cornerRadius = MVMCoreUISwitch.getHeight() / 2.0 + + onthumbTintColor = UIColor.white + offthumbTintColor = UIColor.white + + let thumbView = MVMCoreUICommonViewsUtility.commonView() + thumbView?.backgroundColor = UIColor.white + thumbView?.layer.cornerRadius = MVMCoreUISwitch.getthumbHeight() * 0.5 + + if let thumbView = thumbView { + baseView?.addSubview(thumbView) } - public @objc func addCustomAction() { - if actionBlock { - actionBlock() - } + let heightWidth = NSLayoutConstraint.constraintPinView(thumbView, heightConstraint: true, heightConstant: MVMCoreUISwitch.getthumbHeight(), widthConstraint: true, widthConstant: MVMCoreUISwitch.getthumbWidth()) + let height = heightWidth.object(forKey: ConstraintHeight, ofType: NSLayoutConstraint.self) + let width = heightWidth.object(forKey: ConstraintWidth, ofType: NSLayoutConstraint.self) + thumbHeightConstraint = height + thumbWidthConstraint = width + let leadingTrailingDic = NSLayoutConstraint.constraintPinSubview(thumbView, pinTop: false, topConstant: 0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 1, pinRight: true, rightConstant: 1) + + let leadingTrailingDic = NSLayoutConstraint.constraintPinSubview(thumbView, pinTop: false, topConstant: 0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 1, pinRight: true, rightConstant: 1) + let `left` = leadingTrailingDic.object(forKey: ConstraintLeading, ofType: NSLayoutConstraint.self) + let `right` = leadingTrailingDic.object(forKey: ConstraintTrailing, ofType: NSLayoutConstraint.self) + NSLayoutConstraint.constraintPinSubview(thumbView, pinCenterX: false, centerXConstant: 0, pinCenterY: true, centerYConstant: 0) + `right`?.constant = 15 + thumbLeftConstraint = `left` + thumbRightConstraint = `right` + baseView.bringSubviewToFront(thumbView) + + //baseView = baseView // Skipping redundant initializing to itself + //thumbView = thumbView // Skipping redundant initializing to itself + + baseView.isUserInteractionEnabled = false + thumbView.isUserInteractionEnabled = false + shouldTouchToSwitch = false + setState(on, animated: false) + shouldTouchToSwitch = true + } + + @objc func addCustomAction() { + + actionBlock?() + } + + public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + + return Switch.getSwitchHeight() + } + + // MARK: - UIResponder overide + public func touchesBegan(_ touches: Set, with event: UIEvent) { + + thumbEnlargeAnimation() + sendActions(for: .touchDown) + } + + public func touchesEnded(_ touches: Set, with event: UIEvent) { + + if shouldTouchToSwitch && valueShouldChange{ + changeValue() } - public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return self.getSwitchHeight() - } + sendActions(for: .touchUpInside) + thumbMoveAnitmationTo(on: isOn) + thumbShakeAnitmationTo(on: isOn) + thumbReformAnimation(true) + valueShouldChange = true + } + + public func touchesMoved(_ touches: Set, with event: UIEvent) { - // MARK: - MVMCoreUIMoleculeViewProtocol - public func needsToBeConstrained() -> Bool { - return true - } - - public func alignment() -> UIStackView.Alignment { - return .trailing - } - - // MARK: - UIResponder overide - public func touchesBegan(_ touches: Set, with event: UIEvent) { - knobEnlargeAnimation() - sendActions(for: .touchDown) - } - - public func touchesEnded(_ touches: Set, with event: UIEvent) { - if shouldTouchToSwitch { - if valueShouldChange { - changeValue() - } - } - sendActions(for: .touchUpInside) - knobMoveAnitmationTo(on: isOn) - knobShakeAnitmationTo(on: isOn) - knobReformAnimation(true) - valueShouldChange = true - } - - public func touchesMoved(_ touches: Set, with event: UIEvent) { - if shouldTouchToSwitch { - if touchMoves(toLeft: touches) { - knobMoveAnitmationTo(on: false) - knobShakeAnitmationTo(on: false) - knobReformAnimation(true) - } else { - knobMoveAnitmationTo(on: true) - knobShakeAnitmationTo(on: true) - knobReformAnimation(true) - } - } - if touchIsOutSide(touches) { - sendActions(for: .touchDragOutside) - if !shouldTouchToSwitch { - knobReformAnimation(true) - } + if shouldTouchToSwitch { + if touchMoves(toLeft: touches) { + thumbMoveAnitmationTo(on: false) + thumbShakeAnitmationTo(on: false) + thumbReformAnimation(true) } else { - sendActions(for: .touchDragInside) + thumbMoveAnitmationTo(on: true) + thumbShakeAnitmationTo(on: true) + thumbReformAnimation(true) } } - public func touchesCancelled(_ touches: Set, with event: UIEvent) { - if shouldTouchToSwitch { - knobReformAnimation(true) - canChangeValue = true - } - sendActions(for: .touchCancel) - } - - // MARK: - animation - public func knobEnlargeAnimation() { - UIView.animate(withDuration: 0.1, animations: { - self.knobWidthConstraint.constant += PaddingOne - self.layoutIfNeeded() - }) - } - - public func knobReformAnimation(_ animated: Bool) { - if animated { - UIView.animate(withDuration: 0.1, animations: { - self.knobWidthConstraint.constant = MVMCoreUISwitch.getKnobWidth() - self.layoutIfNeeded() - }) { finished in - } - } else { - knobWidthConstraint.constant = MVMCoreUISwitch.getKnobWidth() - layoutIfNeeded() - } - } - - public func knobMoveAnitmationTo(on toOn: Bool) { - UIView.animate(withDuration: 0.1, animations: { - if toOn { - self.knobLeftConstraint.priority = 1 - self.knobRightConstraint.priority = 999 - self.knobRightConstraint.constant = 1 - } else { - self.knobLeftConstraint.priority = 999 - self.knobRightConstraint.priority = 1 - self.knobLeftConstraint.constant = 1 - } - self.setBaseColorToOn(toOn, animated: true) - self.knobWidthConstraint.constant = MVMCoreUISwitch.getKnobWidth() + PaddingOne - self.valueShouldChange = toOn != self.on - self.layoutIfNeeded() - }) - } - - public func setBaseColorToOn(_ toOn: Bool, animated: Bool) { - if animated { - UIView.beginAnimations(nil, context: nil) - UIView.setAnimationDuration(0.2) - UIView.setAnimationCurve(.easeIn) - if toOn { - knobView.backgroundColor = onKnobTintColor - baseView.backgroundColor = onTintColor - } else { - knobView.backgroundColor = offKnobTintColor - baseView.backgroundColor = offTintColor - } - UIView.commitAnimations() - } else if on { - knobView.backgroundColor = onKnobTintColor - baseView.backgroundColor = onTintColor - } else { - knobView.backgroundColor = offKnobTintColor - baseView.backgroundColor = offTintColor - } - } - - //used after knob moving to match the behavior of default uiswitch - public func knobShakeAnitmationTo(on toOn: Bool) { - UIView.animate(withDuration: 0.1, delay: 0.1, options: .curveEaseIn, animations: { - if toOn { - self.knobRightConstraint.constant = SwitchShakeIntensity - } else { - self.knobLeftConstraint.constant = SwitchShakeIntensity - } - self.layoutIfNeeded() - }) { finished in - } - UIView.animate(withDuration: 0.2, delay: 0.1, options: [], animations: { - if toOn { - self.knobRightConstraint.constant = 1 - } else { - self.knobLeftConstraint.constant = 1 - } - self.layoutIfNeeded() - }) { finished in - self.valueShouldChange = true - } - } - - // MARK: - switch logic - public func setState(_ state: Bool, animated: Bool) { - setState(state, withoutBlockAnimated: animated) - if valueChangedBlock { - valueChangedBlock() - } - - if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { - let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator - formValidator?.enableByValidation() - } - } - - public func setState(_ state: Bool, withoutBlockAnimated animated: Bool) { - on = state + if touchIsOutSide(touches) { + sendActions(for: .touchDragOutside) if !shouldTouchToSwitch { - knobEnlargeAnimation() - knobMoveAnitmationTo(on: on) - knobShakeAnitmationTo(on: on) + thumbReformAnimation(true) } - if on { - knobLeftConstraint.priority = 1 - knobRightConstraint.priority = 999 - knobRightConstraint.constant = 1 - } else { - knobRightConstraint.priority = 1 - knobLeftConstraint.priority = 999 - knobLeftConstraint.constant = 1 + } else { + sendActions(for: .touchDragInside) + } + } + + public func touchesCancelled(_ touches: Set, with event: UIEvent) { + + if shouldTouchToSwitch { + thumbReformAnimation(true) + canChangeValue = true + } + + sendActions(for: .touchCancel) + } + + // MARK: - animation + public func thumbEnlargeAnimation() { + + UIView.animate(withDuration: 0.1, animations: { + self.thumbWidthConstraint?.constant += PaddingOne + self.layoutIfNeeded() + }) + } + + public func thumbReformAnimation(_ animated: Bool) { + + if animated { + UIView.animate(withDuration: 0.1, animations: { + self.thumbWidthConstraint?.constant = MVMCoreUISwitch.getthumbWidth() + self.layoutIfNeeded() + }) { finished in } - - setBaseColorToOn(on, animated: animated) - knobReformAnimation(animated) - accessibilityValue = state ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") - setNeedsLayout() + } else { + thumbWidthConstraint?.constant = MVMCoreUISwitch.getthumbWidth() layoutIfNeeded() } + } + + public func thumbMoveAnitmationTo(on toOn: Bool) { - public func changeValue() -> Bool { - on ^= true - shouldTouchToSwitch = false - setState(on, animated: true) - shouldTouchToSwitch = true - sendActions(for: .valueChanged) - return on + UIView.animate(withDuration: 0.1, animations: { + if toOn { + self.thumbLeftConstraint?.priority = UILayoutPriority(1) + self.thumbRightConstraint?.priority = UILayoutPriority(999) + self.thumbRightConstraint?.constant = 1 + } else { + self.thumbLeftConstraint?.priority = UILayoutPriority(999) + self.thumbRightConstraint?.priority = UILayoutPriority(1) + self.thumbLeftConstraint?.constant = 1 + } + self.setBaseColorToOn(toOn, animated: true) + self.thumbWidthConstraint?.constant = MVMCoreUISwitch.getThumbWidth() + PaddingOne + self.valueShouldChange = toOn != self.isOn + self.layoutIfNeeded() + }) + } + + public func setBaseColorToOn(_ toOn: Bool, animated: Bool) { + + if animated { + UIView.beginAnimations(nil, context: nil) + UIView.setAnimationDuration(0.2) + UIView.setAnimationCurve(.easeIn) + + if toOn { + thumbView?.backgroundColor = thumbTintColor!.on + baseView?.backgroundColor = trackTintColor!.on + } else { + thumbView?.backgroundColor = thumbTintColor!.off + baseView?.backgroundColor = trackTintColor!.off + } + UIView.commitAnimations() + } else if isOn { + thumbView?.backgroundColor = thumbTintColor!.on + baseView?.backgroundColor = trackTintColor!.on + } else { + thumbView?.backgroundColor = thumbTintColor!.off + baseView?.backgroundColor = trackTintColor!.off } } + + //used after thumb moving to match the behavior of default uiswitch + public func thumbShakeAnitmationTo(on toOn: Bool) { + UIView.animate(withDuration: 0.1, delay: 0.1, options: .curveEaseIn, animations: { + if toOn { + self.thumbRightConstraint?.constant = Switch.shakeIntensity + } else { + self.thumbLeftConstraint?.constant = Switch.shakeIntensity + } + self.layoutIfNeeded() + }) { finished in + } + UIView.animate(withDuration: 0.2, delay: 0.1, options: [], animations: { + if toOn { + self.thumbRightConstraint?.constant = 1 + } else { + self.thumbLeftConstraint?.constant = 1 + } + self.layoutIfNeeded() + }) { finished in + self.valueShouldChange = true + } + } + + public func setState(_ state: Bool, animated: Bool) { + setState(state, withoutBlockAnimated: animated) + + valueChangedBlock?() + + if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { + let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator + formValidator?.enableByValidation() + } + } + + public func setState(_ state: Bool, withoutBlockAnimated animated: Bool) { + + isOn = state + + if !shouldTouchToSwitch { + thumbEnlargeAnimation() + thumbMoveAnitmationTo(on: isOn) + thumbShakeAnitmationTo(on: isOn) + } + + if isOn { + thumbLeftConstraint?.priority = UILayoutPriority(1) + thumbRightConstraint?.priority = UILayoutPriority(999) + thumbRightConstraint?.constant = 1 + } else { + thumbRightConstraint?.priority = UILayoutPriority(1) + thumbLeftConstraint?.priority = UILayoutPriority(999) + thumbLeftConstraint?.constant = 1 + } + + setBaseColorToOn(isOn, animated: animated) + thumbReformAnimation(animated) + accessibilityValue = state ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") + setNeedsLayout() + layoutIfNeeded() + } + + public func changeValue() -> Bool { + isOn ^= true + shouldTouchToSwitch = false + setState(isOn, animated: true) + shouldTouchToSwitch = true + sendActions(for: .valueChanged) + return isOn + } + + + // MARK: - helper + func touchIsOutSide(_ touches: Set?) -> Bool { + + let location = touches?.first?.location(in: self) + let x = Float(location?.x ?? 0.0) + let y = Float(location?.y ?? 0.0) + return x < 0 || x > frame.size.width || y < 0 || y > frame.size.height + } + + func touchMoves(toLeft touches: Set?) -> Bool { + + let location = touches?.first?.location(in: self) + let x = Float(location?.x ?? 0.0) + return x < frame.size.width / 2.0 + } + + class func getSwitchWidth() -> CGFloat { + return (MFSizeObject(standardSize: Switch.width, standardiPadPortraitSize: Double(SwitchWidth) * 1.5)).getValueBasedOnApplicationWidth() + } + + class func getSwitchHeight() -> CGFloat { + return (MFSizeObject(standardSize: Switch.height, standardiPadPortraitSize: Double(SwitchHeight) * 1.5)).getValueBasedOnApplicationWidth() + } + + class func getSwitchthumbWidth() -> CGFloat { + return (MFSizeObject(standardSize: Switch.thumbWidth, standardiPadPortraitSize: Double(SwitchthumbWidth) * 1.5)).getValueBasedOnApplicationWidth() + } + + class func getSwitchthumbHeight() -> CGFloat { + return (MFSizeObject(standardSize: Switch.thumbHeight, standardiPadPortraitSize: Double(SwitchthumbHeight) * 1.5)).getValueBasedOnApplicationWidth() + } } +// MARK: - Accessibility +extension Switch { + + func formFieldGroupName() -> String? { + return json.string("groupName") + } +} + +// MARK: FormValidationProtocol +extension Switch { + + func isValidField() -> Bool { + return isOn && json.bool(forKey: "required") + } + + func formFieldName() -> String? { + return json.string(KeyFieldKey) + } + + func formFieldValue() -> Any? { + return NSNumber(value: isOn) + } +} + +// MARK: - MVMCoreUIMoleculeViewProtocol +extension Switch: MVMCoreUIMoleculeViewProtocol { + + public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + self.json = json + delegate = delegateObject + + guard let dictionary = json else { return } + + FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) + + if let color = dictionary["onTintColor"] as? String { + trackTintColor?.on = UIColor.mfGet(forHex: color) + } + + if let color = dictionary["offTintColor"] as? String { + trackTintColor?.off = UIColor.mfGet(forHex: color) + } + + if let color = dictionary["onthumbTintColor"] as? String { + thumbTintColor?.on = UIColor.mfGet(forHex: color) + } + + if let color = dictionary["offthumbTintColor"] as? String { + thumbTintColor?.off = UIColor.mfGet(forHex: color) + } + + setState(json?.bool(forKey: "state"), animated: false) + + let actionMap = json?.dict("actionMap") + if actionMap != nil { + addTarget(self, action: #selector(addCustomAction), for: .touchUpInside) + } + } + + public func needsToBeConstrained() -> Bool { + return true + } + + public func alignment() -> UIStackView.Alignment { + return .trailing + } +} diff --git a/MVMCoreUI/BaseClasses/Control.swift b/MVMCoreUI/BaseClasses/Control.swift index fdf8204f..e1690306 100644 --- a/MVMCoreUI/BaseClasses/Control.swift +++ b/MVMCoreUI/BaseClasses/Control.swift @@ -9,6 +9,7 @@ import UIKit public class Control: UIControl { + var json: [AnyHashable: Any]? private var initialSetupPerformed = false @@ -37,8 +38,8 @@ public class Control: UIControl { } extension Control: MVMCoreViewProtocol { - public func updateView(_ size: CGFloat) { - } + + public func updateView(_ size: CGFloat) { } /// Will be called only once. public func setupView() { From cbb016406a5b6463119fc5ef90e185fee2400845 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 9 Dec 2019 13:37:33 -0500 Subject: [PATCH 03/18] WIP, putting Switch together. --- MVMCoreUI/Atoms/Views/Switch.swift | 377 +++++++++--------- MVMCoreUI/BaseClasses/Control.swift | 19 +- .../MVMCoreUIMoleculeMappingObject.m | 2 +- 3 files changed, 212 insertions(+), 186 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Switch.swift b/MVMCoreUI/Atoms/Views/Switch.swift index e12fd890..025c7b64 100644 --- a/MVMCoreUI/Atoms/Views/Switch.swift +++ b/MVMCoreUI/Atoms/Views/Switch.swift @@ -7,17 +7,16 @@ // import MVMCore -import MVMCoreUI import UIKit -typealias ValueChangeBlock = () -> () +public typealias ValueChangeBlock = () -> () -open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol { + +@objcMembers open class Switch: Control, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public var isOn = false public var trackTintColor: (on: UIColor?, off: UIColor?)? public var thumbTintColor: (on: UIColor?, off: UIColor?)? @@ -25,18 +24,40 @@ open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtoc var valueChangedBlock: ValueChangeBlock? var actionBlock: ValueChangeBlock? - static let width: CGFloat = 42 - static let height: CGFloat = 22 - static let thumbWidth: CGFloat = 20 - static let thumbHeight: CGFloat = 20 + static let trackSize = CGSize(width: 46, height: 24) + static let thumbSize = CGSize(width: 22, height: 22) + static let shakeIntensity: CGFloat = 2 - private weak var baseView: UIView? - private weak var thumbView: UIView? + private var thumbView = View() private var valueShouldChange = false private var canChangeValue = false + //-------------------------------------------------- + // MARK: - Computed Properties + //-------------------------------------------------- + + open override var isEnabled: Bool { + didSet { + if isEnabled { + + } else { + + } + } + } + + open var isOn: Bool = false { + didSet { + if isOn { + + } else { + + } + } + } + //-------------------------------------------------- // MARK: - Delegate //-------------------------------------------------- @@ -47,36 +68,12 @@ open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtoc // MARK: - Constraints //-------------------------------------------------- - private var thumbLeftConstraint: NSLayoutConstraint? - private var thumbRightConstraint: NSLayoutConstraint? + private var thumbLeadingConstraint: NSLayoutConstraint? + private var thumbTrailingConstraint: NSLayoutConstraint? private var thumbHeightConstraint: NSLayoutConstraint? private var thumbWidthConstraint: NSLayoutConstraint? - private weak var height: NSLayoutConstraint? - private weak var width: NSLayoutConstraint? - - //-------------------------------------------------- - // MARK: - Lifecycle - //-------------------------------------------------- - - public func updateView(_ size: CGFloat) { - - height?.constant = MVMCoreUISwitch.getHeight() - width?.constant = MVMCoreUISwitch.getWidth() - baseView?.layer.cornerRadius = MVMCoreUISwitch.getHeight() / 2.0 - thumbView?.layer.cornerRadius = MVMCoreUISwitch.getthumbHeight() * 0.5 - thumbHeightConstraint?.constant = MVMCoreUISwitch.getthumbHeight() - thumbWidthConstraint?.constant = MVMCoreUISwitch.getthumbWidth() - } - - public func setupView() { - - trackTintColor?.on = .mfSwitchOnTint() - trackTintColor?.off = .mfSwitchOffTint() - canChangeValue = true - shouldTouchToSwitch = true - setUpViewWithState(isOn) - accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "MVMCoreUISwitch_buttonlabel") - } + private var heightConstraint: NSLayoutConstraint? + private var widthConstraint: NSLayoutConstraint? //-------------------------------------------------- // MARK: - Initializers @@ -87,100 +84,101 @@ open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtoc setupView() } - public convenience init() { + public convenience override init() { self.init(frame: frame) } + public convenience init(isOn: Bool, changeBlock: ValueChangeBlock?) { + self.init(frame: frame) + self.isOn = isOn + valueChangedBlock = changeBlock + } + + public convenience init(changeBlock: ValueChangeBlock?) { + self.init(frame: frame) + valueChangedBlock = changeBlock + } + public required init?(coder: NSCoder) { super.init(coder: coder) - fatalError("Switch does not support xib") + fatalError("Switch does not support xib.") + } + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + + public override func updateView(_ size: CGFloat) { + super.updateView(size) + + heightConstraint?.constant = Switch.getTrackHeight() + widthConstraint?.constant = Switch.getTrackWidth() + + layer.cornerRadius = Switch.getTrackHeight() / 2.0 + thumbView.layer.cornerRadius = Switch.getThumbHeight() / 2.0 + + thumbHeightConstraint?.constant = Switch.getThumbHeight() + thumbWidthConstraint?.constant = Switch.getThumbWidth() + } + + public override func setupView() { + super.setupView() + + guard !subviews.isEmpty else { return } + + trackTintColor?.on = .mfSwitchOnTint() // Green + trackTintColor?.off = .mfSwitchOffTint() // Black + + thumbTintColor?.on = .white + thumbTintColor?.off = .white + + canChangeValue = true + shouldTouchToSwitch = true + valueShouldChange = true + + heightConstraint = heightAnchor.constraint(equalToConstant: Switch.trackSize.height) + heightConstraint?.isActive = true + + widthConstraint = widthAnchor.constraint(equalToConstant: Switch.trackSize.width) + widthConstraint?.isActive = true + + layer.cornerRadius = Switch.trackSize.height / 2.0 + + let thumbView = MVMCoreUICommonViewsUtility.commonView() + thumbView.backgroundColor = .white + thumbView.layer.cornerRadius = Switch.getThumbHeight() / 2.0 + + addSubview(thumbView) + + thumbView.heightAnchor.constraint(equalToConstant: Switch.thumbSize.height).isActive = true + thumbView.widthAnchor.constraint(equalToConstant: Switch.thumbSize.width).isActive = true + thumbView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + + thumbTrailingConstraint = trailingAnchor.constraint(equalTo: thumbView.trailingAnchor, constant: 1) + thumbLeadingConstraint = thumbView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 1) + thumbLeadingConstraint?.isActive = true + + shouldTouchToSwitch = false + setState(isOn, animated: false) + shouldTouchToSwitch = true + + accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Switch_buttonlabel") } //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- - public class func mvmSwitchDefault() -> Self { - - let mySwitch = self.init(frame: .zero) - mySwitch?.translatesAutoresizingMaskIntoConstraints = false - return mySwitch - } - - public class func mvmSwitchDefault(with block: ValueChangeBlock?) -> Self { - - let mySwitch = self.init(frame: .zero) - mySwitch?.valueChangedBlock = block - return mySwitch - } - - public func setUpViewWithState(_ on: Bool) { - - guard baseView == nil else { return } - - isUserInteractionEnabled = true - valueShouldChange = true - - let baseView = MVMCoreUICommonViewsUtility.commonView() - - if let baseView = baseView { - addSubview(baseView) - } - - NSLayoutConstraint.constraintPinSubview(toSuperview: baseView) - let constraints = NSLayoutConstraint.constraintPinView(baseView, heightConstraint: true, heightConstant: MVMCoreUISwitch.getHeight(), widthConstraint: true, widthConstant: MVMCoreUISwitch.getWidth()) - self.height = constraints.object(forKey: ConstraintHeight, ofType: NSLayoutConstraint.self) - self.width = constraints.object(forKey: ConstraintWidth, ofType: NSLayoutConstraint.self) - baseView?.layer.cornerRadius = MVMCoreUISwitch.getHeight() / 2.0 - - onthumbTintColor = UIColor.white - offthumbTintColor = UIColor.white - - let thumbView = MVMCoreUICommonViewsUtility.commonView() - thumbView?.backgroundColor = UIColor.white - thumbView?.layer.cornerRadius = MVMCoreUISwitch.getthumbHeight() * 0.5 - - if let thumbView = thumbView { - baseView?.addSubview(thumbView) - } - - let heightWidth = NSLayoutConstraint.constraintPinView(thumbView, heightConstraint: true, heightConstant: MVMCoreUISwitch.getthumbHeight(), widthConstraint: true, widthConstant: MVMCoreUISwitch.getthumbWidth()) - let height = heightWidth.object(forKey: ConstraintHeight, ofType: NSLayoutConstraint.self) - let width = heightWidth.object(forKey: ConstraintWidth, ofType: NSLayoutConstraint.self) - thumbHeightConstraint = height - thumbWidthConstraint = width - let leadingTrailingDic = NSLayoutConstraint.constraintPinSubview(thumbView, pinTop: false, topConstant: 0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 1, pinRight: true, rightConstant: 1) - - let leadingTrailingDic = NSLayoutConstraint.constraintPinSubview(thumbView, pinTop: false, topConstant: 0, pinBottom: false, bottomConstant: 0, pinLeft: true, leftConstant: 1, pinRight: true, rightConstant: 1) - let `left` = leadingTrailingDic.object(forKey: ConstraintLeading, ofType: NSLayoutConstraint.self) - let `right` = leadingTrailingDic.object(forKey: ConstraintTrailing, ofType: NSLayoutConstraint.self) - NSLayoutConstraint.constraintPinSubview(thumbView, pinCenterX: false, centerXConstant: 0, pinCenterY: true, centerYConstant: 0) - `right`?.constant = 15 - thumbLeftConstraint = `left` - thumbRightConstraint = `right` - baseView.bringSubviewToFront(thumbView) - - //baseView = baseView // Skipping redundant initializing to itself - //thumbView = thumbView // Skipping redundant initializing to itself - - baseView.isUserInteractionEnabled = false - thumbView.isUserInteractionEnabled = false - shouldTouchToSwitch = false - setState(on, animated: false) - shouldTouchToSwitch = true - } - @objc func addCustomAction() { actionBlock?() } - public class func estimatedHeight(forRow json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - - return Switch.getSwitchHeight() - } - // MARK: - UIResponder overide + //-------------------------------------------------- + // MARK: - UIResponder + //-------------------------------------------------- + public func touchesBegan(_ touches: Set, with event: UIEvent) { thumbEnlargeAnimation() @@ -189,7 +187,7 @@ open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtoc public func touchesEnded(_ touches: Set, with event: UIEvent) { - if shouldTouchToSwitch && valueShouldChange{ + if shouldTouchToSwitch && valueShouldChange { changeValue() } @@ -247,12 +245,11 @@ open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtoc if animated { UIView.animate(withDuration: 0.1, animations: { - self.thumbWidthConstraint?.constant = MVMCoreUISwitch.getthumbWidth() + self.thumbWidthConstraint?.constant = Switch.getThumbWidth() self.layoutIfNeeded() - }) { finished in - } + }) { finished in } } else { - thumbWidthConstraint?.constant = MVMCoreUISwitch.getthumbWidth() + thumbWidthConstraint?.constant = Switch.getThumbWidth() layoutIfNeeded() } } @@ -261,16 +258,18 @@ open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtoc UIView.animate(withDuration: 0.1, animations: { if toOn { - self.thumbLeftConstraint?.priority = UILayoutPriority(1) - self.thumbRightConstraint?.priority = UILayoutPriority(999) - self.thumbRightConstraint?.constant = 1 + self.thumbLeadingConstraint?.priority = UILayoutPriority(1) + self.thumbTrailingConstraint?.priority = UILayoutPriority(999) + self.thumbTrailingConstraint?.constant = 1 + } else { - self.thumbLeftConstraint?.priority = UILayoutPriority(999) - self.thumbRightConstraint?.priority = UILayoutPriority(1) - self.thumbLeftConstraint?.constant = 1 + self.thumbLeadingConstraint?.priority = UILayoutPriority(999) + self.thumbTrailingConstraint?.priority = UILayoutPriority(1) + self.thumbLeadingConstraint?.constant = 1 } + self.setBaseColorToOn(toOn, animated: true) - self.thumbWidthConstraint?.constant = MVMCoreUISwitch.getThumbWidth() + PaddingOne + self.thumbWidthConstraint?.constant = Switch.getThumbWidth() + PaddingOne self.valueShouldChange = toOn != self.isOn self.layoutIfNeeded() }) @@ -284,38 +283,41 @@ open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtoc UIView.setAnimationCurve(.easeIn) if toOn { - thumbView?.backgroundColor = thumbTintColor!.on - baseView?.backgroundColor = trackTintColor!.on + thumbView.backgroundColor = thumbTintColor?.on + backgroundColor = trackTintColor?.on } else { - thumbView?.backgroundColor = thumbTintColor!.off - baseView?.backgroundColor = trackTintColor!.off + thumbView.backgroundColor = thumbTintColor?.off + backgroundColor = trackTintColor?.off } UIView.commitAnimations() + } else if isOn { - thumbView?.backgroundColor = thumbTintColor!.on - baseView?.backgroundColor = trackTintColor!.on + thumbView.backgroundColor = thumbTintColor?.on + backgroundColor = trackTintColor?.on + } else { - thumbView?.backgroundColor = thumbTintColor!.off - baseView?.backgroundColor = trackTintColor!.off + thumbView.backgroundColor = thumbTintColor?.off + backgroundColor = trackTintColor?.off } } //used after thumb moving to match the behavior of default uiswitch public func thumbShakeAnitmationTo(on toOn: Bool) { + UIView.animate(withDuration: 0.1, delay: 0.1, options: .curveEaseIn, animations: { if toOn { - self.thumbRightConstraint?.constant = Switch.shakeIntensity + self.thumbTrailingConstraint?.constant = Switch.shakeIntensity } else { - self.thumbLeftConstraint?.constant = Switch.shakeIntensity + self.thumbLeadingConstraint?.constant = Switch.shakeIntensity } self.layoutIfNeeded() - }) { finished in - } + }, completion: nil) + UIView.animate(withDuration: 0.2, delay: 0.1, options: [], animations: { if toOn { - self.thumbRightConstraint?.constant = 1 + self.thumbTrailingConstraint?.constant = 1 } else { - self.thumbLeftConstraint?.constant = 1 + self.thumbLeadingConstraint?.constant = 1 } self.layoutIfNeeded() }) { finished in @@ -328,10 +330,10 @@ open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtoc valueChangedBlock?() - if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { - let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator - formValidator?.enableByValidation() - } + // if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { + let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator + formValidator?.enableByValidation() + // } } public func setState(_ state: Bool, withoutBlockAnimated animated: Bool) { @@ -345,13 +347,13 @@ open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtoc } if isOn { - thumbLeftConstraint?.priority = UILayoutPriority(1) - thumbRightConstraint?.priority = UILayoutPriority(999) - thumbRightConstraint?.constant = 1 + thumbLeadingConstraint?.priority = UILayoutPriority(1) + thumbTrailingConstraint?.priority = UILayoutPriority(999) + thumbTrailingConstraint?.constant = 1 } else { - thumbRightConstraint?.priority = UILayoutPriority(1) - thumbLeftConstraint?.priority = UILayoutPriority(999) - thumbLeftConstraint?.constant = 1 + thumbTrailingConstraint?.priority = UILayoutPriority(1) + thumbLeadingConstraint?.priority = UILayoutPriority(999) + thumbLeadingConstraint?.constant = 1 } setBaseColorToOn(isOn, animated: animated) @@ -361,77 +363,82 @@ open class Switch: Control, MVMCoreViewProtocol, MVMCoreUIViewConstrainingProtoc layoutIfNeeded() } + @discardableResult public func changeValue() -> Bool { - isOn ^= true + + isOn.toggle() shouldTouchToSwitch = false setState(isOn, animated: true) shouldTouchToSwitch = true sendActions(for: .valueChanged) - return isOn - } - - - // MARK: - helper - func touchIsOutSide(_ touches: Set?) -> Bool { - let location = touches?.first?.location(in: self) - let x = Float(location?.x ?? 0.0) - let y = Float(location?.y ?? 0.0) - return x < 0 || x > frame.size.width || y < 0 || y > frame.size.height + return isOn } func touchMoves(toLeft touches: Set?) -> Bool { let location = touches?.first?.location(in: self) - let x = Float(location?.x ?? 0.0) + let x = CGFloat(location?.x ?? 0.0) return x < frame.size.width / 2.0 } - class func getSwitchWidth() -> CGFloat { - return (MFSizeObject(standardSize: Switch.width, standardiPadPortraitSize: Double(SwitchWidth) * 1.5)).getValueBasedOnApplicationWidth() + override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + + let faultTolerance: CGFloat = 20.0 + let area = bounds.insetBy(dx: -faultTolerance, dy: -faultTolerance) + return area.contains(point) } - class func getSwitchHeight() -> CGFloat { - return (MFSizeObject(standardSize: Switch.height, standardiPadPortraitSize: Double(SwitchHeight) * 1.5)).getValueBasedOnApplicationWidth() + open override func touchesEnded(_ touches: Set, with event: UIEvent?) { + + sendActions(for: .touchUpInside) } - class func getSwitchthumbWidth() -> CGFloat { - return (MFSizeObject(standardSize: Switch.thumbWidth, standardiPadPortraitSize: Double(SwitchthumbWidth) * 1.5)).getValueBasedOnApplicationWidth() + class func getTrackWidth() -> CGFloat { + return (MFSizeObject(standardSize: Switch.trackSize.width, standardiPadPortraitSize: CGFloat(Switch.trackSize.width * 1.5)))!.getValueBasedOnApplicationWidth() } - class func getSwitchthumbHeight() -> CGFloat { - return (MFSizeObject(standardSize: Switch.thumbHeight, standardiPadPortraitSize: Double(SwitchthumbHeight) * 1.5)).getValueBasedOnApplicationWidth() + class func getTrackHeight() -> CGFloat { + return (MFSizeObject(standardSize: Switch.trackSize.height, standardiPadPortraitSize: CGFloat(Switch.trackSize.height * 1.5) ))!.getValueBasedOnApplicationWidth() + } + + class func getThumbWidth() -> CGFloat { + return (MFSizeObject(standardSize: Switch.thumbSize.width, standardiPadPortraitSize: CGFloat(Switch.thumbSize.width * 1.5)))!.getValueBasedOnApplicationWidth() + } + + class func getThumbHeight() -> CGFloat { + return (MFSizeObject(standardSize: Switch.thumbSize.height, standardiPadPortraitSize: CGFloat(Switch.thumbSize.height * 1.5)))!.getValueBasedOnApplicationWidth() } } // MARK: - Accessibility extension Switch { - func formFieldGroupName() -> String? { - return json.string("groupName") + public func formFieldGroupName() -> String? { + return json?["groupName"] as? String } } // MARK: FormValidationProtocol extension Switch { - func isValidField() -> Bool { - return isOn && json.bool(forKey: "required") + public func isValidField() -> Bool { + return isOn && json?["required"] as? Bool ?? false } - func formFieldName() -> String? { - return json.string(KeyFieldKey) + public func formFieldName() -> String? { + return json?[KeyFieldKey] as? String ?? "" } - func formFieldValue() -> Any? { + public func formFieldValue() -> Any? { return NSNumber(value: isOn) } } // MARK: - MVMCoreUIMoleculeViewProtocol -extension Switch: MVMCoreUIMoleculeViewProtocol { +extension Switch { - public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { self.json = json delegate = delegateObject @@ -455,14 +462,18 @@ extension Switch: MVMCoreUIMoleculeViewProtocol { thumbTintColor?.off = UIColor.mfGet(forHex: color) } - setState(json?.bool(forKey: "state"), animated: false) + setState(dictionary["state"] as? Bool ?? false, animated: false) - let actionMap = json?.dict("actionMap") - if actionMap != nil { + if let _ = dictionary["actionMap"] as? [String: Any] { addTarget(self, action: #selector(addCustomAction), for: .touchUpInside) } } + public class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { + + return Switch.getTrackHeight() + } + public func needsToBeConstrained() -> Bool { return true } diff --git a/MVMCoreUI/BaseClasses/Control.swift b/MVMCoreUI/BaseClasses/Control.swift index e1690306..34785ce1 100644 --- a/MVMCoreUI/BaseClasses/Control.swift +++ b/MVMCoreUI/BaseClasses/Control.swift @@ -8,12 +8,19 @@ import UIKit -public class Control: UIControl { +open class Control: UIControl { + //-------------------------------------------------- + // MARK: - Properties + //-------------------------------------------------- var json: [AnyHashable: Any]? private var initialSetupPerformed = false + //-------------------------------------------------- + // MARK: - Initialiers + //-------------------------------------------------- + public override init(frame: CGRect) { super.init(frame: .zero) initialSetup() @@ -29,7 +36,12 @@ public class Control: UIControl { initialSetup() } + //-------------------------------------------------- + // MARK: - Setup + //-------------------------------------------------- + public func initialSetup() { + if !initialSetupPerformed { initialSetupPerformed = true setupView() @@ -37,6 +49,7 @@ public class Control: UIControl { } } +// MARK: MVMCoreViewProtocol extension Control: MVMCoreViewProtocol { public func updateView(_ size: CGFloat) { } @@ -48,8 +61,10 @@ extension Control: MVMCoreViewProtocol { } } +// MARK: MVMCoreUIMoleculeViewProtocol extension Control: MVMCoreUIMoleculeViewProtocol { - public func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { + + public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.json = json if let backgroundColorString = json?.optionalStringForKey(KeyBackgroundColor) { diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 51a07225..4923f21d 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -48,7 +48,7 @@ @"radioButtonLabel": RadioButtonLabel.class, @"listItem": MoleculeTableViewCell.class, @"accordionListItem": AccordionMoleculeTableViewCell.class, - @"switch": MVMCoreUISwitch.class, + @"switch": Switch.class, @"leftRightLabelView": LeftRightLabelView.class, @"actionDetailWithImage": ActionDetailWithImage.class, @"image": MFLoadImageView.class, From 1668fb8c3235ada04fa4d2c3f2156cb299f08921 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Mon, 9 Dec 2019 15:03:53 -0500 Subject: [PATCH 04/18] More Switch development. --- MVMCoreUI/Atoms/Views/Switch.swift | 227 ++++++++++++++--------------- 1 file changed, 111 insertions(+), 116 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Switch.swift b/MVMCoreUI/Atoms/Views/Switch.swift index 025c7b64..03724267 100644 --- a/MVMCoreUI/Atoms/Views/Switch.swift +++ b/MVMCoreUI/Atoms/Views/Switch.swift @@ -16,9 +16,10 @@ public typealias ValueChangeBlock = () -> () //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - - public var trackTintColor: (on: UIColor?, off: UIColor?)? - public var thumbTintColor: (on: UIColor?, off: UIColor?)? + + public var trackTintColor: (on: UIColor?, off: UIColor?)? = (on: .mfShamrock(), off: .black) + public var thumbTintColor: (on: UIColor?, off: UIColor?)? = (on: .white, off: .white) + public var disabledTintColor: (track: UIColor?, thumb: UIColor?)? = (track: .mfSilver(), thumb: .white) var shouldTouchToSwitch = false var valueChangedBlock: ValueChangeBlock? @@ -40,21 +41,16 @@ public typealias ValueChangeBlock = () -> () open override var isEnabled: Bool { didSet { - if isEnabled { - - } else { - - } + isUserInteractionEnabled = isEnabled + backgroundColor = isEnabled ? trackTintColor?.on : disabledTintColor?.track + thumbView.backgroundColor = isEnabled ? thumbTintColor?.on : disabledTintColor?.thumb } } open var isOn: Bool = false { didSet { - if isOn { - - } else { - - } + backgroundColor = isOn ? trackTintColor?.on : trackTintColor?.off + thumbView.backgroundColor = isOn ? thumbTintColor?.on : thumbTintColor?.off } } @@ -62,7 +58,7 @@ public typealias ValueChangeBlock = () -> () // MARK: - Delegate //-------------------------------------------------- - private var delegate: DelegateObject? + private var delegateObject: MVMCoreUIDelegateObject? //-------------------------------------------------- // MARK: - Constraints @@ -85,17 +81,17 @@ public typealias ValueChangeBlock = () -> () } public convenience override init() { - self.init(frame: frame) + self.init(frame: .zero) } public convenience init(isOn: Bool, changeBlock: ValueChangeBlock?) { - self.init(frame: frame) + self.init(frame: .zero) self.isOn = isOn valueChangedBlock = changeBlock } public convenience init(changeBlock: ValueChangeBlock?) { - self.init(frame: frame) + self.init(frame: .zero) valueChangedBlock = changeBlock } @@ -126,12 +122,6 @@ public typealias ValueChangeBlock = () -> () guard !subviews.isEmpty else { return } - trackTintColor?.on = .mfSwitchOnTint() // Green - trackTintColor?.off = .mfSwitchOffTint() // Black - - thumbTintColor?.on = .white - thumbTintColor?.off = .white - canChangeValue = true shouldTouchToSwitch = true valueShouldChange = true @@ -165,6 +155,22 @@ public typealias ValueChangeBlock = () -> () accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Switch_buttonlabel") } + class func getTrackWidth() -> CGFloat { + return (MFSizeObject(standardSize: Switch.trackSize.width, standardiPadPortraitSize: CGFloat(Switch.trackSize.width * 1.5)))!.getValueBasedOnApplicationWidth() + } + + class func getTrackHeight() -> CGFloat { + return (MFSizeObject(standardSize: Switch.trackSize.height, standardiPadPortraitSize: CGFloat(Switch.trackSize.height * 1.5) ))!.getValueBasedOnApplicationWidth() + } + + class func getThumbWidth() -> CGFloat { + return (MFSizeObject(standardSize: Switch.thumbSize.width, standardiPadPortraitSize: CGFloat(Switch.thumbSize.width * 1.5)))!.getValueBasedOnApplicationWidth() + } + + class func getThumbHeight() -> CGFloat { + return (MFSizeObject(standardSize: Switch.thumbSize.height, standardiPadPortraitSize: CGFloat(Switch.thumbSize.height * 1.5)))!.getValueBasedOnApplicationWidth() + } + //-------------------------------------------------- // MARK: - Methods //-------------------------------------------------- @@ -174,11 +180,64 @@ public typealias ValueChangeBlock = () -> () actionBlock?() } + public func setState(_ state: Bool, animated: Bool) { + + setState(state, withoutBlockAnimated: animated) + valueChangedBlock?() + FormValidator.enableByValidationWith(delegate: delegateObject?.formValidationProtocol) + } + + public func setState(_ state: Bool, withoutBlockAnimated animated: Bool) { + + isOn = state + + if !shouldTouchToSwitch { + thumbEnlargeAnimation() + thumbMoveAnitmationTo(on: isOn) + thumbShakeAnitmationTo(on: isOn) + } + + if isOn { + thumbLeadingConstraint?.priority = UILayoutPriority(1) + thumbTrailingConstraint?.priority = UILayoutPriority(999) + thumbTrailingConstraint?.constant = 1 + + } else { + thumbTrailingConstraint?.priority = UILayoutPriority(1) + thumbLeadingConstraint?.priority = UILayoutPriority(999) + thumbLeadingConstraint?.constant = 1 + } + + setBaseColorToOn(isOn, animated: animated) + thumbReformAnimation(animated) + accessibilityValue = state ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") + setNeedsLayout() + layoutIfNeeded() + } + + @discardableResult + public func changeValue() -> Bool { + + isOn.toggle() + shouldTouchToSwitch = false + setState(isOn, animated: true) + shouldTouchToSwitch = true + sendActions(for: .valueChanged) + + return isOn + } //-------------------------------------------------- // MARK: - UIResponder //-------------------------------------------------- + override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + + let faultTolerance: CGFloat = 20.0 + let area = bounds.insetBy(dx: -faultTolerance, dy: -faultTolerance) + return area.contains(point) + } + public func touchesBegan(_ touches: Set, with event: UIEvent) { thumbEnlargeAnimation() @@ -198,6 +257,11 @@ public typealias ValueChangeBlock = () -> () valueShouldChange = true } + open override func touchesEnded(_ touches: Set, with event: UIEvent?) { + + sendActions(for: .touchUpInside) + } + public func touchesMoved(_ touches: Set, with event: UIEvent) { if shouldTouchToSwitch { @@ -211,7 +275,7 @@ public typealias ValueChangeBlock = () -> () thumbReformAnimation(true) } } - + /* if touchIsOutSide(touches) { sendActions(for: .touchDragOutside) if !shouldTouchToSwitch { @@ -220,6 +284,14 @@ public typealias ValueChangeBlock = () -> () } else { sendActions(for: .touchDragInside) } + */ + } + + func touchMoves(toLeft touches: Set?) -> Bool { + + let location = touches?.first?.location(in: self) + let x = CGFloat(location?.x ?? 0.0) + return x < frame.size.width / 2.0 } public func touchesCancelled(_ touches: Set, with event: UIEvent) { @@ -232,7 +304,10 @@ public typealias ValueChangeBlock = () -> () sendActions(for: .touchCancel) } - // MARK: - animation + //-------------------------------------------------- + // MARK: - Animation + //-------------------------------------------------- + public func thumbEnlargeAnimation() { UIView.animate(withDuration: 0.1, animations: { @@ -247,7 +322,7 @@ public typealias ValueChangeBlock = () -> () UIView.animate(withDuration: 0.1, animations: { self.thumbWidthConstraint?.constant = Switch.getThumbWidth() self.layoutIfNeeded() - }) { finished in } + }, completion: nil) } else { thumbWidthConstraint?.constant = Switch.getThumbWidth() layoutIfNeeded() @@ -285,6 +360,7 @@ public typealias ValueChangeBlock = () -> () if toOn { thumbView.backgroundColor = thumbTintColor?.on backgroundColor = trackTintColor?.on + } else { thumbView.backgroundColor = thumbTintColor?.off backgroundColor = trackTintColor?.off @@ -310,7 +386,9 @@ public typealias ValueChangeBlock = () -> () } else { self.thumbLeadingConstraint?.constant = Switch.shakeIntensity } + self.layoutIfNeeded() + }, completion: nil) UIView.animate(withDuration: 0.2, delay: 0.1, options: [], animations: { @@ -319,96 +397,13 @@ public typealias ValueChangeBlock = () -> () } else { self.thumbLeadingConstraint?.constant = 1 } + self.layoutIfNeeded() + }) { finished in self.valueShouldChange = true } } - - public func setState(_ state: Bool, animated: Bool) { - setState(state, withoutBlockAnimated: animated) - - valueChangedBlock?() - - // if delegate && delegate.responds(to: #selector(formValidationProtocol)) && delegate.perform(#selector(formValidationProtocol)).responds(to: #selector(Unmanaged.formValidatorModel)) { - let formValidator = delegate.perform(#selector(formValidationProtocol)).perform(#selector(Unmanaged.formValidatorModel)) as? FormValidator - formValidator?.enableByValidation() - // } - } - - public func setState(_ state: Bool, withoutBlockAnimated animated: Bool) { - - isOn = state - - if !shouldTouchToSwitch { - thumbEnlargeAnimation() - thumbMoveAnitmationTo(on: isOn) - thumbShakeAnitmationTo(on: isOn) - } - - if isOn { - thumbLeadingConstraint?.priority = UILayoutPriority(1) - thumbTrailingConstraint?.priority = UILayoutPriority(999) - thumbTrailingConstraint?.constant = 1 - } else { - thumbTrailingConstraint?.priority = UILayoutPriority(1) - thumbLeadingConstraint?.priority = UILayoutPriority(999) - thumbLeadingConstraint?.constant = 1 - } - - setBaseColorToOn(isOn, animated: animated) - thumbReformAnimation(animated) - accessibilityValue = state ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") - setNeedsLayout() - layoutIfNeeded() - } - - @discardableResult - public func changeValue() -> Bool { - - isOn.toggle() - shouldTouchToSwitch = false - setState(isOn, animated: true) - shouldTouchToSwitch = true - sendActions(for: .valueChanged) - - return isOn - } - - func touchMoves(toLeft touches: Set?) -> Bool { - - let location = touches?.first?.location(in: self) - let x = CGFloat(location?.x ?? 0.0) - return x < frame.size.width / 2.0 - } - - override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - - let faultTolerance: CGFloat = 20.0 - let area = bounds.insetBy(dx: -faultTolerance, dy: -faultTolerance) - return area.contains(point) - } - - open override func touchesEnded(_ touches: Set, with event: UIEvent?) { - - sendActions(for: .touchUpInside) - } - - class func getTrackWidth() -> CGFloat { - return (MFSizeObject(standardSize: Switch.trackSize.width, standardiPadPortraitSize: CGFloat(Switch.trackSize.width * 1.5)))!.getValueBasedOnApplicationWidth() - } - - class func getTrackHeight() -> CGFloat { - return (MFSizeObject(standardSize: Switch.trackSize.height, standardiPadPortraitSize: CGFloat(Switch.trackSize.height * 1.5) ))!.getValueBasedOnApplicationWidth() - } - - class func getThumbWidth() -> CGFloat { - return (MFSizeObject(standardSize: Switch.thumbSize.width, standardiPadPortraitSize: CGFloat(Switch.thumbSize.width * 1.5)))!.getValueBasedOnApplicationWidth() - } - - class func getThumbHeight() -> CGFloat { - return (MFSizeObject(standardSize: Switch.thumbSize.height, standardiPadPortraitSize: CGFloat(Switch.thumbSize.height * 1.5)))!.getValueBasedOnApplicationWidth() - } } // MARK: - Accessibility @@ -438,9 +433,9 @@ extension Switch { // MARK: - MVMCoreUIMoleculeViewProtocol extension Switch { - public override func setWithJSON(_ json: [AnyHashable : Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable : Any]?) { - self.json = json - delegate = delegateObject + public override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { + super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) + self.delegateObject = delegateObject guard let dictionary = json else { return } @@ -454,11 +449,11 @@ extension Switch { trackTintColor?.off = UIColor.mfGet(forHex: color) } - if let color = dictionary["onthumbTintColor"] as? String { + if let color = dictionary["onThumbTintColor"] as? String { thumbTintColor?.on = UIColor.mfGet(forHex: color) } - if let color = dictionary["offthumbTintColor"] as? String { + if let color = dictionary["offThumbTintColor"] as? String { thumbTintColor?.off = UIColor.mfGet(forHex: color) } From dd4770706493771679ae1066888a1cb077292284 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 10 Dec 2019 08:54:01 -0500 Subject: [PATCH 05/18] the latest switch. --- MVMCoreUI/Atoms/Views/Switch.swift | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Switch.swift b/MVMCoreUI/Atoms/Views/Switch.swift index 03724267..8ea86227 100644 --- a/MVMCoreUI/Atoms/Views/Switch.swift +++ b/MVMCoreUI/Atoms/Views/Switch.swift @@ -11,7 +11,12 @@ import UIKit public typealias ValueChangeBlock = () -> () - +/** + A custom implementation of Apple's UISwitch. + + Track: The background of the switch control. + Thumb: The circular indicator that slides on the track. + */ @objcMembers open class Switch: Control, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol { //-------------------------------------------------- // MARK: - Properties @@ -21,10 +26,14 @@ public typealias ValueChangeBlock = () -> () public var thumbTintColor: (on: UIColor?, off: UIColor?)? = (on: .white, off: .white) public var disabledTintColor: (track: UIColor?, thumb: UIColor?)? = (track: .mfSilver(), thumb: .white) - var shouldTouchToSwitch = false - var valueChangedBlock: ValueChangeBlock? - var actionBlock: ValueChangeBlock? + private var valueShouldChange = false + private var canChangeValue = false + private var shouldTouchToSwitch = false + private var valueChangedBlock: ValueChangeBlock? + private var actionBlock: ValueChangeBlock? + + // Sizes are from InVision design specs. static let trackSize = CGSize(width: 46, height: 24) static let thumbSize = CGSize(width: 22, height: 22) @@ -32,9 +41,6 @@ public typealias ValueChangeBlock = () -> () private var thumbView = View() - private var valueShouldChange = false - private var canChangeValue = false - //-------------------------------------------------- // MARK: - Computed Properties //-------------------------------------------------- From 83370956c1b43cdec3a389e23d59d078e43f1993 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 10 Dec 2019 10:34:08 -0500 Subject: [PATCH 06/18] movemt,. --- MVMCoreUI/Atoms/Views/Switch.swift | 34 ++++++++++++------------------ 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Switch.swift b/MVMCoreUI/Atoms/Views/Switch.swift index 8ea86227..da2c13bf 100644 --- a/MVMCoreUI/Atoms/Views/Switch.swift +++ b/MVMCoreUI/Atoms/Views/Switch.swift @@ -9,8 +9,6 @@ import MVMCore import UIKit -public typealias ValueChangeBlock = () -> () - /** A custom implementation of Apple's UISwitch. @@ -27,11 +25,11 @@ public typealias ValueChangeBlock = () -> () public var disabledTintColor: (track: UIColor?, thumb: UIColor?)? = (track: .mfSilver(), thumb: .white) private var valueShouldChange = false - private var canChangeValue = false +// private var canChangeValue = false private var shouldTouchToSwitch = false - private var valueChangedBlock: ValueChangeBlock? - private var actionBlock: ValueChangeBlock? + private var valueChangedBlock: ActionBlock? + private var actionBlock: ActionBlock? // Sizes are from InVision design specs. static let trackSize = CGSize(width: 46, height: 24) @@ -90,13 +88,13 @@ public typealias ValueChangeBlock = () -> () self.init(frame: .zero) } - public convenience init(isOn: Bool, changeBlock: ValueChangeBlock?) { + public convenience init(isOn: Bool, changeBlock: ActionBlock?) { self.init(frame: .zero) self.isOn = isOn valueChangedBlock = changeBlock } - public convenience init(changeBlock: ValueChangeBlock?) { + public convenience init(changeBlock: ActionBlock?) { self.init(frame: .zero) valueChangedBlock = changeBlock } @@ -116,19 +114,19 @@ public typealias ValueChangeBlock = () -> () heightConstraint?.constant = Switch.getTrackHeight() widthConstraint?.constant = Switch.getTrackWidth() - layer.cornerRadius = Switch.getTrackHeight() / 2.0 - thumbView.layer.cornerRadius = Switch.getThumbHeight() / 2.0 - thumbHeightConstraint?.constant = Switch.getThumbHeight() thumbWidthConstraint?.constant = Switch.getThumbWidth() + + layer.cornerRadius = Switch.getTrackHeight() / 2.0 + thumbView.layer.cornerRadius = Switch.getThumbHeight() / 2.0 } public override func setupView() { super.setupView() - guard !subviews.isEmpty else { return } + guard subviews.isEmpty else { return } - canChangeValue = true +// canChangeValue = true shouldTouchToSwitch = true valueShouldChange = true @@ -263,11 +261,6 @@ public typealias ValueChangeBlock = () -> () valueShouldChange = true } - open override func touchesEnded(_ touches: Set, with event: UIEvent?) { - - sendActions(for: .touchUpInside) - } - public func touchesMoved(_ touches: Set, with event: UIEvent) { if shouldTouchToSwitch { @@ -304,14 +297,13 @@ public typealias ValueChangeBlock = () -> () if shouldTouchToSwitch { thumbReformAnimation(true) - canChangeValue = true } sendActions(for: .touchCancel) } //-------------------------------------------------- - // MARK: - Animation + // MARK: - Animations //-------------------------------------------------- public func thumbEnlargeAnimation() { @@ -443,10 +435,10 @@ extension Switch { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) self.delegateObject = delegateObject - guard let dictionary = json else { return } - FormValidator.setupValidation(molecule: self, delegate: delegateObject?.formValidationProtocol) + guard let dictionary = json else { return } + if let color = dictionary["onTintColor"] as? String { trackTintColor?.on = UIColor.mfGet(forHex: color) } From ab0ff64677e39bdb30f5906cc847c247bc0e33fb Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 10 Dec 2019 14:38:32 -0500 Subject: [PATCH 07/18] General Working order. --- MVMCoreUI/Atoms/Views/Switch.swift | 311 ++++++++++------------------- 1 file changed, 102 insertions(+), 209 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Switch.swift b/MVMCoreUI/Atoms/Views/Switch.swift index da2c13bf..332bacc5 100644 --- a/MVMCoreUI/Atoms/Views/Switch.swift +++ b/MVMCoreUI/Atoms/Views/Switch.swift @@ -10,25 +10,25 @@ import MVMCore import UIKit /** - A custom implementation of Apple's UISwitch. - - Track: The background of the switch control. - Thumb: The circular indicator that slides on the track. + A custom implementation of Apple's UISwitch. + + Track: The background of the switch control. + Thumb: The circular indicator that slides on the track. */ @objcMembers open class Switch: Control, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - + public var trackTintColor: (on: UIColor?, off: UIColor?)? = (on: .mfShamrock(), off: .black) public var thumbTintColor: (on: UIColor?, off: UIColor?)? = (on: .white, off: .white) public var disabledTintColor: (track: UIColor?, thumb: UIColor?)? = (track: .mfSilver(), thumb: .white) - private var valueShouldChange = false -// private var canChangeValue = false - private var shouldTouchToSwitch = false + public var isAnimated = true + + private var valueShouldChange = true + private var shouldTouchToSwitch = true - private var valueChangedBlock: ActionBlock? private var actionBlock: ActionBlock? // Sizes are from InVision design specs. @@ -37,7 +37,12 @@ import UIKit static let shakeIntensity: CGFloat = 2 - private var thumbView = View() + private var thumbView: View = { + let view = View() + view.backgroundColor = .white + view.layer.cornerRadius = Switch.getThumbHeight() / 2.0 + return view + }() //-------------------------------------------------- // MARK: - Computed Properties @@ -53,8 +58,49 @@ import UIKit open var isOn: Bool = false { didSet { - backgroundColor = isOn ? trackTintColor?.on : trackTintColor?.off - thumbView.backgroundColor = isOn ? thumbTintColor?.on : thumbTintColor?.off + isSelected = isOn + + if isAnimated { + UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: { + if self.isOn { + self.thumbView.backgroundColor = self.thumbTintColor?.on + self.backgroundColor = self.trackTintColor?.on + + } else { + self.thumbView.backgroundColor = self.thumbTintColor?.off + self.backgroundColor = self.trackTintColor?.off + } + + }, completion: nil) + + UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.3, options: [], animations: { + if self.isOn { + self.thumbLeadingConstraint?.isActive = true + self.thumbTrailingConstraint?.isActive = false + self.relativeThumbLeadingConstraint?.isActive = false + self.relativeThumbTrailingConstraint?.isActive = true + + } else { + self.thumbLeadingConstraint?.isActive = false + self.thumbTrailingConstraint?.isActive = true + self.relativeThumbLeadingConstraint?.isActive = true + self.relativeThumbTrailingConstraint?.isActive = false + } + + self.thumbWidthConstraint?.constant = Switch.getThumbWidth() + PaddingOne + self.layoutIfNeeded() + + }, completion: nil) + + } else { + backgroundColor = isOn ? trackTintColor?.on : trackTintColor?.off + thumbView.backgroundColor = isOn ? thumbTintColor?.on : thumbTintColor?.off + } + + FormValidator.enableByValidationWith(delegate: delegateObject?.formValidationProtocol) + accessibilityValue = isOn ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") + setNeedsLayout() + layoutIfNeeded() } } @@ -70,6 +116,9 @@ import UIKit private var thumbLeadingConstraint: NSLayoutConstraint? private var thumbTrailingConstraint: NSLayoutConstraint? + private var relativeThumbLeadingConstraint: NSLayoutConstraint? + private var relativeThumbTrailingConstraint: NSLayoutConstraint? + private var thumbHeightConstraint: NSLayoutConstraint? private var thumbWidthConstraint: NSLayoutConstraint? private var heightConstraint: NSLayoutConstraint? @@ -88,15 +137,15 @@ import UIKit self.init(frame: .zero) } - public convenience init(isOn: Bool, changeBlock: ActionBlock?) { + public convenience init(isOn: Bool, actionBlock: ActionBlock?) { self.init(frame: .zero) self.isOn = isOn - valueChangedBlock = changeBlock + self.actionBlock = actionBlock } - public convenience init(changeBlock: ActionBlock?) { + public convenience init(actionBlock: ActionBlock?) { self.init(frame: .zero) - valueChangedBlock = changeBlock + self.actionBlock = actionBlock } public required init?(coder: NSCoder) { @@ -126,10 +175,6 @@ import UIKit guard subviews.isEmpty else { return } -// canChangeValue = true - shouldTouchToSwitch = true - valueShouldChange = true - heightConstraint = heightAnchor.constraint(equalToConstant: Switch.trackSize.height) heightConstraint?.isActive = true @@ -137,10 +182,7 @@ import UIKit widthConstraint?.isActive = true layer.cornerRadius = Switch.trackSize.height / 2.0 - - let thumbView = MVMCoreUICommonViewsUtility.commonView() - thumbView.backgroundColor = .white - thumbView.layer.cornerRadius = Switch.getThumbHeight() / 2.0 + backgroundColor = trackTintColor?.off addSubview(thumbView) @@ -149,13 +191,13 @@ import UIKit thumbView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true thumbTrailingConstraint = trailingAnchor.constraint(equalTo: thumbView.trailingAnchor, constant: 1) + relativeThumbTrailingConstraint = trailingAnchor.constraint(greaterThanOrEqualTo: thumbView.trailingAnchor) + relativeThumbTrailingConstraint?.isActive = true + + relativeThumbLeadingConstraint = thumbView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor) thumbLeadingConstraint = thumbView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 1) thumbLeadingConstraint?.isActive = true - shouldTouchToSwitch = false - setState(isOn, animated: false) - shouldTouchToSwitch = true - accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Switch_buttonlabel") } @@ -176,59 +218,23 @@ import UIKit } //-------------------------------------------------- - // MARK: - Methods + // MARK: - Actions //-------------------------------------------------- - @objc func addCustomAction() { - - actionBlock?() + open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) { + super.sendAction(action, to: target, for: event) + toggleAndAction() } - public func setState(_ state: Bool, animated: Bool) { - - setState(state, withoutBlockAnimated: animated) - valueChangedBlock?() - FormValidator.enableByValidationWith(delegate: delegateObject?.formValidationProtocol) + open override func sendActions(for controlEvents: UIControl.Event) { + super.sendActions(for: controlEvents) + toggleAndAction() } - public func setState(_ state: Bool, withoutBlockAnimated animated: Bool) { - - isOn = state - - if !shouldTouchToSwitch { - thumbEnlargeAnimation() - thumbMoveAnitmationTo(on: isOn) - thumbShakeAnitmationTo(on: isOn) - } - - if isOn { - thumbLeadingConstraint?.priority = UILayoutPriority(1) - thumbTrailingConstraint?.priority = UILayoutPriority(999) - thumbTrailingConstraint?.constant = 1 - - } else { - thumbTrailingConstraint?.priority = UILayoutPriority(1) - thumbLeadingConstraint?.priority = UILayoutPriority(999) - thumbLeadingConstraint?.constant = 1 - } - - setBaseColorToOn(isOn, animated: animated) - thumbReformAnimation(animated) - accessibilityValue = state ? MVMCoreUIUtility.hardcodedString(withKey: "AccOn") : MVMCoreUIUtility.hardcodedString(withKey: "AccOff") - setNeedsLayout() - layoutIfNeeded() - } - - @discardableResult - public func changeValue() -> Bool { - + /// This will toggle the state of the Switch and execute the actionBlock if provided. + public func toggleAndAction() { isOn.toggle() - shouldTouchToSwitch = false - setState(isOn, animated: true) - shouldTouchToSwitch = true - sendActions(for: .valueChanged) - - return isOn + actionBlock?() } //-------------------------------------------------- @@ -244,61 +250,24 @@ import UIKit public func touchesBegan(_ touches: Set, with event: UIEvent) { - thumbEnlargeAnimation() + UIView.animate(withDuration: 0.1, animations: { + self.thumbWidthConstraint?.constant += PaddingOne + self.layoutIfNeeded() + }) + sendActions(for: .touchDown) } - public func touchesEnded(_ touches: Set, with event: UIEvent) { + public override func touchesEnded(_ touches: Set, with event: UIEvent?) { - if shouldTouchToSwitch && valueShouldChange { - changeValue() - } - - sendActions(for: .touchUpInside) - thumbMoveAnitmationTo(on: isOn) - thumbShakeAnitmationTo(on: isOn) - thumbReformAnimation(true) + thumbReformAnimation() valueShouldChange = true - } - - public func touchesMoved(_ touches: Set, with event: UIEvent) { - - if shouldTouchToSwitch { - if touchMoves(toLeft: touches) { - thumbMoveAnitmationTo(on: false) - thumbShakeAnitmationTo(on: false) - thumbReformAnimation(true) - } else { - thumbMoveAnitmationTo(on: true) - thumbShakeAnitmationTo(on: true) - thumbReformAnimation(true) - } - } - /* - if touchIsOutSide(touches) { - sendActions(for: .touchDragOutside) - if !shouldTouchToSwitch { - thumbReformAnimation(true) - } - } else { - sendActions(for: .touchDragInside) - } - */ - } - - func touchMoves(toLeft touches: Set?) -> Bool { - - let location = touches?.first?.location(in: self) - let x = CGFloat(location?.x ?? 0.0) - return x < frame.size.width / 2.0 + sendActions(for: .touchUpInside) } public func touchesCancelled(_ touches: Set, with event: UIEvent) { - if shouldTouchToSwitch { - thumbReformAnimation(true) - } - + thumbReformAnimation() sendActions(for: .touchCancel) } @@ -306,17 +275,9 @@ import UIKit // MARK: - Animations //-------------------------------------------------- - public func thumbEnlargeAnimation() { + public func thumbReformAnimation() { - UIView.animate(withDuration: 0.1, animations: { - self.thumbWidthConstraint?.constant += PaddingOne - self.layoutIfNeeded() - }) - } - - public func thumbReformAnimation(_ animated: Bool) { - - if animated { + if isAnimated { UIView.animate(withDuration: 0.1, animations: { self.thumbWidthConstraint?.constant = Switch.getThumbWidth() self.layoutIfNeeded() @@ -326,82 +287,6 @@ import UIKit layoutIfNeeded() } } - - public func thumbMoveAnitmationTo(on toOn: Bool) { - - UIView.animate(withDuration: 0.1, animations: { - if toOn { - self.thumbLeadingConstraint?.priority = UILayoutPriority(1) - self.thumbTrailingConstraint?.priority = UILayoutPriority(999) - self.thumbTrailingConstraint?.constant = 1 - - } else { - self.thumbLeadingConstraint?.priority = UILayoutPriority(999) - self.thumbTrailingConstraint?.priority = UILayoutPriority(1) - self.thumbLeadingConstraint?.constant = 1 - } - - self.setBaseColorToOn(toOn, animated: true) - self.thumbWidthConstraint?.constant = Switch.getThumbWidth() + PaddingOne - self.valueShouldChange = toOn != self.isOn - self.layoutIfNeeded() - }) - } - - public func setBaseColorToOn(_ toOn: Bool, animated: Bool) { - - if animated { - UIView.beginAnimations(nil, context: nil) - UIView.setAnimationDuration(0.2) - UIView.setAnimationCurve(.easeIn) - - if toOn { - thumbView.backgroundColor = thumbTintColor?.on - backgroundColor = trackTintColor?.on - - } else { - thumbView.backgroundColor = thumbTintColor?.off - backgroundColor = trackTintColor?.off - } - UIView.commitAnimations() - - } else if isOn { - thumbView.backgroundColor = thumbTintColor?.on - backgroundColor = trackTintColor?.on - - } else { - thumbView.backgroundColor = thumbTintColor?.off - backgroundColor = trackTintColor?.off - } - } - - //used after thumb moving to match the behavior of default uiswitch - public func thumbShakeAnitmationTo(on toOn: Bool) { - - UIView.animate(withDuration: 0.1, delay: 0.1, options: .curveEaseIn, animations: { - if toOn { - self.thumbTrailingConstraint?.constant = Switch.shakeIntensity - } else { - self.thumbLeadingConstraint?.constant = Switch.shakeIntensity - } - - self.layoutIfNeeded() - - }, completion: nil) - - UIView.animate(withDuration: 0.2, delay: 0.1, options: [], animations: { - if toOn { - self.thumbTrailingConstraint?.constant = 1 - } else { - self.thumbLeadingConstraint?.constant = 1 - } - - self.layoutIfNeeded() - - }) { finished in - self.valueShouldChange = true - } - } } // MARK: - Accessibility @@ -455,10 +340,18 @@ extension Switch { thumbTintColor?.off = UIColor.mfGet(forHex: color) } - setState(dictionary["state"] as? Bool ?? false, animated: false) + if let state = dictionary["state"] as? Bool { + isAnimated = false + isOn = state + isAnimated = true + } - if let _ = dictionary["actionMap"] as? [String: Any] { - addTarget(self, action: #selector(addCustomAction), for: .touchUpInside) + if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { +// actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } + } + + if let isAnimated = dictionary["state"] as? Bool { + self.isAnimated = isAnimated } } From c32a0d71b0a081359fde5c7fd4fe327722e4b18b Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Tue, 10 Dec 2019 15:58:59 -0500 Subject: [PATCH 08/18] good working state. --- MVMCoreUI/Atoms/Views/Switch.swift | 111 +++++++++++++++++------------ 1 file changed, 67 insertions(+), 44 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Switch.swift b/MVMCoreUI/Atoms/Views/Switch.swift index 332bacc5..ee257ce8 100644 --- a/MVMCoreUI/Atoms/Views/Switch.swift +++ b/MVMCoreUI/Atoms/Views/Switch.swift @@ -26,17 +26,12 @@ import UIKit public var isAnimated = true - private var valueShouldChange = true - private var shouldTouchToSwitch = true - private var actionBlock: ActionBlock? // Sizes are from InVision design specs. static let trackSize = CGSize(width: 46, height: 24) static let thumbSize = CGSize(width: 22, height: 22) - static let shakeIntensity: CGFloat = 2 - private var thumbView: View = { let view = View() view.backgroundColor = .white @@ -56,6 +51,12 @@ import UIKit } } + public var isLocked: Bool = false { + didSet { + isUserInteractionEnabled = !isLocked + } + } + open var isOn: Bool = false { didSet { isSelected = isOn @@ -73,21 +74,10 @@ import UIKit }, completion: nil) - UIView.animate(withDuration: 0.3, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.3, options: [], animations: { - if self.isOn { - self.thumbLeadingConstraint?.isActive = true - self.thumbTrailingConstraint?.isActive = false - self.relativeThumbLeadingConstraint?.isActive = false - self.relativeThumbTrailingConstraint?.isActive = true - - } else { - self.thumbLeadingConstraint?.isActive = false - self.thumbTrailingConstraint?.isActive = true - self.relativeThumbLeadingConstraint?.isActive = true - self.relativeThumbTrailingConstraint?.isActive = false - } + UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.2, options: [], animations: { - self.thumbWidthConstraint?.constant = Switch.getThumbWidth() + PaddingOne + self.constrainThumb() + self.thumbWidthConstraint?.constant = Switch.getThumbWidth() self.layoutIfNeeded() }, completion: nil) @@ -95,6 +85,7 @@ import UIKit } else { backgroundColor = isOn ? trackTintColor?.on : trackTintColor?.off thumbView.backgroundColor = isOn ? thumbTintColor?.on : thumbTintColor?.off + self.constrainThumb() } FormValidator.enableByValidationWith(delegate: delegateObject?.formValidationProtocol) @@ -116,14 +107,17 @@ import UIKit private var thumbLeadingConstraint: NSLayoutConstraint? private var thumbTrailingConstraint: NSLayoutConstraint? - private var relativeThumbLeadingConstraint: NSLayoutConstraint? - private var relativeThumbTrailingConstraint: NSLayoutConstraint? - private var thumbHeightConstraint: NSLayoutConstraint? private var thumbWidthConstraint: NSLayoutConstraint? private var heightConstraint: NSLayoutConstraint? private var widthConstraint: NSLayoutConstraint? + private func constrainThumb() { + + self.thumbLeadingConstraint?.isActive = isOn + self.thumbTrailingConstraint?.isActive = !isOn + } + //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- @@ -186,35 +180,51 @@ import UIKit addSubview(thumbView) - thumbView.heightAnchor.constraint(equalToConstant: Switch.thumbSize.height).isActive = true - thumbView.widthAnchor.constraint(equalToConstant: Switch.thumbSize.width).isActive = true + thumbHeightConstraint = thumbView.heightAnchor.constraint(equalToConstant: Switch.thumbSize.height) + thumbHeightConstraint?.isActive = true + thumbWidthConstraint = thumbView.widthAnchor.constraint(equalToConstant: Switch.thumbSize.width) + thumbWidthConstraint?.isActive = true + thumbView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + thumbView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: thumbView.bottomAnchor).isActive = true thumbTrailingConstraint = trailingAnchor.constraint(equalTo: thumbView.trailingAnchor, constant: 1) - relativeThumbTrailingConstraint = trailingAnchor.constraint(greaterThanOrEqualTo: thumbView.trailingAnchor) - relativeThumbTrailingConstraint?.isActive = true - - relativeThumbLeadingConstraint = thumbView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor) thumbLeadingConstraint = thumbView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 1) thumbLeadingConstraint?.isActive = true + let longPress = UILongPressGestureRecognizer(target: self, action: #selector(stretchThumb)) + addGestureRecognizer(longPress) + accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Switch_buttonlabel") } + @objc private func stretchThumb() { + + UIView.animate(withDuration: 0.1, animations: { + self.thumbWidthConstraint?.constant = 26 + self.layoutIfNeeded() + }) + } + class func getTrackWidth() -> CGFloat { - return (MFSizeObject(standardSize: Switch.trackSize.width, standardiPadPortraitSize: CGFloat(Switch.trackSize.width * 1.5)))!.getValueBasedOnApplicationWidth() + let trackWidth = Switch.trackSize.width + return (MFSizeObject(standardSize: trackWidth, standardiPadPortraitSize: CGFloat(Switch.trackSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? trackWidth } class func getTrackHeight() -> CGFloat { - return (MFSizeObject(standardSize: Switch.trackSize.height, standardiPadPortraitSize: CGFloat(Switch.trackSize.height * 1.5) ))!.getValueBasedOnApplicationWidth() + let trackHeight = Switch.trackSize.height + return (MFSizeObject(standardSize: trackHeight, standardiPadPortraitSize: CGFloat(Switch.trackSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? trackHeight } class func getThumbWidth() -> CGFloat { - return (MFSizeObject(standardSize: Switch.thumbSize.width, standardiPadPortraitSize: CGFloat(Switch.thumbSize.width * 1.5)))!.getValueBasedOnApplicationWidth() + let thumbWidth = Switch.thumbSize.width + return (MFSizeObject(standardSize: thumbWidth, standardiPadPortraitSize: CGFloat(Switch.thumbSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? thumbWidth } class func getThumbHeight() -> CGFloat { - return (MFSizeObject(standardSize: Switch.thumbSize.height, standardiPadPortraitSize: CGFloat(Switch.thumbSize.height * 1.5)))!.getValueBasedOnApplicationWidth() + let thumbHeight = Switch.thumbSize.width + return (MFSizeObject(standardSize: thumbHeight, standardiPadPortraitSize: CGFloat(Switch.thumbSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? thumbHeight } //-------------------------------------------------- @@ -248,22 +258,35 @@ import UIKit return area.contains(point) } - public func touchesBegan(_ touches: Set, with event: UIEvent) { - - UIView.animate(withDuration: 0.1, animations: { - self.thumbWidthConstraint?.constant += PaddingOne - self.layoutIfNeeded() - }) - - sendActions(for: .touchDown) - } - public override func touchesEnded(_ touches: Set, with event: UIEvent?) { thumbReformAnimation() - valueShouldChange = true sendActions(for: .touchUpInside) } + /* + open override func touchesMoved(_ touches: Set, with event: UIEvent?) { + + if shouldTouchToSwitch { + if touchMoves(toLeft: touches) { + thumbMoveAnitmationTo(on: false) + thumbShakeAnitmationTo(on: false) + thumbReformAnimation(true) + } else { + thumbMoveAnitmationTo(on: true) + thumbShakeAnitmationTo(on: true) + thumbReformAnimation(true) + } + } + + if touchIsOutSide(touches) { + sendActions(for: .touchDragOutside) + if !shouldTouchToSwitch { + thumbReformAnimation(true) + } + } else { + sendActions(for: .touchDragInside) + } + }*/ public func touchesCancelled(_ touches: Set, with event: UIEvent) { @@ -347,7 +370,7 @@ extension Switch { } 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) } } if let isAnimated = dictionary["state"] as? Bool { From d1ee242ca7cadbec92edd495b4bc8e4e39c2f232 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 11 Dec 2019 10:47:52 -0500 Subject: [PATCH 09/18] should did action. --- MVMCoreUI/Atoms/Views/Switch.swift | 84 ++++++++++++++---------------- 1 file changed, 39 insertions(+), 45 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Switch.swift b/MVMCoreUI/Atoms/Views/Switch.swift index ee257ce8..3fbe1055 100644 --- a/MVMCoreUI/Atoms/Views/Switch.swift +++ b/MVMCoreUI/Atoms/Views/Switch.swift @@ -9,6 +9,8 @@ import MVMCore import UIKit +public typealias ActionBlockConfirmation = () -> (Bool) + /** A custom implementation of Apple's UISwitch. @@ -26,7 +28,10 @@ import UIKit public var isAnimated = true - private var actionBlock: ActionBlock? + public var didSwitchAction: ActionBlock? + public var shouldSwitchAction: ActionBlockConfirmation? = { + return { return true } + }() // Sizes are from InVision design specs. static let trackSize = CGSize(width: 46, height: 24) @@ -131,15 +136,21 @@ import UIKit self.init(frame: .zero) } - public convenience init(isOn: Bool, actionBlock: ActionBlock?) { + public convenience init(isOn: Bool, didSwitchAction: ActionBlock?) { self.init(frame: .zero) self.isOn = isOn - self.actionBlock = actionBlock + self.didSwitchAction = didSwitchAction } - public convenience init(actionBlock: ActionBlock?) { + public convenience init(didSwitchAction: ActionBlock?) { self.init(frame: .zero) - self.actionBlock = actionBlock + self.didSwitchAction = didSwitchAction + } + + public convenience init(shouldSwitchAction: ActionBlockConfirmation?, didSwitchAction: ActionBlock?) { + self.init(frame: .zero) + self.didSwitchAction = didSwitchAction + self.shouldSwitchAction = shouldSwitchAction } public required init?(coder: NSCoder) { @@ -184,7 +195,6 @@ import UIKit thumbHeightConstraint?.isActive = true thumbWidthConstraint = thumbView.widthAnchor.constraint(equalToConstant: Switch.thumbSize.width) thumbWidthConstraint?.isActive = true - thumbView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true thumbView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true bottomAnchor.constraint(greaterThanOrEqualTo: thumbView.bottomAnchor).isActive = true @@ -193,20 +203,9 @@ import UIKit thumbLeadingConstraint = thumbView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 1) thumbLeadingConstraint?.isActive = true - let longPress = UILongPressGestureRecognizer(target: self, action: #selector(stretchThumb)) - addGestureRecognizer(longPress) - accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Switch_buttonlabel") } - @objc private func stretchThumb() { - - UIView.animate(withDuration: 0.1, animations: { - self.thumbWidthConstraint?.constant = 26 - self.layoutIfNeeded() - }) - } - class func getTrackWidth() -> CGFloat { let trackWidth = Switch.trackSize.width return (MFSizeObject(standardSize: trackWidth, standardiPadPortraitSize: CGFloat(Switch.trackSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? trackWidth @@ -243,8 +242,11 @@ import UIKit /// This will toggle the state of the Switch and execute the actionBlock if provided. public func toggleAndAction() { - isOn.toggle() - actionBlock?() + + if let result = shouldSwitchAction?(), result { + isOn.toggle() + didSwitchAction?() + } } //-------------------------------------------------- @@ -258,35 +260,27 @@ import UIKit return area.contains(point) } + open override func touchesBegan(_ touches: Set, with event: UIEvent?) { + + UIView.animate(withDuration: 0.1, animations: { + self.thumbWidthConstraint?.constant += PaddingOne + self.layoutIfNeeded() + }) + } + public override func touchesEnded(_ touches: Set, with event: UIEvent?) { thumbReformAnimation() + + guard let coordinates: CGPoint = touches.first?.location(in: self), + coordinates.x > -20, + coordinates.x < bounds.width + 20, + coordinates.y > -20, + coordinates.y < bounds.height + 20 + else { return } + sendActions(for: .touchUpInside) } - /* - open override func touchesMoved(_ touches: Set, with event: UIEvent?) { - - if shouldTouchToSwitch { - if touchMoves(toLeft: touches) { - thumbMoveAnitmationTo(on: false) - thumbShakeAnitmationTo(on: false) - thumbReformAnimation(true) - } else { - thumbMoveAnitmationTo(on: true) - thumbShakeAnitmationTo(on: true) - thumbReformAnimation(true) - } - } - - if touchIsOutSide(touches) { - sendActions(for: .touchDragOutside) - if !shouldTouchToSwitch { - thumbReformAnimation(true) - } - } else { - sendActions(for: .touchDragInside) - } - }*/ public func touchesCancelled(_ touches: Set, with event: UIEvent) { @@ -320,7 +314,7 @@ extension Switch { } } -// MARK: FormValidationProtocol +// MARK: - FormValidationProtocol extension Switch { public func isValidField() -> Bool { @@ -370,7 +364,7 @@ extension Switch { } 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) } } if let isAnimated = dictionary["state"] as? Bool { From eab910011ecabb3c4b13a28954450cbfec7a7ccb Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 11 Dec 2019 15:10:26 -0500 Subject: [PATCH 10/18] General working order of the Toggle. --- MVMCoreUI.xcodeproj/project.pbxproj | 8 +- .../Views/{Switch.swift => Toggle.swift} | 196 +++++++++--------- .../MVMCoreUIMoleculeMappingObject.m | 2 +- 3 files changed, 102 insertions(+), 104 deletions(-) rename MVMCoreUI/Atoms/Views/{Switch.swift => Toggle.swift} (55%) diff --git a/MVMCoreUI.xcodeproj/project.pbxproj b/MVMCoreUI.xcodeproj/project.pbxproj index 33529648..880427d0 100644 --- a/MVMCoreUI.xcodeproj/project.pbxproj +++ b/MVMCoreUI.xcodeproj/project.pbxproj @@ -28,7 +28,7 @@ 0A41BA6E2344FCD400D4C0BC /* CATransaction+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A41BA6D2344FCD400D4C0BC /* CATransaction+Extension.swift */; }; 0A7BAD74232A8DC700FB8E22 /* HeadlineBodyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */; }; 0A7BAFA1232BE61800FB8E22 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */; }; - 0AA33B3A2398524F0067DD0F /* Switch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B392398524F0067DD0F /* Switch.swift */; }; + 0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AA33B392398524F0067DD0F /* Toggle.swift */; }; 943784F5236B77BB006A1E82 /* GraphView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F3236B77BB006A1E82 /* GraphView.swift */; }; 943784F6236B77BB006A1E82 /* GraphViewAnimationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */; }; 9455B19C234F8A0400A574DB /* MVMAnimationFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */; }; @@ -227,7 +227,7 @@ 0A7BAD73232A8DC700FB8E22 /* HeadlineBodyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HeadlineBodyButton.swift; sourceTree = ""; }; 0A7BAFA0232BE61800FB8E22 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; 0A7BAFA2232BE63400FB8E22 /* CheckboxWithLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxWithLabelView.swift; sourceTree = ""; }; - 0AA33B392398524F0067DD0F /* Switch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Switch.swift; sourceTree = ""; }; + 0AA33B392398524F0067DD0F /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = ""; }; 943784F3236B77BB006A1E82 /* GraphView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphView.swift; sourceTree = ""; }; 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GraphViewAnimationHandler.swift; sourceTree = ""; }; 9455B19B234F8A0400A574DB /* MVMAnimationFramework.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MVMAnimationFramework.framework; path = ../SharedFrameworks/MVMAnimationFramework.framework; sourceTree = ""; }; @@ -781,7 +781,7 @@ 01004F2F22721C3800991ECC /* RadioButton.swift */, 943784F3236B77BB006A1E82 /* GraphView.swift */, 943784F4236B77BB006A1E82 /* GraphViewAnimationHandler.swift */, - 0AA33B392398524F0067DD0F /* Switch.swift */, + 0AA33B392398524F0067DD0F /* Toggle.swift */, ); path = Views; sourceTree = ""; @@ -1085,7 +1085,7 @@ D29DF11721E6805F003B2FB9 /* UIColor+MFConvenience.m in Sources */, D29DF25321E6A177003B2FB9 /* MFDigitTextField.m in Sources */, D2B18B7F2360913400A9AEDC /* Control.swift in Sources */, - 0AA33B3A2398524F0067DD0F /* Switch.swift in Sources */, + 0AA33B3A2398524F0067DD0F /* Toggle.swift in Sources */, D29DF12F21E6851E003B2FB9 /* MVMCoreUITopAlertMainView.m in Sources */, DBC4392122491730001AB423 /* LabelWithInternalButton.swift in Sources */, D224798C231450C8003FCCF9 /* HeadlineBodySwitch.swift in Sources */, diff --git a/MVMCoreUI/Atoms/Views/Switch.swift b/MVMCoreUI/Atoms/Views/Toggle.swift similarity index 55% rename from MVMCoreUI/Atoms/Views/Switch.swift rename to MVMCoreUI/Atoms/Views/Toggle.swift index 3fbe1055..0ee42937 100644 --- a/MVMCoreUI/Atoms/Views/Switch.swift +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -1,5 +1,5 @@ // -// Switch.swift +// Toggle.swift // MVMCoreUI // // Created by Kevin Christiano on 12/4/19. @@ -14,33 +14,33 @@ public typealias ActionBlockConfirmation = () -> (Bool) /** A custom implementation of Apple's UISwitch. - Track: The background of the switch control. - Thumb: The circular indicator that slides on the track. + Container: The background of the toggle control. + Knob: The circular indicator that slides on the container. */ -@objcMembers open class Switch: Control, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol { +@objcMembers open class Toggle: Control, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public var trackTintColor: (on: UIColor?, off: UIColor?)? = (on: .mfShamrock(), off: .black) - public var thumbTintColor: (on: UIColor?, off: UIColor?)? = (on: .white, off: .white) - public var disabledTintColor: (track: UIColor?, thumb: UIColor?)? = (track: .mfSilver(), thumb: .white) + public var containerTintColor: (on: UIColor?, off: UIColor?)? = (on: .mfShamrock(), off: .black) + public var knobTintColor: (on: UIColor?, off: UIColor?)? = (on: .white, off: .white) + public var disabledTintColor: (container: UIColor?, knob: UIColor?)? = (container: .mfSilver(), knob: .white) public var isAnimated = true - public var didSwitchAction: ActionBlock? - public var shouldSwitchAction: ActionBlockConfirmation? = { + public var didToggleAction: ActionBlock? + public var shouldToggleAction: ActionBlockConfirmation? = { return { return true } }() // Sizes are from InVision design specs. - static let trackSize = CGSize(width: 46, height: 24) - static let thumbSize = CGSize(width: 22, height: 22) + static let containerSize = CGSize(width: 46, height: 24) + static let knobSize = CGSize(width: 22, height: 22) - private var thumbView: View = { + private var knobView: View = { let view = View() view.backgroundColor = .white - view.layer.cornerRadius = Switch.getThumbHeight() / 2.0 + view.layer.cornerRadius = Toggle.getKnobHeight() / 2.0 return view }() @@ -51,8 +51,8 @@ public typealias ActionBlockConfirmation = () -> (Bool) open override var isEnabled: Bool { didSet { isUserInteractionEnabled = isEnabled - backgroundColor = isEnabled ? trackTintColor?.on : disabledTintColor?.track - thumbView.backgroundColor = isEnabled ? thumbTintColor?.on : disabledTintColor?.thumb + backgroundColor = isEnabled ? containerTintColor?.on : disabledTintColor?.container + knobView.backgroundColor = isEnabled ? knobTintColor?.on : disabledTintColor?.knob } } @@ -69,28 +69,25 @@ public typealias ActionBlockConfirmation = () -> (Bool) if isAnimated { UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: { if self.isOn { - self.thumbView.backgroundColor = self.thumbTintColor?.on - self.backgroundColor = self.trackTintColor?.on + self.knobView.backgroundColor = self.knobTintColor?.on + self.backgroundColor = self.containerTintColor?.on } else { - self.thumbView.backgroundColor = self.thumbTintColor?.off - self.backgroundColor = self.trackTintColor?.off + self.knobView.backgroundColor = self.knobTintColor?.off + self.backgroundColor = self.containerTintColor?.off } - }, completion: nil) UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.2, options: [], animations: { - - self.constrainThumb() - self.thumbWidthConstraint?.constant = Switch.getThumbWidth() + self.constrainKnob() + self.knobWidthConstraint?.constant = Toggle.getKnobWidth() self.layoutIfNeeded() - }, completion: nil) } else { - backgroundColor = isOn ? trackTintColor?.on : trackTintColor?.off - thumbView.backgroundColor = isOn ? thumbTintColor?.on : thumbTintColor?.off - self.constrainThumb() + backgroundColor = isOn ? containerTintColor?.on : containerTintColor?.off + knobView.backgroundColor = isOn ? knobTintColor?.on : knobTintColor?.off + self.constrainKnob() } FormValidator.enableByValidationWith(delegate: delegateObject?.formValidationProtocol) @@ -110,17 +107,17 @@ public typealias ActionBlockConfirmation = () -> (Bool) // MARK: - Constraints //-------------------------------------------------- - private var thumbLeadingConstraint: NSLayoutConstraint? - private var thumbTrailingConstraint: NSLayoutConstraint? - private var thumbHeightConstraint: NSLayoutConstraint? - private var thumbWidthConstraint: NSLayoutConstraint? + private var knobLeadingConstraint: NSLayoutConstraint? + private var knobTrailingConstraint: NSLayoutConstraint? + private var knobHeightConstraint: NSLayoutConstraint? + private var knobWidthConstraint: NSLayoutConstraint? private var heightConstraint: NSLayoutConstraint? private var widthConstraint: NSLayoutConstraint? - private func constrainThumb() { + private func constrainKnob() { - self.thumbLeadingConstraint?.isActive = isOn - self.thumbTrailingConstraint?.isActive = !isOn + self.knobLeadingConstraint?.isActive = !isOn + self.knobTrailingConstraint?.isActive = isOn } //-------------------------------------------------- @@ -136,26 +133,26 @@ public typealias ActionBlockConfirmation = () -> (Bool) self.init(frame: .zero) } - public convenience init(isOn: Bool, didSwitchAction: ActionBlock?) { + public convenience init(isOn: Bool, didToggleAction: ActionBlock?) { self.init(frame: .zero) self.isOn = isOn - self.didSwitchAction = didSwitchAction + self.didToggleAction = didToggleAction } - public convenience init(didSwitchAction: ActionBlock?) { + public convenience init(didToggleAction: ActionBlock?) { self.init(frame: .zero) - self.didSwitchAction = didSwitchAction + self.didToggleAction = didToggleAction } - public convenience init(shouldSwitchAction: ActionBlockConfirmation?, didSwitchAction: ActionBlock?) { + public convenience init(shouldToggleAction: ActionBlockConfirmation?, didToggleAction: ActionBlock?) { self.init(frame: .zero) - self.didSwitchAction = didSwitchAction - self.shouldSwitchAction = shouldSwitchAction + self.didToggleAction = didToggleAction + self.shouldToggleAction = shouldToggleAction } public required init?(coder: NSCoder) { super.init(coder: coder) - fatalError("Switch does not support xib.") + fatalError("Toggle does not support xib.") } //-------------------------------------------------- @@ -165,14 +162,14 @@ public typealias ActionBlockConfirmation = () -> (Bool) public override func updateView(_ size: CGFloat) { super.updateView(size) - heightConstraint?.constant = Switch.getTrackHeight() - widthConstraint?.constant = Switch.getTrackWidth() + heightConstraint?.constant = Toggle.getContainerHeight() + widthConstraint?.constant = Toggle.getContainerWidth() - thumbHeightConstraint?.constant = Switch.getThumbHeight() - thumbWidthConstraint?.constant = Switch.getThumbWidth() + knobHeightConstraint?.constant = Toggle.getKnobHeight() + knobWidthConstraint?.constant = Toggle.getKnobWidth() - layer.cornerRadius = Switch.getTrackHeight() / 2.0 - thumbView.layer.cornerRadius = Switch.getThumbHeight() / 2.0 + layer.cornerRadius = Toggle.getContainerHeight() / 2.0 + knobView.layer.cornerRadius = Toggle.getKnobHeight() / 2.0 } public override func setupView() { @@ -180,50 +177,50 @@ public typealias ActionBlockConfirmation = () -> (Bool) guard subviews.isEmpty else { return } - heightConstraint = heightAnchor.constraint(equalToConstant: Switch.trackSize.height) + heightConstraint = heightAnchor.constraint(equalToConstant: Toggle.containerSize.height) heightConstraint?.isActive = true - widthConstraint = widthAnchor.constraint(equalToConstant: Switch.trackSize.width) + widthConstraint = widthAnchor.constraint(equalToConstant: Toggle.containerSize.width) widthConstraint?.isActive = true - layer.cornerRadius = Switch.trackSize.height / 2.0 - backgroundColor = trackTintColor?.off + layer.cornerRadius = Toggle.containerSize.height / 2.0 + backgroundColor = containerTintColor?.off - addSubview(thumbView) + addSubview(knobView) - thumbHeightConstraint = thumbView.heightAnchor.constraint(equalToConstant: Switch.thumbSize.height) - thumbHeightConstraint?.isActive = true - thumbWidthConstraint = thumbView.widthAnchor.constraint(equalToConstant: Switch.thumbSize.width) - thumbWidthConstraint?.isActive = true - thumbView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true - thumbView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true - bottomAnchor.constraint(greaterThanOrEqualTo: thumbView.bottomAnchor).isActive = true + knobHeightConstraint = knobView.heightAnchor.constraint(equalToConstant: Toggle.knobSize.height) + knobHeightConstraint?.isActive = true + knobWidthConstraint = knobView.widthAnchor.constraint(equalToConstant: Toggle.knobSize.width) + knobWidthConstraint?.isActive = true + knobView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + knobView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true + bottomAnchor.constraint(greaterThanOrEqualTo: knobView.bottomAnchor).isActive = true - thumbTrailingConstraint = trailingAnchor.constraint(equalTo: thumbView.trailingAnchor, constant: 1) - thumbLeadingConstraint = thumbView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 1) - thumbLeadingConstraint?.isActive = true + knobTrailingConstraint = trailingAnchor.constraint(equalTo: knobView.trailingAnchor, constant: 1) + knobLeadingConstraint = knobView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 1) + knobLeadingConstraint?.isActive = true - accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Switch_buttonlabel") + accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel") } - class func getTrackWidth() -> CGFloat { - let trackWidth = Switch.trackSize.width - return (MFSizeObject(standardSize: trackWidth, standardiPadPortraitSize: CGFloat(Switch.trackSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? trackWidth + class func getContainerWidth() -> CGFloat { + let containerWidth = Toggle.containerSize.width + return (MFSizeObject(standardSize: containerWidth, standardiPadPortraitSize: CGFloat(Toggle.containerSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerWidth } - class func getTrackHeight() -> CGFloat { - let trackHeight = Switch.trackSize.height - return (MFSizeObject(standardSize: trackHeight, standardiPadPortraitSize: CGFloat(Switch.trackSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? trackHeight + class func getContainerHeight() -> CGFloat { + let containerHeight = Toggle.containerSize.height + return (MFSizeObject(standardSize: containerHeight, standardiPadPortraitSize: CGFloat(Toggle.containerSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerHeight } - class func getThumbWidth() -> CGFloat { - let thumbWidth = Switch.thumbSize.width - return (MFSizeObject(standardSize: thumbWidth, standardiPadPortraitSize: CGFloat(Switch.thumbSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? thumbWidth + class func getKnobWidth() -> CGFloat { + let knobWidth = Toggle.knobSize.width + return (MFSizeObject(standardSize: knobWidth, standardiPadPortraitSize: CGFloat(Toggle.knobSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobWidth } - class func getThumbHeight() -> CGFloat { - let thumbHeight = Switch.thumbSize.width - return (MFSizeObject(standardSize: thumbHeight, standardiPadPortraitSize: CGFloat(Switch.thumbSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? thumbHeight + class func getKnobHeight() -> CGFloat { + let knobHeight = Toggle.knobSize.width + return (MFSizeObject(standardSize: knobHeight, standardiPadPortraitSize: CGFloat(Toggle.knobSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobHeight } //-------------------------------------------------- @@ -240,12 +237,12 @@ public typealias ActionBlockConfirmation = () -> (Bool) toggleAndAction() } - /// This will toggle the state of the Switch and execute the actionBlock if provided. + /// This will toggle the state of the Toggle and execute the actionBlock if provided. public func toggleAndAction() { - if let result = shouldSwitchAction?(), result { + if let result = shouldToggleAction?(), result { isOn.toggle() - didSwitchAction?() + didToggleAction?() } } @@ -263,16 +260,17 @@ public typealias ActionBlockConfirmation = () -> (Bool) open override func touchesBegan(_ touches: Set, with event: UIEvent?) { UIView.animate(withDuration: 0.1, animations: { - self.thumbWidthConstraint?.constant += PaddingOne + self.knobWidthConstraint?.constant += PaddingOne self.layoutIfNeeded() }) } public override func touchesEnded(_ touches: Set, with event: UIEvent?) { - thumbReformAnimation() + knobReformAnimation() - guard let coordinates: CGPoint = touches.first?.location(in: self), + // Action only occurs of the user lifts up from withing acceptable region of the toggle. + guard let coordinates = touches.first?.location(in: self), coordinates.x > -20, coordinates.x < bounds.width + 20, coordinates.y > -20, @@ -284,7 +282,7 @@ public typealias ActionBlockConfirmation = () -> (Bool) public func touchesCancelled(_ touches: Set, with event: UIEvent) { - thumbReformAnimation() + knobReformAnimation() sendActions(for: .touchCancel) } @@ -292,22 +290,23 @@ public typealias ActionBlockConfirmation = () -> (Bool) // MARK: - Animations //-------------------------------------------------- - public func thumbReformAnimation() { + public func knobReformAnimation() { if isAnimated { UIView.animate(withDuration: 0.1, animations: { - self.thumbWidthConstraint?.constant = Switch.getThumbWidth() + self.knobWidthConstraint?.constant = Toggle.getKnobWidth() self.layoutIfNeeded() }, completion: nil) + } else { - thumbWidthConstraint?.constant = Switch.getThumbWidth() + knobWidthConstraint?.constant = Toggle.getKnobWidth() layoutIfNeeded() } } } // MARK: - Accessibility -extension Switch { +extension Toggle { public func formFieldGroupName() -> String? { return json?["groupName"] as? String @@ -315,7 +314,7 @@ extension Switch { } // MARK: - FormValidationProtocol -extension Switch { +extension Toggle { public func isValidField() -> Bool { return isOn && json?["required"] as? Bool ?? false @@ -331,7 +330,7 @@ extension Switch { } // MARK: - MVMCoreUIMoleculeViewProtocol -extension Switch { +extension Toggle { public override func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { super.setWithJSON(json, delegateObject: delegateObject, additionalData: additionalData) @@ -342,19 +341,19 @@ extension Switch { guard let dictionary = json else { return } if let color = dictionary["onTintColor"] as? String { - trackTintColor?.on = UIColor.mfGet(forHex: color) + containerTintColor?.on = UIColor.mfGet(forHex: color) } if let color = dictionary["offTintColor"] as? String { - trackTintColor?.off = UIColor.mfGet(forHex: color) + containerTintColor?.off = UIColor.mfGet(forHex: color) } - if let color = dictionary["onThumbTintColor"] as? String { - thumbTintColor?.on = UIColor.mfGet(forHex: color) + if let color = dictionary["onKnobTintColor"] as? String { + knobTintColor?.on = UIColor.mfGet(forHex: color) } - if let color = dictionary["offThumbTintColor"] as? String { - thumbTintColor?.off = UIColor.mfGet(forHex: color) + if let color = dictionary["offKnobTintColor"] as? String { + knobTintColor?.off = UIColor.mfGet(forHex: color) } if let state = dictionary["state"] as? Bool { @@ -364,17 +363,16 @@ extension Switch { } if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { - actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } + didToggleAction = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) } } - if let isAnimated = dictionary["state"] as? Bool { + if let isAnimated = dictionary["isAnimated"] as? Bool { self.isAnimated = isAnimated } } public class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - - return Switch.getTrackHeight() + return Toggle.getContainerHeight() } public func needsToBeConstrained() -> Bool { diff --git a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m index 4923f21d..98df5822 100644 --- a/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m +++ b/MVMCoreUI/OtherHandlers/MVMCoreUIMoleculeMappingObject.m @@ -48,7 +48,7 @@ @"radioButtonLabel": RadioButtonLabel.class, @"listItem": MoleculeTableViewCell.class, @"accordionListItem": AccordionMoleculeTableViewCell.class, - @"switch": Switch.class, + @"toggle": Toggle.class, @"leftRightLabelView": LeftRightLabelView.class, @"actionDetailWithImage": ActionDetailWithImage.class, @"image": MFLoadImageView.class, From 98997bfa8046cc86d9958e5d8923570c862a28b5 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 11 Dec 2019 15:58:18 -0500 Subject: [PATCH 11/18] Comments, logical updates, refactoring. --- MVMCoreUI/Atoms/Views/Toggle.swift | 34 +++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Toggle.swift b/MVMCoreUI/Atoms/Views/Toggle.swift index 0ee42937..b49104c5 100644 --- a/MVMCoreUI/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -14,6 +14,8 @@ public typealias ActionBlockConfirmation = () -> (Bool) /** A custom implementation of Apple's UISwitch. + By default this class begins in the off state. + Container: The background of the toggle control. Knob: The circular indicator that slides on the container. */ @@ -29,6 +31,8 @@ public typealias ActionBlockConfirmation = () -> (Bool) public var isAnimated = true public var didToggleAction: ActionBlock? + + /// Executes logic before state change. If false, then toggle state will not change and the didToggleAction will not execute. public var shouldToggleAction: ActionBlockConfirmation? = { return { return true } }() @@ -51,8 +55,9 @@ public typealias ActionBlockConfirmation = () -> (Bool) open override var isEnabled: Bool { didSet { isUserInteractionEnabled = isEnabled - backgroundColor = isEnabled ? containerTintColor?.on : disabledTintColor?.container - knobView.backgroundColor = isEnabled ? knobTintColor?.on : disabledTintColor?.knob + changeStateNoAnimation(isEnabled) + backgroundColor = isEnabled ? containerTintColor?.off : disabledTintColor?.container + knobView.backgroundColor = isEnabled ? knobTintColor?.off : disabledTintColor?.knob } } @@ -116,8 +121,8 @@ public typealias ActionBlockConfirmation = () -> (Bool) private func constrainKnob() { - self.knobLeadingConstraint?.isActive = !isOn - self.knobTrailingConstraint?.isActive = isOn + knobLeadingConstraint?.isActive = !isOn + knobTrailingConstraint?.isActive = isOn } //-------------------------------------------------- @@ -133,17 +138,21 @@ public typealias ActionBlockConfirmation = () -> (Bool) self.init(frame: .zero) } - public convenience init(isOn: Bool, didToggleAction: ActionBlock?) { + public convenience init(isOn: Bool) { self.init(frame: .zero) self.isOn = isOn - self.didToggleAction = didToggleAction } - public convenience init(didToggleAction: ActionBlock?) { + /// - parameter isOn: Bool to set the state of the toggle. + /// - parameter didToggleAction: A closure which is executed after the toggle changes states. + public convenience init(isOn: Bool = false, didToggleAction: ActionBlock?) { self.init(frame: .zero) + changeStateNoAnimation(isOn) self.didToggleAction = didToggleAction } + /// - parameter shouldToggleAction: Takes a closure that returns a boolean. + /// - parameter didToggleAction: A closure which is executed after the toggle changes states. public convenience init(shouldToggleAction: ActionBlockConfirmation?, didToggleAction: ActionBlock?) { self.init(frame: .zero) self.didToggleAction = didToggleAction @@ -246,6 +255,13 @@ public typealias ActionBlockConfirmation = () -> (Bool) } } + private func changeStateNoAnimation(_ state: Bool) { + + isAnimated = false + isOn = state + isAnimated = true + } + //-------------------------------------------------- // MARK: - UIResponder //-------------------------------------------------- @@ -357,9 +373,7 @@ extension Toggle { } if let state = dictionary["state"] as? Bool { - isAnimated = false - isOn = state - isAnimated = true + changeStateNoAnimation(state) } if let actionMap = dictionary.optionalDictionaryForKey("actionMap") { From 639330ad5bd56755a03c35cb232c006c02910869 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 11 Dec 2019 16:03:01 -0500 Subject: [PATCH 12/18] More comments. --- MVMCoreUI/Atoms/Views/Toggle.swift | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MVMCoreUI/Atoms/Views/Toggle.swift b/MVMCoreUI/Atoms/Views/Toggle.swift index b49104c5..2d056d24 100644 --- a/MVMCoreUI/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -24,10 +24,16 @@ public typealias ActionBlockConfirmation = () -> (Bool) // MARK: - Properties //-------------------------------------------------- + /// Holds the on and off colors for the container. public var containerTintColor: (on: UIColor?, off: UIColor?)? = (on: .mfShamrock(), off: .black) + + /// Holds the on and off colors for the knob. public var knobTintColor: (on: UIColor?, off: UIColor?)? = (on: .white, off: .white) + + /// Holds the on and off colors for the disabled state.. public var disabledTintColor: (container: UIColor?, knob: UIColor?)? = (container: .mfSilver(), knob: .white) + /// Set this flag to false if you do not want to animate state changes. public var isAnimated = true public var didToggleAction: ActionBlock? @@ -61,12 +67,14 @@ public typealias ActionBlockConfirmation = () -> (Bool) } } + /// Simple means to prevent user interaction with the toggle. public var isLocked: Bool = false { didSet { isUserInteractionEnabled = !isLocked } } + /// The state on the toggle. Default value: false. open var isOn: Bool = false { didSet { isSelected = isOn From 39b752d01b556b9fd0581c7fe2dd728b0920c4f0 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Thu, 12 Dec 2019 13:37:18 -0500 Subject: [PATCH 13/18] Included reset function. accounted for isAnimated state when changing isOn without animations. --- MVMCoreUI/Atoms/Views/Toggle.swift | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/Views/Toggle.swift b/MVMCoreUI/Atoms/Views/Toggle.swift index 2d056d24..aff1abb3 100644 --- a/MVMCoreUI/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -220,6 +220,18 @@ public typealias ActionBlockConfirmation = () -> (Bool) accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Toggle_buttonlabel") } + public override func reset() { + super.reset() + + backgroundColor = containerTintColor?.off + knobView.backgroundColor = knobTintColor?.off + isAnimated = false + isOn = false + constrainKnob() + didToggleAction = nil + shouldToggleAction = { return true } + } + class func getContainerWidth() -> CGFloat { let containerWidth = Toggle.containerSize.width return (MFSizeObject(standardSize: containerWidth, standardiPadPortraitSize: CGFloat(Toggle.containerSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerWidth @@ -265,9 +277,12 @@ public typealias ActionBlockConfirmation = () -> (Bool) private func changeStateNoAnimation(_ state: Bool) { + // Hold state in case User wanted isAnimated to remain off. + let isAnimatedState = isAnimated + isAnimated = false isOn = state - isAnimated = true + isAnimated = isAnimatedState } //-------------------------------------------------- From 4a49c40b19068747cbfd6e1c3918dd50753458aa Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 8 Jan 2020 10:28:37 -0500 Subject: [PATCH 14/18] Using pointInside from Control. --- MVMCoreUI/Atoms/Views/Toggle.swift | 7 ------- 1 file changed, 7 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Toggle.swift b/MVMCoreUI/Atoms/Views/Toggle.swift index aff1abb3..d8b73837 100644 --- a/MVMCoreUI/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -289,13 +289,6 @@ public typealias ActionBlockConfirmation = () -> (Bool) // MARK: - UIResponder //-------------------------------------------------- - override open func point(inside point: CGPoint, with event: UIEvent?) -> Bool { - - let faultTolerance: CGFloat = 20.0 - let area = bounds.insetBy(dx: -faultTolerance, dy: -faultTolerance) - return area.contains(point) - } - open override func touchesBegan(_ touches: Set, with event: UIEvent?) { UIView.animate(withDuration: 0.1, animations: { From 14f49882b1f1c485f492ef9292b478b190a1b0cc Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 8 Jan 2020 10:44:36 -0500 Subject: [PATCH 15/18] Big Self. --- MVMCoreUI/Atoms/Views/Toggle.swift | 47 ++++++++++++++--------------- MVMCoreUI/BaseClasses/Control.swift | 1 + 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Toggle.swift b/MVMCoreUI/Atoms/Views/Toggle.swift index d8b73837..1a2341c2 100644 --- a/MVMCoreUI/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -93,7 +93,7 @@ public typealias ActionBlockConfirmation = () -> (Bool) UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.2, options: [], animations: { self.constrainKnob() - self.knobWidthConstraint?.constant = Toggle.getKnobWidth() + self.knobWidthConstraint?.constant = Self.getKnobWidth() self.layoutIfNeeded() }, completion: nil) @@ -128,7 +128,6 @@ public typealias ActionBlockConfirmation = () -> (Bool) private var widthConstraint: NSLayoutConstraint? private func constrainKnob() { - knobLeadingConstraint?.isActive = !isOn knobTrailingConstraint?.isActive = isOn } @@ -179,14 +178,14 @@ public typealias ActionBlockConfirmation = () -> (Bool) public override func updateView(_ size: CGFloat) { super.updateView(size) - heightConstraint?.constant = Toggle.getContainerHeight() - widthConstraint?.constant = Toggle.getContainerWidth() + heightConstraint?.constant = Self.getContainerHeight() + widthConstraint?.constant = Self.getContainerWidth() - knobHeightConstraint?.constant = Toggle.getKnobHeight() - knobWidthConstraint?.constant = Toggle.getKnobWidth() + knobHeightConstraint?.constant = Self.getKnobHeight() + knobWidthConstraint?.constant = Self.getKnobWidth() - layer.cornerRadius = Toggle.getContainerHeight() / 2.0 - knobView.layer.cornerRadius = Toggle.getKnobHeight() / 2.0 + layer.cornerRadius = Self.getContainerHeight() / 2.0 + knobView.layer.cornerRadius = Self.getKnobHeight() / 2.0 } public override func setupView() { @@ -194,20 +193,20 @@ public typealias ActionBlockConfirmation = () -> (Bool) guard subviews.isEmpty else { return } - heightConstraint = heightAnchor.constraint(equalToConstant: Toggle.containerSize.height) + heightConstraint = heightAnchor.constraint(equalToConstant: Self.containerSize.height) heightConstraint?.isActive = true - widthConstraint = widthAnchor.constraint(equalToConstant: Toggle.containerSize.width) + widthConstraint = widthAnchor.constraint(equalToConstant: Self.containerSize.width) widthConstraint?.isActive = true - layer.cornerRadius = Toggle.containerSize.height / 2.0 + layer.cornerRadius = Self.containerSize.height / 2.0 backgroundColor = containerTintColor?.off addSubview(knobView) - knobHeightConstraint = knobView.heightAnchor.constraint(equalToConstant: Toggle.knobSize.height) + knobHeightConstraint = knobView.heightAnchor.constraint(equalToConstant: Self.knobSize.height) knobHeightConstraint?.isActive = true - knobWidthConstraint = knobView.widthAnchor.constraint(equalToConstant: Toggle.knobSize.width) + knobWidthConstraint = knobView.widthAnchor.constraint(equalToConstant: Self.knobSize.width) knobWidthConstraint?.isActive = true knobView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true knobView.topAnchor.constraint(greaterThanOrEqualTo: topAnchor).isActive = true @@ -233,23 +232,23 @@ public typealias ActionBlockConfirmation = () -> (Bool) } class func getContainerWidth() -> CGFloat { - let containerWidth = Toggle.containerSize.width - return (MFSizeObject(standardSize: containerWidth, standardiPadPortraitSize: CGFloat(Toggle.containerSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerWidth + let containerWidth = Self.containerSize.width + return (MFSizeObject(standardSize: containerWidth, standardiPadPortraitSize: CGFloat(Self.containerSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerWidth } class func getContainerHeight() -> CGFloat { - let containerHeight = Toggle.containerSize.height - return (MFSizeObject(standardSize: containerHeight, standardiPadPortraitSize: CGFloat(Toggle.containerSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerHeight + let containerHeight = Self.containerSize.height + return (MFSizeObject(standardSize: containerHeight, standardiPadPortraitSize: CGFloat(Self.containerSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? containerHeight } class func getKnobWidth() -> CGFloat { - let knobWidth = Toggle.knobSize.width - return (MFSizeObject(standardSize: knobWidth, standardiPadPortraitSize: CGFloat(Toggle.knobSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobWidth + let knobWidth = Self.knobSize.width + return (MFSizeObject(standardSize: knobWidth, standardiPadPortraitSize: CGFloat(Self.knobSize.width * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobWidth } class func getKnobHeight() -> CGFloat { - let knobHeight = Toggle.knobSize.width - return (MFSizeObject(standardSize: knobHeight, standardiPadPortraitSize: CGFloat(Toggle.knobSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobHeight + let knobHeight = Self.knobSize.width + return (MFSizeObject(standardSize: knobHeight, standardiPadPortraitSize: CGFloat(Self.knobSize.height * 1.5)))?.getValueBasedOnApplicationWidth() ?? knobHeight } //-------------------------------------------------- @@ -326,12 +325,12 @@ public typealias ActionBlockConfirmation = () -> (Bool) if isAnimated { UIView.animate(withDuration: 0.1, animations: { - self.knobWidthConstraint?.constant = Toggle.getKnobWidth() + self.knobWidthConstraint?.constant = Self.getKnobWidth() self.layoutIfNeeded() }, completion: nil) } else { - knobWidthConstraint?.constant = Toggle.getKnobWidth() + knobWidthConstraint?.constant = Self.getKnobWidth() layoutIfNeeded() } } @@ -402,7 +401,7 @@ extension Toggle { } public class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { - return Toggle.getContainerHeight() + return Self.getContainerHeight() } public func needsToBeConstrained() -> Bool { diff --git a/MVMCoreUI/BaseClasses/Control.swift b/MVMCoreUI/BaseClasses/Control.swift index 0b03ba27..5bc80003 100644 --- a/MVMCoreUI/BaseClasses/Control.swift +++ b/MVMCoreUI/BaseClasses/Control.swift @@ -72,6 +72,7 @@ extension Control: MVMCoreViewProtocol { // MARK: - MVMCoreUIMoleculeViewProtocol extension Control: MVMCoreUIMoleculeViewProtocol { + public func setWithJSON(_ json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?, additionalData: [AnyHashable: Any]?) { self.json = json From dfa2fa334356f3f655e9fb41e7ddb4e81dc9bd70 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 8 Jan 2020 11:03:44 -0500 Subject: [PATCH 16/18] Ensure that isEnabled only triggers when false. Preventing isEnabled from changing isON. --- MVMCoreUI/Atoms/Views/Toggle.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/Views/Toggle.swift b/MVMCoreUI/Atoms/Views/Toggle.swift index 1a2341c2..22956a98 100644 --- a/MVMCoreUI/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -61,7 +61,7 @@ public typealias ActionBlockConfirmation = () -> (Bool) open override var isEnabled: Bool { didSet { isUserInteractionEnabled = isEnabled - changeStateNoAnimation(isEnabled) + changeStateNoAnimation(isEnabled ? isOn : false) backgroundColor = isEnabled ? containerTintColor?.off : disabledTintColor?.container knobView.backgroundColor = isEnabled ? knobTintColor?.off : disabledTintColor?.knob } @@ -398,6 +398,10 @@ extension Toggle { if let isAnimated = dictionary["isAnimated"] as? Bool { self.isAnimated = isAnimated } + + if let isEnabled = dictionary["isEnabled"] as? Bool{//}, !isEnabled { + self.isEnabled = isEnabled + } } public class func estimatedHeight(forRow json: [AnyHashable: Any]?, delegateObject: MVMCoreUIDelegateObject?) -> CGFloat { From a99dd7f0a6169184991aca0518fb3b58bd12de84 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 8 Jan 2020 14:46:40 -0500 Subject: [PATCH 17/18] removed isSelected. --- MVMCoreUI/Atoms/Views/Toggle.swift | 2 -- 1 file changed, 2 deletions(-) diff --git a/MVMCoreUI/Atoms/Views/Toggle.swift b/MVMCoreUI/Atoms/Views/Toggle.swift index 22956a98..9d97f5a6 100644 --- a/MVMCoreUI/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -77,8 +77,6 @@ public typealias ActionBlockConfirmation = () -> (Bool) /// The state on the toggle. Default value: false. open var isOn: Bool = false { didSet { - isSelected = isOn - if isAnimated { UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: { if self.isOn { From efc163ab38d2f955fd411d3fb7929a634fc445b7 Mon Sep 17 00:00:00 2001 From: Kevin G Christiano Date: Wed, 8 Jan 2020 15:08:31 -0500 Subject: [PATCH 18/18] no com --- MVMCoreUI/Atoms/Views/Toggle.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MVMCoreUI/Atoms/Views/Toggle.swift b/MVMCoreUI/Atoms/Views/Toggle.swift index 9d97f5a6..17d286ac 100644 --- a/MVMCoreUI/Atoms/Views/Toggle.swift +++ b/MVMCoreUI/Atoms/Views/Toggle.swift @@ -397,7 +397,7 @@ extension Toggle { self.isAnimated = isAnimated } - if let isEnabled = dictionary["isEnabled"] as? Bool{//}, !isEnabled { + if let isEnabled = dictionary["isEnabled"] as? Bool{ self.isEnabled = isEnabled } }