General Working order.
This commit is contained in:
parent
83370956c1
commit
ab0ff64677
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user