updated toggle

Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
Matt Bruce 2022-07-28 18:07:16 -05:00
parent 74cbd6a2fe
commit 6c0a0460e8
2 changed files with 192 additions and 97 deletions

View File

@ -8,6 +8,7 @@
import Foundation
import UIKit
import VDSColorTokens
import Combine
/**
A custom implementation of Apple's UISwitch.
@ -16,60 +17,53 @@ import VDSColorTokens
Container: The background of the toggle control.
Knob: The circular indicator that slides on the container.
*/
@objcMembers open class VDSToggle: Control<VDSToggleModel>, Changable {
public class DefaultToggleModel: DefaultLabelModel, VDSToggleModel, ObservableObject {
public var id: String?
public var inputId: String?
public var disabled: Bool = false
public var showText: Bool = false
public var on: Bool = false
public var offText: String = "Off"
public var onText: String = "On"
public var value: AnyHashable? = true
public var dataAnalyticsTrack: String?
public var dataClickStream: String?
public var dataTrack: String?
public var accessibilityHintEnabled: String?
public var accessibilityHintDisabled: String?
public var accessibilityValueEnabled: String?
public var accessibilityValueDisabled: String?
public var accessibilityLabelEnabled: String?
public var accessibilityLabelDisabled: String?
public required init() {
super.init()
}
}
@objcMembers open class VDSToggle: VDSControl, Modelable, Changable {
public typealias ModelType = VDSToggleModel
@Published public var model: ModelType = DefaultToggleModel()
private var cancellable: AnyCancellable?
//--------------------------------------------------
// MARK: - Properties
// MARK: - Private Properties
//--------------------------------------------------
/// Holds the on and off colors for the container.
public var containerTintColor: (on: UIColor, off: UIColor) = (on: VDSColor.paletteGreen26, off: VDSColor.paletteGray44)
private var containerTintColor: (on: UIColor, off: UIColor) = (on: VDSColor.paletteGreen26, off: VDSColor.paletteGray44)
/// Holds the on and off colors for the knob.
public var knobTintColor: (on: UIColor, off: UIColor) = (on: VDSColor.paletteWhite, off: VDSColor.paletteWhite)
private var knobTintColor: (on: UIColor, off: UIColor) = (on: VDSColor.paletteWhite, off: VDSColor.paletteWhite)
/// Holds the on and off colors for the disabled state..
public var disabledTintColor: (container: UIColor, knob: UIColor) = (container: VDSColor.paletteGray11, knob: VDSColor.paletteWhite)
/// Set this flag to false if you do not want to animate state changes.
public var isAnimated = true
public var onChange: Blocks.ActionBlock?
private var showText: Bool {
return model?.showText ?? false
}
private var onText: String {
return model?.onText ?? "On"
}
private var offText: String {
return model?.offText ?? "off"
}
private var disabledTintColor: (container: UIColor, knob: UIColor) = (container: VDSColor.paletteGray11, knob: VDSColor.paletteWhite)
private var showTextSpacing: CGFloat {
showText ? 12 : 0
}
private var textPosition: VDSTextPosition {
return model?.textPosition ?? .left
}
private var fontSize: VDSFontSize {
return model?.fontSize ?? .small
}
private var fontWeight: VDSFontWeight {
return model?.fontWeight ?? .regular
}
// Sizes are from InVision design specs.
public static var toggleSize = CGSize(width: 52, height: 24)
open class func getToggleScaledSize() -> CGSize { return Self.toggleSize }
public static var knobSize = CGSize(width: 20, height: 20)
open class func getKnobScaledSize() -> CGSize { return Self.knobSize }
private var stackView: UIStackView = {
let stackView = UIStackView()
stackView.translatesAutoresizingMaskIntoConstraints = false
@ -78,8 +72,8 @@ import VDSColorTokens
return stackView
}()
private var label: UILabel = {
let label = UILabel()
private var label: VDSLabel = {
let label = VDSLabel()
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
@ -97,16 +91,105 @@ import VDSColorTokens
return view
}()
//--------------------------------------------------
// MARK: - Public Properties
//--------------------------------------------------
/// Set this flag to false if you do not want to animate state changes.
public var isAnimated = true
public var onChange: Blocks.ActionBlock?
//--------------------------------------------------
// MARK: - Static Properties
//--------------------------------------------------
// Sizes are from InVision design specs.
public static var toggleSize = CGSize(width: 52, height: 24)
open class func getToggleScaledSize() -> CGSize { return Self.toggleSize }
public static var knobSize = CGSize(width: 20, height: 20)
open class func getKnobScaledSize() -> CGSize { return Self.knobSize }
//--------------------------------------------------
// MARK: - Computed Properties
//--------------------------------------------------
public var showText: Bool {
get { model.showText }
set {
if model.showText != newValue {
model.showText = newValue
}
}
}
public var onText: String {
get { model.onText }
set {
if model.onText != newValue {
model.onText = newValue
}
}
}
public var offText: String {
get { model.offText }
set {
if model.offText != newValue {
model.offText = newValue
}
}
}
public var textPosition: VDSTextPosition {
get { model.textPosition }
set {
if model.textPosition != newValue {
model.textPosition = newValue
}
}
}
public var fontSize: VDSFontSize {
get { model.fontSize }
set {
if model.fontSize != newValue {
model.fontSize = newValue
}
}
}
public var fontWeight: VDSFontWeight {
get { model.fontWeight }
set {
if model.fontWeight != newValue {
model.fontWeight = newValue
}
}
}
public var surface: Surface {
get { model.surface }
set {
if model.surface != newValue {
model.surface = newValue
}
}
}
open override var isEnabled: Bool {
didSet {
isUserInteractionEnabled = isEnabled
changeStateNoAnimation(isEnabled ? isOn : false)
get { !model.disabled }
set {
//create local vars for clear coding
let enabled = newValue
let disabled = !newValue
if model.disabled != disabled {
model.disabled = disabled
}
isUserInteractionEnabled = enabled
changeStateNoAnimation(enabled ? isOn : false)
setToggleAppearanceFromState()
setAccessibilityHint(isEnabled)
setAccessibilityHint(enabled)
}
}
@ -116,37 +199,37 @@ import VDSColorTokens
}
/// The state on the toggle. Default value: false.
open var isOn: Bool = false {
didSet {
label.text = isOn ? onText : offText
if isAnimated {
UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: {
if self.isOn {
self.knobView.backgroundColor = self.knobTintColor.on
self.toggleView.backgroundColor = self.containerTintColor.on
} else {
self.knobView.backgroundColor = self.knobTintColor.off
self.toggleView.backgroundColor = self.containerTintColor.off
}
}, completion: nil)
UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.2, options: [], animations: {
open var isOn: Bool {
get { model.on }
set {
if model.on != newValue {
model.on = newValue
setAccessibilityValue(model.on)
if isAnimated {
UIView.animate(withDuration: 0.2, delay: 0.0, options: .curveEaseIn, animations: {
if newValue {
self.knobView.backgroundColor = self.knobTintColor.on
self.toggleView.backgroundColor = self.containerTintColor.on
} else {
self.knobView.backgroundColor = self.knobTintColor.off
self.toggleView.backgroundColor = self.containerTintColor.off
}
}, completion: nil)
UIView.animate(withDuration: 0.33, delay: 0, usingSpringWithDamping: 0.6, initialSpringVelocity: 0.2, options: [], animations: {
self.constrainKnob()
self.knobWidthConstraint?.constant = Self.getKnobScaledSize().width
self.layoutIfNeeded()
}, completion: nil)
} else {
setToggleAppearanceFromState()
self.constrainKnob()
self.knobWidthConstraint?.constant = Self.getKnobScaledSize().width
self.layoutIfNeeded()
}, completion: nil)
} else {
setToggleAppearanceFromState()
self.constrainKnob()
}
setNeedsLayout()
layoutIfNeeded()
}
model?.on = isOn
setAccessibilityValue(isOn)
setNeedsLayout()
layoutIfNeeded()
}
}
@ -193,12 +276,21 @@ import VDSColorTokens
public override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
public convenience override init() {
self.init(frame: .zero)
setup()
}
func setup() {
cancellable = $model.sink { [weak self] viewModel in
self?.onStateChange(viewModel: viewModel)
}
}
//functions
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
@ -392,19 +484,19 @@ import VDSColorTokens
}
}
// MARK:- MoleculeViewProtocol
open override func set(with model: ModelType) {
self.model = model
isOn = model.on
changeStateNoAnimation(isOn)
private func onStateChange(viewModel: ModelType) {
isAnimated = true
isEnabled = !model.disabled
guard let font = try? VDSFontStyle.font(for: .body, fontWeight: model.fontWeight, fontSize: model.fontSize) else {
return
}
label.font = font
isOn = viewModel.on
isEnabled = !viewModel.disabled
changeStateNoAnimation(viewModel.on)
backgroundColor = viewModel.surface == .dark ? VDSColor.backgroundPrimaryDark : .clear
label.set(with: viewModel)
label.text = viewModel.on ? viewModel.onText : viewModel.offText
}
// MARK:- Modable
open func set(with model: ModelType) {
self.model = model
}
}

View File

@ -7,7 +7,6 @@
import Foundation
import UIKit
import VDSTypographyTokens
extension VDSToggle {
public enum TextPosition: String, Codable {
@ -15,13 +14,17 @@ extension VDSToggle {
}
}
public protocol VDSToggleModel: Surfaceable, FormFieldable, DataTrackable, Disabling, Accessable {
public protocol VDSToggleModel: VDSLabelModel, FormFieldable, DataTrackable, Disabling, Accessable {
var id: String? { get set }
var showText: Bool { get set }
var on: Bool { get set }
var fontSize: VDSFontSize { get set }
var textPosition: VDSTextPosition { get set }
var fontWeight: VDSFontWeight { get set }
var offText: String { get set }
var onText: String { get set }
}
extension VDSToggleModel {
public var fontCategory: VDSFontCategory {
get { return .body }
set { return }
}
}