diff --git a/VDS/Classes/VDSControl.swift b/VDS/Classes/VDSControl.swift index d60f4901..ec48eb74 100644 --- a/VDS/Classes/VDSControl.swift +++ b/VDS/Classes/VDSControl.swift @@ -7,8 +7,21 @@ import Foundation import UIKit +import Combine -open class VDSControl: UIControl, ViewProtocol { +open class VDSControl: UIControl, Modelable, ViewProtocol { + + @Published public var model: ModelType + private var cancellable: AnyCancellable? + + open func set(with model: ModelType) { + self.model = model + } + + open func onStateChange(viewModel: ModelType) { + + } + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -18,18 +31,14 @@ open class VDSControl: UIControl, ViewProtocol { // MARK: - Initializers //-------------------------------------------------- - public override init(frame: CGRect) { - super.init(frame: .zero) - initialSetup() - } - - public init() { + required public init(with model: ModelType) { + self.model = model super.init(frame: .zero) initialSetup() + set(with: model) } public required init?(coder: NSCoder) { - super.init(coder: coder) fatalError("Control does not support xib.") } @@ -42,6 +51,9 @@ open class VDSControl: UIControl, ViewProtocol { initialSetupPerformed = true setupView() } + cancellable = $model.debounce(for: .seconds(ModelStateDebounce), scheduler: RunLoop.main).sink { [weak self] viewModel in + self?.onStateChange(viewModel: viewModel) + } } open func reset() { diff --git a/VDS/Components/Label/VDSLabel.swift b/VDS/Components/Label/VDSLabel.swift index acb6fc58..e6f1d630 100644 --- a/VDS/Components/Label/VDSLabel.swift +++ b/VDS/Components/Label/VDSLabel.swift @@ -10,7 +10,8 @@ import UIKit import VDSColorTokens import Combine -open class VDSLabel: UILabel, Modelable { +open class VDSLabel: UILabel, Modelable, Initable { + @Published public var model: VDSLabelModel = DefaultLabelModel() private var cancellable: AnyCancellable? @@ -30,10 +31,16 @@ open class VDSLabel: UILabel, Modelable { public var surface: Surface //Initializers - public convenience init() { + required public convenience init() { self.init(frame: .zero) } + public required convenience init(with model: VDSLabelModel) { + self.init() + self.model = model + set(with: model) + } + public override init(frame: CGRect) { super.init(frame: frame) setup() diff --git a/VDS/Components/Toggle/VDSToggle.swift b/VDS/Components/Toggle/VDSToggle.swift index 124b4917..e5ed5777 100644 --- a/VDS/Components/Toggle/VDSToggle.swift +++ b/VDS/Components/Toggle/VDSToggle.swift @@ -17,11 +17,22 @@ import Combine Container: The background of the toggle control. Knob: The circular indicator that slides on the container. */ -@objcMembers open class VDSToggle: VDSControl, Modelable, Changable { - public typealias ModelType = VDSToggleModel - @Published public var model: ModelType = DefaultToggleModel() - private var cancellable: AnyCancellable? +final public class VDSToggle: VDSToggleBase{ + public convenience init() { + self.init(with: DefaultToggleModel()) + } + + required public init(with model: ModelType) { + super.init(with: model) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +@objcMembers open class VDSToggleBase: VDSControl, Changable { //-------------------------------------------------- // MARK: - Private Properties @@ -76,8 +87,8 @@ import Combine // MARK: - Static Properties //-------------------------------------------------- // Sizes are from InVision design specs. - public static var toggleSize = CGSize(width: 52, height: 24) - public static var knobSize = CGSize(width: 20, height: 20) + public let toggleSize = CGSize(width: 52, height: 24) + public let knobSize = CGSize(width: 20, height: 20) //-------------------------------------------------- // MARK: - Computed Properties @@ -103,7 +114,7 @@ import Combine @Proxy(\.model.surface) public var surface: Surface - @Proxy(\VDSToggle.model.on) + @Proxy(\.model.on) open var isOn: Bool open override var isEnabled: Bool { @@ -128,29 +139,6 @@ import Combine private var toggleHeightConstraint: NSLayoutConstraint? private var toggleWidthConstraint: NSLayoutConstraint? - //-------------------------------------------------- - // MARK: - Initializers - //-------------------------------------------------- - public required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public override init(frame: CGRect) { - super.init(frame: frame) - setup() - } - - public convenience override init() { - self.init(frame: .zero) - setup() - } - - func setup() { - cancellable = $model.debounce(for: .seconds(ModelStateDebounce), scheduler: RunLoop.main).sink { [weak self] viewModel in - self?.onStateChange(viewModel: viewModel) - } - } - //functions //-------------------------------------------------- // MARK: - Lifecycle @@ -159,9 +147,6 @@ import Combine public override func updateView(_ size: CGFloat) { super.updateView(size) - let toggleSize = Self.toggleSize - let knobSize = Self.knobSize - toggleHeightConstraint?.constant = toggleSize.height toggleWidthConstraint?.constant = toggleSize.width @@ -185,9 +170,6 @@ import Combine addSubview(stackView) - let toggleSize = Self.toggleSize - let knobSize = Self.knobSize - toggleHeightConstraint = toggleView.heightAnchor.constraint(equalToConstant: toggleSize.height) toggleHeightConstraint?.isActive = true @@ -209,10 +191,6 @@ import Combine knobView.topAnchor.constraint(greaterThanOrEqualTo: toggleView.topAnchor).isActive = true toggleView.bottomAnchor.constraint(greaterThanOrEqualTo: knobView.bottomAnchor).isActive = true - //setup stackview - if showText { - stackView.addArrangedSubview(label) - } ensureLabel() stackView.addArrangedSubview(toggleView) stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true @@ -310,14 +288,14 @@ import Combine public func knobReformAnimation() { UIView.animate(withDuration: 0.1, animations: { - self.knobWidthConstraint?.constant = Self.knobSize.width + self.knobWidthConstraint?.constant = self.knobSize.width self.layoutIfNeeded() }, completion: nil) } /// Follow the SwiftUI View paradigm /// - Parameter viewModel: state - private func onStateChange(viewModel: ModelType) { + open override func onStateChange(viewModel: ModelType) { let enabled = !viewModel.disabled label.set(with: viewModel) @@ -339,7 +317,7 @@ import Combine } self.knobTrailingConstraint?.isActive = true self.knobLeadingConstraint?.isActive = true - self.knobWidthConstraint?.constant = Self.knobSize.width + self.knobWidthConstraint?.constant = self.knobSize.width self.layoutIfNeeded() } @@ -374,9 +352,4 @@ import Combine setNeedsLayout() layoutIfNeeded() } - - // MARK:- Modable - open func set(with model: ModelType) { - self.model = model - } } diff --git a/VDS/Protocols/Modelable.swift b/VDS/Protocols/Modelable.swift index 0b05a33c..83b0e780 100644 --- a/VDS/Protocols/Modelable.swift +++ b/VDS/Protocols/Modelable.swift @@ -12,5 +12,6 @@ public let ModelStateDebounce = 0.02 public protocol Modelable { associatedtype ModelType var model: ModelType { get set } + init(with model: ModelType) func set(with model: ModelType) }