From 6c0a0460e86c11e005e760f28daaec645b530823 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 28 Jul 2022 18:07:16 -0500 Subject: [PATCH] updated toggle Signed-off-by: Matt Bruce --- VDS/Components/Toggle/VDSToggle.swift | 276 ++++++++++++++------- VDS/Components/Toggle/VDSToggleModel.swift | 13 +- 2 files changed, 192 insertions(+), 97 deletions(-) diff --git a/VDS/Components/Toggle/VDSToggle.swift b/VDS/Components/Toggle/VDSToggle.swift index 748b8db8..0c132a7f 100644 --- a/VDS/Components/Toggle/VDSToggle.swift +++ b/VDS/Components/Toggle/VDSToggle.swift @@ -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, 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 } } - diff --git a/VDS/Components/Toggle/VDSToggleModel.swift b/VDS/Components/Toggle/VDSToggleModel.swift index 1c1ff5a9..d7a3f6a9 100644 --- a/VDS/Components/Toggle/VDSToggleModel.swift +++ b/VDS/Components/Toggle/VDSToggleModel.swift @@ -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 } + } +}