diff --git a/JSONCreator_iOS/JSONCreator/TestToggleVM.swift b/JSONCreator_iOS/JSONCreator/TestToggleVM.swift index 2f31a4a..f282483 100644 --- a/JSONCreator_iOS/JSONCreator/TestToggleVM.swift +++ b/JSONCreator_iOS/JSONCreator/TestToggleVM.swift @@ -162,7 +162,7 @@ public class TestToggleModel3: MoleculeModelProtocol, FormFieldProtocol, VDS.Tog } } -/// The is a Sample of using the ViewModelHandler which is a an intermediate class between the View and the model +/// The is a Sample of using the ViewModelHandlerable which is a an intermediate class between the View and the model /// Since we are using a viewModel there is more legwork upfront, however you can use classes or structs as the model open class TestToggle3: ToggleViewModelHandlerBase>, VDSVMMoleculeViewProtocol { diff --git a/JSONCreator_iOS/JSONCreator/VDSToggleVM.swift b/JSONCreator_iOS/JSONCreator/VDSToggleVM.swift index 12e4ee7..2099ef0 100644 --- a/JSONCreator_iOS/JSONCreator/VDSToggleVM.swift +++ b/JSONCreator_iOS/JSONCreator/VDSToggleVM.swift @@ -16,7 +16,7 @@ import MVMCoreUI ///----------------------------------------------------------------------------- ///MARK: -- VDSVMMoleculeViewProtocol (Contract between VDS -> Atomic ///----------------------------------------------------------------------------- -public protocol VDSVMMoleculeViewProtocol: MoleculeViewProtocol, MVMCoreViewProtocol, ViewModelHandler { +public protocol VDSVMMoleculeViewProtocol: MoleculeViewProtocol, MVMCoreViewProtocol, ViewModelHandlerable { var delegateObject: MVMCoreUIDelegateObject? { get set } var additionalData: [AnyHashable: Any]? { get set } func viewModelDidSet() @@ -32,45 +32,45 @@ extension VDSVMMoleculeViewProtocol { } ///----------------------------------------------------------------------------- -///MARK: -- ViewModelHandler Protocol +///MARK: -- ViewModelHandlerable Protocol (replaces existing ModelHandler ///----------------------------------------------------------------------------- -public protocol ViewModelHandler: AnyObject, Initable { +public protocol ViewModelHandlerable: AnyObject, Initable { associatedtype ModelType: Modelable associatedtype ViewModelType: ViewModel var viewModel: ViewModelType { get set } var subscribers: Set { get set } init(with model: ModelType) func set(with model: ModelType) - func shouldUpdateView(viewModel: ModelType) -> Bool - func updateView(viewModel: ModelType) + func shouldUpdateView(model: ModelType) -> Bool + func updateView() } -extension ViewModelHandler { +extension ViewModelHandlerable { public init() { self.init(with: ModelType()) } public func set(with model: ModelType) { - if shouldUpdateView(viewModel: model){ + if shouldUpdateView(model: model){ viewModel.set(with: model) } } - public func shouldUpdateView(viewModel: ModelType) -> Bool { - self.viewModel.model != viewModel + public func shouldUpdateView(model: ModelType) -> Bool { + self.viewModel.model != model } public func setupUpdateView() { handlerPublisher() .subscribe(on: RunLoop.main) - .sink { [weak self] viewModel in - self?.updateView(viewModel: viewModel) + .sink { [weak self] _ in + self?.updateView() } .store(in: &subscribers) } - public func handlerPublisher() -> AnyPublisher { + public func handlerPublisher() -> AnyPublisher { viewModel .publisher .debounce(for: .seconds(Constants.ModelStateDebounce), scheduler: RunLoop.main) @@ -84,8 +84,8 @@ extension ViewModelHandler { public protocol ViewModel: AnyObject, Surfaceable, Disabling { associatedtype ModelType: Modelable var model: ModelType { get set } - var modelSubject: CurrentValueSubject { get set } - var publisher: AnyPublisher { get } + var modelSubject: PassthroughSubject { get set } + var publisher: AnyPublisher { get } init(with model: ModelType) func set(with model: ModelType) } @@ -95,31 +95,31 @@ public protocol ViewModel: AnyObject, Surfaceable, Disabling { ///----------------------------------------------------------------------------- public class ViewModelBase: NSObject, ViewModel, ObservableObject { public var model: ModelType - public var modelSubject = CurrentValueSubject(ModelType()) - public var publisher: AnyPublisher { modelSubject.eraseToAnyPublisher() } + public var modelSubject = PassthroughSubject() + public var publisher: AnyPublisher { modelSubject.eraseToAnyPublisher() } required public init(with model: ModelType) { self.model = model - modelSubject.send(model) + modelSubject.send() } public func set(with model: ModelType){ self.model = model - modelSubject.send(model) + modelSubject.send() } @Proxy(\.model.surface) - open var surface: Surface { didSet { modelSubject.send(model) }} + open var surface: Surface { didSet { modelSubject.send() }} @Proxy(\.model.disabled) - open var disabled: Bool { didSet { modelSubject.send(model) }} + open var disabled: Bool { didSet { modelSubject.send() }} } ///----------------------------------------------------------------------------- ///MARK: -- ControlViewModelHandler Generic Base Class (Old Control) ///----------------------------------------------------------------------------- -open class ControlViewModelHandler: UIControl, ViewModelHandler, ViewProtocol, Resettable { +open class ControlViewModelHandler: UIControl, ViewModelHandlerable, ViewProtocol, Resettable { public typealias ModelType = ViewModelType.ModelType public var viewModel: ViewModelType = ViewModelType.init(with: ModelType()) @@ -201,7 +201,7 @@ open class ControlViewModelHandler: UIControl, ViewMod //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- - open func updateView(viewModel: ModelType) { + open func updateView() { fatalError("Implement updateView") } @@ -236,6 +236,9 @@ public protocol ToggleViewModel: ViewModel where ModelType: VDS.ToggleModel { var textSize: ToggleTextSize { get set } var textWeight: ToggleTextWeight { get set } var textPosition: ToggleTextPosition { get set } + var knobColor: UIColor { get } + var toggleColor: UIColor { get } + var labelModel: DefaultLabelModel { get } } ///----------------------------------------------------------------------------- @@ -244,25 +247,51 @@ public protocol ToggleViewModel: ViewModel where ModelType: VDS.ToggleModel { public class ToggleViewModelBase: ViewModelBase, ToggleViewModel { @Proxy(\.model.on) - open var isOn: Bool { didSet { modelSubject.send(model) }} + open var isOn: Bool { didSet { modelSubject.send() }} @Proxy(\.model.showText) - public var showText: Bool { didSet { modelSubject.send(model) }} + public var showText: Bool { didSet { modelSubject.send() }} @Proxy(\.model.onText) - public var onText: String { didSet { modelSubject.send(model) }} + public var onText: String { didSet { modelSubject.send() }} @Proxy(\.model.offText) - public var offText: String { didSet { modelSubject.send(model) }} + public var offText: String { didSet { modelSubject.send() }} @Proxy(\.model.textSize) - public var textSize: ToggleTextSize { didSet { modelSubject.send(model) }} + public var textSize: ToggleTextSize { didSet { modelSubject.send() }} @Proxy(\.model.textWeight) - public var textWeight: ToggleTextWeight { didSet { modelSubject.send(model) }} + public var textWeight: ToggleTextWeight { didSet { modelSubject.send() }} @Proxy(\.model.textPosition) - public var textPosition: ToggleTextPosition { didSet { modelSubject.send(model) }} + public var textPosition: ToggleTextPosition { didSet { modelSubject.send() }} + + public var toggleColor: UIColor { return toggleColorConfiguration.getColor(model) } + public var knobColor: UIColor { return toggleColorConfiguration.getColor(model) } + public var labelModel: DefaultLabelModel { model.labelModel } + + private var toggleColorConfiguration = BinaryDisabledSurfaceColorConfiguration().with { + $0.forTrue.enabled.lightColor = VDSColor.paletteGreen26 + $0.forTrue.enabled.darkColor = VDSColor.paletteGreen34 + $0.forTrue.disabled.lightColor = VDSColor.interactiveDisabledOnlight + $0.forTrue.disabled.darkColor = VDSColor.interactiveDisabledOndark + $0.forFalse.enabled.lightColor = VDSColor.elementsSecondaryOnlight + $0.forFalse.enabled.darkColor = VDSColor.paletteGray44 + $0.forFalse.disabled.lightColor = VDSColor.interactiveDisabledOnlight + $0.forFalse.disabled.darkColor = VDSColor.interactiveDisabledOndark + } + + private var knobColorConfiguration = BinaryDisabledSurfaceColorConfiguration().with { + $0.forTrue.enabled.lightColor = VDSColor.elementsPrimaryOndark + $0.forTrue.enabled.darkColor = VDSColor.elementsPrimaryOndark + $0.forTrue.disabled.lightColor = VDSColor.paletteGray95 + $0.forTrue.disabled.darkColor = VDSColor.paletteGray44 + $0.forFalse.enabled.lightColor = VDSColor.elementsPrimaryOndark + $0.forFalse.enabled.darkColor = VDSColor.elementsPrimaryOndark + $0.forFalse.disabled.lightColor = VDSColor.paletteGray95 + $0.forFalse.disabled.darkColor = VDSColor.paletteGray44 + } } ///----------------------------------------------------------------------------- @@ -304,28 +333,6 @@ open class ToggleViewModelHandlerBase: ControlVi public let toggleContainerSize = CGSize(width: 52, height: 44) public let knobSize = CGSize(width: 20, height: 20) - private var toggleColorConfiguration = BinaryDisabledSurfaceColorConfiguration().with { - $0.forTrue.enabled.lightColor = VDSColor.paletteGreen26 - $0.forTrue.enabled.darkColor = VDSColor.paletteGreen34 - $0.forTrue.disabled.lightColor = VDSColor.interactiveDisabledOnlight - $0.forTrue.disabled.darkColor = VDSColor.interactiveDisabledOndark - $0.forFalse.enabled.lightColor = VDSColor.elementsSecondaryOnlight - $0.forFalse.enabled.darkColor = VDSColor.paletteGray44 - $0.forFalse.disabled.lightColor = VDSColor.interactiveDisabledOnlight - $0.forFalse.disabled.darkColor = VDSColor.interactiveDisabledOndark - } - - private var knobColorConfiguration = BinaryDisabledSurfaceColorConfiguration().with { - $0.forTrue.enabled.lightColor = VDSColor.elementsPrimaryOndark - $0.forTrue.enabled.darkColor = VDSColor.elementsPrimaryOndark - $0.forTrue.disabled.lightColor = VDSColor.paletteGray95 - $0.forTrue.disabled.darkColor = VDSColor.paletteGray44 - $0.forFalse.enabled.lightColor = VDSColor.elementsPrimaryOndark - $0.forFalse.enabled.darkColor = VDSColor.elementsPrimaryOndark - $0.forFalse.disabled.lightColor = VDSColor.paletteGray95 - $0.forFalse.disabled.darkColor = VDSColor.paletteGray44 - } - //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- @@ -364,12 +371,12 @@ open class ToggleViewModelHandlerBase: ControlVi //-------------------------------------------------- // MARK: - Toggle //-------------------------------------------------- - private func updateToggle(_ viewModel: ModelType) { + private func updateToggle() { //private func func constrainKnob(){ self.knobLeadingConstraint?.isActive = false self.knobTrailingConstraint?.isActive = false - if viewModel.on { + if viewModel.isOn { self.knobTrailingConstraint = self.toggleView.trailingAnchor.constraint(equalTo: self.knobView.trailingAnchor, constant: 2) self.knobLeadingConstraint = self.knobView.leadingAnchor.constraint(greaterThanOrEqualTo: self.toggleView.leadingAnchor) } else { @@ -382,8 +389,8 @@ open class ToggleViewModelHandlerBase: ControlVi self.layoutIfNeeded() } - let toggleColor = toggleColorConfiguration.getColor(viewModel) - let knobColor = knobColorConfiguration.getColor(viewModel) + let toggleColor = viewModel.toggleColor + let knobColor = viewModel.knobColor if viewModel.disabled { toggleView.backgroundColor = toggleColor @@ -404,7 +411,7 @@ open class ToggleViewModelHandlerBase: ControlVi //-------------------------------------------------- // MARK: - Labels //-------------------------------------------------- - private func updateLabel(_ viewModel: ModelType) { + private func updateLabel() { let showText = viewModel.showText stackView.spacing = showText ? 12 : 0 label.set(with: viewModel.labelModel) @@ -456,7 +463,7 @@ open class ToggleViewModelHandlerBase: ControlVi toggleView.layer.cornerRadius = toggleSize.height / 2.0 knobView.layer.cornerRadius = knobSize.height / 2.0 - toggleView.backgroundColor = toggleColorConfiguration.getColor(viewModel.model) + toggleView.backgroundColor = viewModel.toggleColor toggleContainerView.addSubview(toggleView) toggleView.addSubview(knobView) @@ -471,11 +478,11 @@ open class ToggleViewModelHandlerBase: ControlVi toggleView.bottomAnchor.constraint(greaterThanOrEqualTo: knobView.bottomAnchor).isActive = true - updateLabel(viewModel.model) + updateLabel() stackView.addArrangedSubview(toggleContainerView) stackView.topAnchor.constraint(equalTo: topAnchor).isActive = true stackView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true - stackView.trailingAnchor.constraint(equalTo: trailingAnchor).isActive = true + stackView.widthAnchor.constraint(greaterThanOrEqualToConstant: toggleContainerSize.width).isActive = true stackView.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true toggleView.centerXAnchor.constraint(equalTo: toggleContainerView.centerXAnchor).isActive = true @@ -485,8 +492,8 @@ open class ToggleViewModelHandlerBase: ControlVi public override func reset() { super.reset() - toggleView.backgroundColor = toggleColorConfiguration.getColor(viewModel.model) - knobView.backgroundColor = knobColorConfiguration.getColor(viewModel.model) + toggleView.backgroundColor = viewModel.toggleColor + knobView.backgroundColor = viewModel.knobColor } /// This will toggle the state of the Toggle and execute the actionBlock if provided. @@ -498,38 +505,11 @@ open class ToggleViewModelHandlerBase: ControlVi //-------------------------------------------------- // MARK: - State //-------------------------------------------------- - open override func updateView(viewModel: ModelType) { - updateLabel(viewModel) - updateToggle(viewModel) + open override func updateView() { + updateLabel() + updateToggle() backgroundColor = viewModel.surface.color setNeedsLayout() layoutIfNeeded() } - - public func set(with model: ModelType) { - if shouldUpdateView(viewModel: model){ - viewModel.set(with: model) - } - } - - public func shouldUpdateView(viewModel: ModelType) -> Bool { - self.viewModel.model != viewModel - } - - public func setupUpdateView() { - handlerPublisher() - .subscribe(on: RunLoop.main) - .sink { [weak self] viewModel in - self?.updateView(viewModel: viewModel) - } - .store(in: &subscribers) - } - - public func handlerPublisher() -> AnyPublisher { - viewModel - .publisher - .debounce(for: .seconds(Constants.ModelStateDebounce), scheduler: RunLoop.main) - .eraseToAnyPublisher() - } - }