General Working order.

This commit is contained in:
Kevin G Christiano 2019-12-10 14:38:32 -05:00
parent 83370956c1
commit ab0ff64677

View File

@ -10,25 +10,25 @@ import MVMCore
import UIKit import UIKit
/** /**
A custom implementation of Apple's UISwitch. A custom implementation of Apple's UISwitch.
Track: The background of the switch control. Track: The background of the switch control.
Thumb: The circular indicator that slides on the track. Thumb: The circular indicator that slides on the track.
*/ */
@objcMembers open class Switch: Control, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol { @objcMembers open class Switch: Control, MVMCoreUIViewConstrainingProtocol, FormValidationFormFieldProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public var trackTintColor: (on: UIColor?, off: UIColor?)? = (on: .mfShamrock(), off: .black) public var trackTintColor: (on: UIColor?, off: UIColor?)? = (on: .mfShamrock(), off: .black)
public var thumbTintColor: (on: UIColor?, off: UIColor?)? = (on: .white, off: .white) public var thumbTintColor: (on: UIColor?, off: UIColor?)? = (on: .white, off: .white)
public var disabledTintColor: (track: UIColor?, thumb: UIColor?)? = (track: .mfSilver(), thumb: .white) public var disabledTintColor: (track: UIColor?, thumb: UIColor?)? = (track: .mfSilver(), thumb: .white)
private var valueShouldChange = false public var isAnimated = true
// private var canChangeValue = false
private var shouldTouchToSwitch = false private var valueShouldChange = true
private var shouldTouchToSwitch = true
private var valueChangedBlock: ActionBlock?
private var actionBlock: ActionBlock? private var actionBlock: ActionBlock?
// Sizes are from InVision design specs. // Sizes are from InVision design specs.
@ -37,7 +37,12 @@ import UIKit
static let shakeIntensity: CGFloat = 2 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 // MARK: - Computed Properties
@ -53,8 +58,49 @@ import UIKit
open var isOn: Bool = false { open var isOn: Bool = false {
didSet { didSet {
backgroundColor = isOn ? trackTintColor?.on : trackTintColor?.off isSelected = isOn
thumbView.backgroundColor = isOn ? thumbTintColor?.on : thumbTintColor?.off
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 thumbLeadingConstraint: NSLayoutConstraint?
private var thumbTrailingConstraint: NSLayoutConstraint? private var thumbTrailingConstraint: NSLayoutConstraint?
private var relativeThumbLeadingConstraint: NSLayoutConstraint?
private var relativeThumbTrailingConstraint: NSLayoutConstraint?
private var thumbHeightConstraint: NSLayoutConstraint? private var thumbHeightConstraint: NSLayoutConstraint?
private var thumbWidthConstraint: NSLayoutConstraint? private var thumbWidthConstraint: NSLayoutConstraint?
private var heightConstraint: NSLayoutConstraint? private var heightConstraint: NSLayoutConstraint?
@ -88,15 +137,15 @@ import UIKit
self.init(frame: .zero) self.init(frame: .zero)
} }
public convenience init(isOn: Bool, changeBlock: ActionBlock?) { public convenience init(isOn: Bool, actionBlock: ActionBlock?) {
self.init(frame: .zero) self.init(frame: .zero)
self.isOn = isOn self.isOn = isOn
valueChangedBlock = changeBlock self.actionBlock = actionBlock
} }
public convenience init(changeBlock: ActionBlock?) { public convenience init(actionBlock: ActionBlock?) {
self.init(frame: .zero) self.init(frame: .zero)
valueChangedBlock = changeBlock self.actionBlock = actionBlock
} }
public required init?(coder: NSCoder) { public required init?(coder: NSCoder) {
@ -126,10 +175,6 @@ import UIKit
guard subviews.isEmpty else { return } guard subviews.isEmpty else { return }
// canChangeValue = true
shouldTouchToSwitch = true
valueShouldChange = true
heightConstraint = heightAnchor.constraint(equalToConstant: Switch.trackSize.height) heightConstraint = heightAnchor.constraint(equalToConstant: Switch.trackSize.height)
heightConstraint?.isActive = true heightConstraint?.isActive = true
@ -137,10 +182,7 @@ import UIKit
widthConstraint?.isActive = true widthConstraint?.isActive = true
layer.cornerRadius = Switch.trackSize.height / 2.0 layer.cornerRadius = Switch.trackSize.height / 2.0
backgroundColor = trackTintColor?.off
let thumbView = MVMCoreUICommonViewsUtility.commonView()
thumbView.backgroundColor = .white
thumbView.layer.cornerRadius = Switch.getThumbHeight() / 2.0
addSubview(thumbView) addSubview(thumbView)
@ -149,13 +191,13 @@ import UIKit
thumbView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true thumbView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
thumbTrailingConstraint = trailingAnchor.constraint(equalTo: thumbView.trailingAnchor, constant: 1) 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 = thumbView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 1)
thumbLeadingConstraint?.isActive = true thumbLeadingConstraint?.isActive = true
shouldTouchToSwitch = false
setState(isOn, animated: false)
shouldTouchToSwitch = true
accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Switch_buttonlabel") accessibilityLabel = MVMCoreUIUtility.hardcodedString(withKey: "Switch_buttonlabel")
} }
@ -176,59 +218,23 @@ import UIKit
} }
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Methods // MARK: - Actions
//-------------------------------------------------- //--------------------------------------------------
@objc func addCustomAction() { open override func sendAction(_ action: Selector, to target: Any?, for event: UIEvent?) {
super.sendAction(action, to: target, for: event)
actionBlock?() toggleAndAction()
} }
public func setState(_ state: Bool, animated: Bool) { open override func sendActions(for controlEvents: UIControl.Event) {
super.sendActions(for: controlEvents)
setState(state, withoutBlockAnimated: animated) toggleAndAction()
valueChangedBlock?()
FormValidator.enableByValidationWith(delegate: delegateObject?.formValidationProtocol)
} }
public func setState(_ state: Bool, withoutBlockAnimated animated: Bool) { /// This will toggle the state of the Switch and execute the actionBlock if provided.
public func toggleAndAction() {
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() isOn.toggle()
shouldTouchToSwitch = false actionBlock?()
setState(isOn, animated: true)
shouldTouchToSwitch = true
sendActions(for: .valueChanged)
return isOn
} }
//-------------------------------------------------- //--------------------------------------------------
@ -244,61 +250,24 @@ import UIKit
public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) { public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
thumbEnlargeAnimation() UIView.animate(withDuration: 0.1, animations: {
self.thumbWidthConstraint?.constant += PaddingOne
self.layoutIfNeeded()
})
sendActions(for: .touchDown) sendActions(for: .touchDown)
} }
public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) { public override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
if shouldTouchToSwitch && valueShouldChange { thumbReformAnimation()
changeValue()
}
sendActions(for: .touchUpInside)
thumbMoveAnitmationTo(on: isOn)
thumbShakeAnitmationTo(on: isOn)
thumbReformAnimation(true)
valueShouldChange = true valueShouldChange = true
} sendActions(for: .touchUpInside)
public func touchesMoved(_ touches: Set<UITouch>, 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<UITouch>?) -> 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<UITouch>, with event: UIEvent) { public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
if shouldTouchToSwitch { thumbReformAnimation()
thumbReformAnimation(true)
}
sendActions(for: .touchCancel) sendActions(for: .touchCancel)
} }
@ -306,17 +275,9 @@ import UIKit
// MARK: - Animations // MARK: - Animations
//-------------------------------------------------- //--------------------------------------------------
public func thumbEnlargeAnimation() { public func thumbReformAnimation() {
UIView.animate(withDuration: 0.1, animations: { if isAnimated {
self.thumbWidthConstraint?.constant += PaddingOne
self.layoutIfNeeded()
})
}
public func thumbReformAnimation(_ animated: Bool) {
if animated {
UIView.animate(withDuration: 0.1, animations: { UIView.animate(withDuration: 0.1, animations: {
self.thumbWidthConstraint?.constant = Switch.getThumbWidth() self.thumbWidthConstraint?.constant = Switch.getThumbWidth()
self.layoutIfNeeded() self.layoutIfNeeded()
@ -326,82 +287,6 @@ import UIKit
layoutIfNeeded() 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 // MARK: - Accessibility
@ -455,10 +340,18 @@ extension Switch {
thumbTintColor?.off = UIColor.mfGet(forHex: color) 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] { if let actionMap = dictionary.optionalDictionaryForKey("actionMap") {
addTarget(self, action: #selector(addCustomAction), for: .touchUpInside) // actionBlock = { MVMCoreActionHandler.shared()?.handleAction(with: actionMap, additionalData: additionalData, delegateObject: delegateObject) }
}
if let isAnimated = dictionary["state"] as? Bool {
self.isAnimated = isAnimated
} }
} }