diff --git a/VDS/Components/Toggle/VDSToggle.swift b/VDS/Components/Toggle/VDSToggle.swift index bfb0b919..8c7c3fcd 100644 --- a/VDS/Components/Toggle/VDSToggle.swift +++ b/VDS/Components/Toggle/VDSToggle.swift @@ -35,13 +35,61 @@ import VDSColorTokens public var onChange: Blocks.ActionBlock? - // Sizes are from InVision design specs. - public static var containerSize = CGSize(width: 51, height: 31) - open class func getContainerScaledSize() -> CGSize { return Self.containerSize } + private var showText: Bool { + return model?.showText ?? false + } - public static var knobSize = CGSize(width: 28, height: 28) + private var onText: String { + return model?.onText ?? "On" + } + + private var offText: String { + return model?.offText ?? "off" + } + + private var showTextSpacing: CGFloat { + showText ? 12 : 0 + } + + private var textPosition: VDSToggle.TextPosition { + return model?.textPosition ?? .left + } + + private var textSize: VDSToggle.TextSize { + return model?.textSize ?? .small + } + + private var fontWeight: Typography.FontWeight { + 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 + stackView.axis = .horizontal + stackView.distribution = .fillProportionally + return stackView + }() + + private var label: UILabel = { + let label = UILabel() + label.translatesAutoresizingMaskIntoConstraints = false + return label + }() + + private var toggleView: UIView = { + let view = UIView() + view.translatesAutoresizingMaskIntoConstraints = false + return view + }() + private var knobView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false @@ -70,15 +118,17 @@ 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.backgroundColor = self.containerTintColor.on + self.toggleView.backgroundColor = self.containerTintColor.on } else { self.knobView.backgroundColor = self.knobTintColor.off - self.backgroundColor = self.containerTintColor.off + self.toggleView.backgroundColor = self.containerTintColor.off } }, completion: nil) @@ -108,8 +158,8 @@ import VDSColorTokens private var knobTrailingConstraint: NSLayoutConstraint? private var knobHeightConstraint: NSLayoutConstraint? private var knobWidthConstraint: NSLayoutConstraint? - private var heightConstraint: NSLayoutConstraint? - private var widthConstraint: NSLayoutConstraint? + private var toggleHeightConstraint: NSLayoutConstraint? + private var toggleWidthConstraint: NSLayoutConstraint? private func constrainKnob() { @@ -124,14 +174,14 @@ import VDSColorTokens private func constrainKnobOn() { - knobTrailingConstraint = trailingAnchor.constraint(equalTo: knobView.trailingAnchor, constant: 2) - knobLeadingConstraint = knobView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor) + knobTrailingConstraint = toggleView.trailingAnchor.constraint(equalTo: knobView.trailingAnchor, constant: 2) + knobLeadingConstraint = knobView.leadingAnchor.constraint(greaterThanOrEqualTo: toggleView.leadingAnchor) } private func constrainKnobOff() { - knobTrailingConstraint = trailingAnchor.constraint(greaterThanOrEqualTo: knobView.trailingAnchor) - knobLeadingConstraint = knobView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 2) + knobTrailingConstraint = toggleView.trailingAnchor.constraint(greaterThanOrEqualTo: knobView.trailingAnchor) + knobLeadingConstraint = knobView.leadingAnchor.constraint(equalTo: toggleView.leadingAnchor, constant: 2) } //-------------------------------------------------- @@ -156,60 +206,88 @@ import VDSColorTokens public override func updateView(_ size: CGFloat) { super.updateView(size) - let containerSize = Self.getContainerScaledSize() + let containerSize = Self.getToggleScaledSize() let knobSize = Self.getKnobScaledSize() - heightConstraint?.constant = containerSize.height - widthConstraint?.constant = containerSize.width + toggleHeightConstraint?.constant = containerSize.height + toggleWidthConstraint?.constant = containerSize.width knobHeightConstraint?.constant = knobSize.height knobWidthConstraint?.constant = knobSize.width - layer.cornerRadius = containerSize.height / 2.0 + toggleView.layer.cornerRadius = containerSize.height / 2.0 knobView.layer.cornerRadius = knobSize.height / 2.0 + resetLabel() + changeStateNoAnimation(isOn) } public override func setupView() { super.setupView() - let containerSize = Self.getContainerScaledSize() - let knobSize = Self.getKnobScaledSize() - isAccessibilityElement = true setAccessibilityHint() setAccessibilityLabel() accessibilityTraits = .button - - heightConstraint = heightAnchor.constraint(equalToConstant: containerSize.height) - heightConstraint?.isActive = true - widthConstraint = widthAnchor.constraint(equalToConstant: containerSize.width) - widthConstraint?.isActive = true + addSubview(stackView) + + let containerSize = Self.getToggleScaledSize() + let knobSize = Self.getKnobScaledSize() - layer.cornerRadius = containerSize.height / 2.0 + toggleHeightConstraint = toggleView.heightAnchor.constraint(equalToConstant: containerSize.height) + toggleHeightConstraint?.isActive = true + + toggleWidthConstraint = toggleView.widthAnchor.constraint(equalToConstant: containerSize.width) + toggleWidthConstraint?.isActive = true + + toggleView.layer.cornerRadius = containerSize.height / 2.0 knobView.layer.cornerRadius = knobSize.height / 2.0 - backgroundColor = containerTintColor.off + toggleView.backgroundColor = containerTintColor.off - addSubview(knobView) + toggleView.addSubview(knobView) knobHeightConstraint = knobView.heightAnchor.constraint(equalToConstant: knobSize.height) knobHeightConstraint?.isActive = true knobWidthConstraint = knobView.widthAnchor.constraint(equalToConstant: 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 + knobView.centerYAnchor.constraint(equalTo: toggleView.centerYAnchor).isActive = true + knobView.topAnchor.constraint(greaterThanOrEqualTo: toggleView.topAnchor).isActive = true + toggleView.bottomAnchor.constraint(greaterThanOrEqualTo: knobView.bottomAnchor).isActive = true + //setup stackview + if showText { + stackView.addArrangedSubview(label) + } + resetLabel() + stackView.addArrangedSubview(toggleView) + stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true + stackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true + stackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + stackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true + constrainKnobOff() } + func resetLabel() { + stackView.spacing = showTextSpacing + if showText { + if textPosition == .left { + stackView.insertArrangedSubview(label, at: 0) + } else { + stackView.addArrangedSubview(label) + } + } else if stackView.subviews.contains(label) { + label.removeFromSuperview() + } + } + public override func reset() { super.reset() - backgroundColor = containerTintColor.off + toggleView.backgroundColor = containerTintColor.off knobView.backgroundColor = knobTintColor.off setAccessibilityLabel() isAnimated = true @@ -295,7 +373,7 @@ import VDSColorTokens public func setToggleAppearanceFromState() { - backgroundColor = isEnabled ? isOn ? containerTintColor.on : containerTintColor.off : disabledTintColor.container + toggleView.backgroundColor = isEnabled ? isOn ? containerTintColor.on : containerTintColor.off : disabledTintColor.container knobView.backgroundColor = isEnabled ? isOn ? knobTintColor.on : knobTintColor.off : disabledTintColor.knob } @@ -321,6 +399,13 @@ import VDSColorTokens changeStateNoAnimation(isOn) isAnimated = true isEnabled = !model.disabled + + if model.fontWeight == .regular { + label.font = model.textSize == .small ? VDSFontStyles.Body.Regular.small : VDSFontStyles.Body.Regular.large + } else { + label.font = model.textSize == .small ? VDSFontStyles.Body.Bold.small : VDSFontStyles.Body.Bold.large + } + } } diff --git a/VDS/Components/Toggle/VDSToggleModel.swift b/VDS/Components/Toggle/VDSToggleModel.swift index 5385a01d..1431b0cf 100644 --- a/VDS/Components/Toggle/VDSToggleModel.swift +++ b/VDS/Components/Toggle/VDSToggleModel.swift @@ -8,8 +8,24 @@ import Foundation import UIKit +extension VDSToggle { + + public enum TextSize: String, Codable { + case small, large + } + + public enum TextPosition: String, Codable { + case left, right + } +} + public protocol VDSToggleModel: Surfaceable, FormFieldable, DataTrackable, Disabling, Accessable { var id: String? { get set } var showText: Bool { get set } var on: Bool { get set } + var textSize: VDSToggle.TextSize { get set } + var textPosition: VDSToggle.TextPosition { get set } + var fontWeight: Typography.FontWeight { get set } + var offText: String { get set } + var onText: String { get set } }