From dec1a00bb5dd8c1c87c7ba2eba44e18a82809b10 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Tue, 8 Aug 2023 09:46:52 -0500 Subject: [PATCH] refactored out Handlerable Signed-off-by: Matt Bruce --- VDS.xcodeproj/project.pbxproj | 8 +- VDS/Classes/Control.swift | 14 +- VDS/Classes/View.swift | 5 +- .../Buttons/Button/ButtonBase.swift | 8 +- VDS/Components/Label/Label.swift | 8 +- VDS/Components/Tooltip/TooltipDialog.swift | 231 ++++++++++++++++++ VDS/Protocols/Handlerable.swift | 40 --- VDS/Protocols/ViewProtocol.swift | 36 ++- 8 files changed, 284 insertions(+), 66 deletions(-) create mode 100644 VDS/Components/Tooltip/TooltipDialog.swift delete mode 100644 VDS/Protocols/Handlerable.swift diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index aa2ade8b..8de483a9 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -36,7 +36,6 @@ EA3361B6288B2A410071C351 /* Control.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361B5288B2A410071C351 /* Control.swift */; }; EA3361B8288B2AAA0071C351 /* ViewProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */; }; EA3361BD288B2C760071C351 /* TypeAlias.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361BC288B2C760071C351 /* TypeAlias.swift */; }; - EA3361BF288B2EA60071C351 /* Handlerable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361BE288B2EA60071C351 /* Handlerable.swift */; }; EA3361C328902D960071C351 /* Toggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361C228902D960071C351 /* Toggle.swift */; }; EA3361C9289054C50071C351 /* Surfaceable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA3361C8289054C50071C351 /* Surfaceable.swift */; }; EA3362042891E14D0071C351 /* VerizonNHGeTX-Bold.otf in Resources */ = {isa = PBXBuildFile; fileRef = EA3362002891E14C0071C351 /* VerizonNHGeTX-Bold.otf */; }; @@ -69,6 +68,7 @@ EA89201328B568D8006B9984 /* RadioBoxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89201228B568D8006B9984 /* RadioBoxItem.swift */; }; EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA89201428B56CF4006B9984 /* RadioBoxGroup.swift */; }; EA8E40912A7D3F6300934ED3 /* UIView+Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8E40902A7D3F6300934ED3 /* UIView+Accessibility.swift */; }; + EA8E40932A82889500934ED3 /* TooltipDialog.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA8E40922A82889500934ED3 /* TooltipDialog.swift */; }; EA978EC5291D6AFE00ACC883 /* AnyLabelAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA978EC4291D6AFE00ACC883 /* AnyLabelAttribute.swift */; }; EA985BE629688F6A00F2FF2E /* TileletBadgeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985BE529688F6A00F2FF2E /* TileletBadgeModel.swift */; }; EA985BE82968951C00F2FF2E /* TileletTitleModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA985BE72968951C00F2FF2E /* TileletTitleModel.swift */; }; @@ -182,7 +182,6 @@ EA3361B5288B2A410071C351 /* Control.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Control.swift; sourceTree = ""; }; EA3361B7288B2AAA0071C351 /* ViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewProtocol.swift; sourceTree = ""; }; EA3361BC288B2C760071C351 /* TypeAlias.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TypeAlias.swift; sourceTree = ""; }; - EA3361BE288B2EA60071C351 /* Handlerable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Handlerable.swift; sourceTree = ""; }; EA3361C228902D960071C351 /* Toggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toggle.swift; sourceTree = ""; }; EA3361C8289054C50071C351 /* Surfaceable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Surfaceable.swift; sourceTree = ""; }; EA3362002891E14C0071C351 /* VerizonNHGeTX-Bold.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "VerizonNHGeTX-Bold.otf"; sourceTree = ""; }; @@ -215,6 +214,7 @@ EA89201228B568D8006B9984 /* RadioBoxItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxItem.swift; sourceTree = ""; }; EA89201428B56CF4006B9984 /* RadioBoxGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioBoxGroup.swift; sourceTree = ""; }; EA8E40902A7D3F6300934ED3 /* UIView+Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Accessibility.swift"; sourceTree = ""; }; + EA8E40922A82889500934ED3 /* TooltipDialog.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipDialog.swift; sourceTree = ""; }; EA978EC4291D6AFE00ACC883 /* AnyLabelAttribute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyLabelAttribute.swift; sourceTree = ""; }; EA985BE529688F6A00F2FF2E /* TileletBadgeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileletBadgeModel.swift; sourceTree = ""; }; EA985BE72968951C00F2FF2E /* TileletTitleModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileletTitleModel.swift; sourceTree = ""; }; @@ -490,7 +490,6 @@ EA5E305929510F8B0082B959 /* EnumSubset.swift */, EAF7F0A1289AFB3900B287F5 /* Errorable.swift */, EA3361AE288B26310071C351 /* FormFieldable.swift */, - EA3361BE288B2EA60071C351 /* Handlerable.swift */, EA33624628931B050071C351 /* Initable.swift */, EA985C7C297DAED300F2FF2E /* Primitive.swift */, EAF7F0A5289B0CE000B287F5 /* Resetable.swift */, @@ -680,6 +679,7 @@ isa = PBXGroup; children = ( EAB2375C29E8789100AABE9A /* Tooltip.swift */, + EA8E40922A82889500934ED3 /* TooltipDialog.swift */, EAB2376729E9992800AABE9A /* TooltipAlertViewController.swift */, EAB2376929E9E59100AABE9A /* TooltipLaunchable.swift */, EAB2376129E9880400AABE9A /* TrailingTooltipLabel.swift */, @@ -957,6 +957,7 @@ EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */, EA0FC2C62914222900DF80B4 /* ButtonGroup.swift in Sources */, EA89200628B526D6006B9984 /* CheckboxGroup.swift in Sources */, + EA8E40932A82889500934ED3 /* TooltipDialog.swift in Sources */, 44604AD429CE186A00E62B51 /* NotificationButtonModel.swift in Sources */, EAD8D2C128BFDE8B006EB6A6 /* UIGestureRecognizer+Publisher.swift in Sources */, EA0D1C372A681CCE00E5C127 /* ToggleView.swift in Sources */, @@ -1008,7 +1009,6 @@ EAF7F0AD289B142900B287F5 /* StrikeThroughLabelAttribute.swift in Sources */, EAB5FEF12927F4AA00998C17 /* SelfSizingCollectionView.swift in Sources */, EA3361B8288B2AAA0071C351 /* ViewProtocol.swift in Sources */, - EA3361BF288B2EA60071C351 /* Handlerable.swift in Sources */, EA3361A8288B23300071C351 /* UIColor.swift in Sources */, EA1DA1CB2A2E36DC001C51D2 /* SelectorBase.swift in Sources */, EAC9257D29119B5400091998 /* TextLink.swift in Sources */, diff --git a/VDS/Classes/Control.swift b/VDS/Classes/Control.swift index 00f13c57..10eb8ef9 100644 --- a/VDS/Classes/Control.swift +++ b/VDS/Classes/Control.swift @@ -11,7 +11,7 @@ import Combine @objc(VDSControl) /// Base Class use to build Controls. -open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoable, Clickable { +open class Control: UIControl, ViewProtocol, UserInfoable, Clickable { //-------------------------------------------------- // MARK: - Combine Properties @@ -50,14 +50,14 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab } } - /// Override for isSelected to handle setNeedsUpdate() on changes. + /// Whether the Control is selected or not. open override var isSelected: Bool { didSet { setNeedsUpdate() } } open var touchUpInsideCount: Int = 0 var isHighlightAnimating = false - /// Override to deal with only calling setNeedsUpdate() if needed. + /// Whether the Control is highlighted or not.. open override var isHighlighted: Bool { didSet { if isHighlightAnimating == false && touchUpInsideCount > 0 { @@ -75,7 +75,7 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab } } - /// Override to deal with setNeedsUpdate(). + /// Whether the Control is enabled or not. open override var isEnabled: Bool { didSet { setNeedsUpdate(); isUserInteractionEnabled = isEnabled } } //-------------------------------------------------- @@ -116,9 +116,7 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab } /// Function used to make changes to the View based off a change events or from local properties. - open func updateView() { - updateAccessibility() - } + open func updateView() { } open func updateAccessibility() { if isSelected { @@ -146,7 +144,7 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab // MARK: - Overrides //-------------------------------------------------- - ///Override to deal with sending actions for accessibility. + /// Implement accessibilityActivate on an element in order to handle the default action. /// - Returns: Based on whether the userInteraction is enabled. override open func accessibilityActivate() -> Bool { // Hold state in case User wanted isAnimated to remain off. diff --git a/VDS/Classes/View.swift b/VDS/Classes/View.swift index 9566b79a..8f1e40a3 100644 --- a/VDS/Classes/View.swift +++ b/VDS/Classes/View.swift @@ -9,10 +9,9 @@ import Foundation import UIKit import Combine - @objc(VDSView) /// Base Class used to build Views. -open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable { +open class View: UIView, ViewProtocol, UserInfoable { //-------------------------------------------------- // MARK: - Combine Properties @@ -44,6 +43,7 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable { } } + /// Whether the View is enabled or not. open var isEnabled: Bool = true { didSet { setNeedsUpdate(); isUserInteractionEnabled = isEnabled } } //-------------------------------------------------- @@ -86,7 +86,6 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable { /// Function used to make changes to the View based off a change events or from local properties.. open func updateView() { - updateAccessibility() } /// Used to update any Accessibility properties. diff --git a/VDS/Components/Buttons/Button/ButtonBase.swift b/VDS/Components/Buttons/Button/ButtonBase.swift index 6672a320..2f1e23b5 100644 --- a/VDS/Components/Buttons/Button/ButtonBase.swift +++ b/VDS/Components/Buttons/Button/ButtonBase.swift @@ -18,7 +18,7 @@ public protocol Buttonable: UIControl, Surfaceable, Disabling { } @objc(VDSButtonBase) -open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettable, UserInfoable, Clickable { +open class ButtonBase: UIButton, Buttonable, ViewProtocol, UserInfoable, Clickable { //-------------------------------------------------- // MARK: - Configuration Properties @@ -72,11 +72,11 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab if isHighlightAnimating == false && touchUpInsideCount > 0 { isHighlightAnimating = true UIView.animate(withDuration: 0.1, animations: { [weak self] in - self?.updateView() + self?.setNeedsUpdate() }) { [weak self] _ in //you update the view since this is typically a quick change UIView.animate(withDuration: 0.1, animations: { [weak self] in - self?.updateView() + self?.setNeedsUpdate() self?.isHighlightAnimating = false }) } @@ -94,6 +94,7 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab } } + /// Whether the Control is enabled or not. open override var isEnabled: Bool { didSet { setNeedsUpdate(); isUserInteractionEnabled = isEnabled } } open var textStyle: TextStyle { .defaultStyle } @@ -163,7 +164,6 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab /// Function used to make changes to the View based off a change events or from local properties. open func updateView() { updateLabel() - updateAccessibility() } open func updateAccessibility() { diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 1701311f..27f3fd79 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -11,7 +11,7 @@ import VDSColorTokens import Combine @objc(VDSLabel) -open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { +open class Label: UILabel, ViewProtocol, UserInfoable { //-------------------------------------------------- // MARK: - Combine Properties @@ -61,6 +61,7 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { } } + /// Whether the View is enabled or not. open override var isEnabled: Bool { didSet { setNeedsUpdate(); isUserInteractionEnabled = isEnabled } } //-------------------------------------------------- @@ -158,10 +159,7 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { //set the attributed text attributedText = mutableText - - //get accessibility - updateAccessibility() - + //force a drawText setNeedsDisplay() } diff --git a/VDS/Components/Tooltip/TooltipDialog.swift b/VDS/Components/Tooltip/TooltipDialog.swift new file mode 100644 index 00000000..3a8826b3 --- /dev/null +++ b/VDS/Components/Tooltip/TooltipDialog.swift @@ -0,0 +1,231 @@ +// +// TooltipDialog.swift +// VDS +// +// Created by Matt Bruce on 8/8/23. +// + +import Foundation +import UIKit +import VDSColorTokens + +open class TooltipDialog: View, UIScrollViewDelegate { + //-------------------------------------------------- + // MARK: - Private Properties + //-------------------------------------------------- + + private var scrollView = UIScrollView().with { + $0.isAccessibilityElement = false + $0.translatesAutoresizingMaskIntoConstraints = false + $0.backgroundColor = .clear + } + + private let contentStackView = UIStackView().with { + $0.isAccessibilityElement = false + $0.translatesAutoresizingMaskIntoConstraints = false + $0.axis = .vertical + $0.distribution = .fillProportionally + $0.spacing = 0 + } + + private var line = Line().with { instance in + instance.lineViewColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsLowcontrastOnlight, VDSColor.elementsLowcontrastOndark).eraseToAnyColorable() + } + + //-------------------------------------------------- + // MARK: - Public Properties + //-------------------------------------------------- + open var titleText: String? { didSet { setNeedsUpdate() }} + open var titleLabel = Label().with { label in + label.isAccessibilityElement = true + label.textStyle = .boldTitleMedium + } + + open var contentText: String? { didSet { setNeedsUpdate() }} + open var contentLabel = Label().with { label in + label.isAccessibilityElement = true + label.textStyle = .bodyLarge + } + + open var contentView: UIView? = nil + + open var closeButtonText: String = "Close" { didSet { setNeedsUpdate() }} + + open lazy var closeButton: UIButton = { + let button = UIButton(type: .system) + button.isAccessibilityElement = true + button.backgroundColor = .clear + button.setTitle("Close", for: .normal) + button.titleLabel?.font = TextStyle.bodyLarge.font + button.translatesAutoresizingMaskIntoConstraints = false + return button + }() + + //-------------------------------------------------- + // MARK: - Configuration + //-------------------------------------------------- + private var closeButtonHeight: CGFloat = 44.0 + private var fullWidth: CGFloat = 296 + private var minHeight: CGFloat = 96.0 + private var maxHeight: CGFloat = 312.0 + private let containerViewInset = VDSLayout.Spacing.space4X.value + + private let backgroundColorConfiguration = SurfaceColorConfiguration(VDSColor.backgroundPrimaryLight, VDSColor.backgroundPrimaryDark) + private let closeButtonTextColorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) + + private var contentStackViewBottomConstraint: NSLayoutConstraint? + private var heightConstraint: NSLayoutConstraint? + + //-------------------------------------------------- + // MARK: - Lifecycle + //-------------------------------------------------- + open override func setup() { + super.setup() + layer.cornerRadius = 8 + contentStackView.addArrangedSubview(titleLabel) + contentStackView.addArrangedSubview(contentLabel) + scrollView.addSubview(contentStackView) + addSubview(scrollView) + addSubview(line) + addSubview(closeButton) + + // Activate constraints + NSLayoutConstraint.activate([ + widthAnchor.constraint(equalToConstant: fullWidth), + + // Constraints for the scroll view + scrollView.topAnchor.constraint(equalTo: topAnchor, constant: VDSLayout.Spacing.space4X.value), + scrollView.leadingAnchor.constraint(equalTo: leadingAnchor), + scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), + scrollView.bottomAnchor.constraint(equalTo: line.topAnchor), + + line.leadingAnchor.constraint(equalTo: leadingAnchor), + line.trailingAnchor.constraint(equalTo: trailingAnchor), + + closeButton.topAnchor.constraint(equalTo: line.bottomAnchor), + closeButton.leadingAnchor.constraint(equalTo: leadingAnchor), + closeButton.trailingAnchor.constraint(equalTo: trailingAnchor), + closeButton.bottomAnchor.constraint(equalTo: bottomAnchor), + closeButton.heightAnchor.constraint(equalToConstant: closeButtonHeight), + + contentStackView.topAnchor.constraint(equalTo: scrollView.topAnchor), + contentStackView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: containerViewInset), + contentStackView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -containerViewInset), + contentStackView.widthAnchor.constraint(equalTo: scrollView.widthAnchor, constant: -(containerViewInset * 2)), + + ]) + contentStackViewBottomConstraint = contentStackView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor) + contentStackViewBottomConstraint?.activate() + + heightConstraint = heightAnchor.constraint(equalToConstant: minHeight) + heightConstraint?.activate() + } + + /// Function used to make changes to the View based off a change events or from local properties. + open override func updateView() { + super.updateView() + + backgroundColor = backgroundColorConfiguration.getColor(self) + scrollView.indicatorStyle = surface == .light ? .black : .white + + titleLabel.removeFromSuperview() + contentLabel.removeFromSuperview() + contentView?.removeFromSuperview() + + titleLabel.surface = surface + contentLabel.surface = surface + line.surface = surface + + titleLabel.text = titleText + contentLabel.text = contentText + + titleLabel.sizeToFit() + contentLabel.sizeToFit() + + var addedTitle = false + + if let titleText, !titleText.isEmpty { + contentStackView.addArrangedSubview(titleLabel) + addedTitle = true + } + + var addedContent = false + if let contentText, !contentText.isEmpty { + contentStackView.addArrangedSubview(contentLabel) + addedContent = true + } else if let contentView { + contentView.translatesAutoresizingMaskIntoConstraints = false + if var surfaceable = contentView as? Surfaceable { + surfaceable.surface = surface + } + let wrapper = View() + wrapper.addSubview(contentView) + contentView.pinTop() + contentView.pinLeading() + contentView.pinBottom() + contentView.pinTrailingLessThanOrEqualTo() + contentView.setNeedsLayout() + contentStackView.addArrangedSubview(wrapper) + addedContent = true + } + + if addedTitle && addedContent { + contentStackView.setCustomSpacing(VDSLayout.Spacing.space1X.value, after: titleLabel) + } + + let closeButtonTextColor = closeButtonTextColorConfiguration.getColor(self) + closeButton.setTitleColor(closeButtonTextColor, for: .normal) + closeButton.setTitleColor(closeButtonTextColor, for: .highlighted) + closeButton.setTitle(closeButtonText, for: .normal) + closeButton.accessibilityLabel = closeButtonText + + contentStackView.setNeedsLayout() + contentStackView.layoutIfNeeded() + + scrollView.setNeedsLayout() + scrollView.layoutIfNeeded() + + //dealing with height + //we can't really use the minMax height and set constraints for + //greaterThan or lessThan on the heightAnchor due to scrollView/stackView intrinsic size + //therefore we can do a little math and manually set the height based off all of the content + var contentHeight = closeButtonHeight + scrollView.contentSize.height + (containerViewInset * 2) + + //reset the bottomConstraint + contentStackViewBottomConstraint?.constant = 0 + + if contentHeight < minHeight { + contentHeight = minHeight + + } else if contentHeight > maxHeight { + contentHeight = maxHeight + //since we are now scrolling, add padding to the bottom of the + //stackView between the bottom of the scrollView + contentStackViewBottomConstraint?.constant = -containerViewInset + } + + heightConstraint?.constant = contentHeight + } + + lazy var primaryAccessibilityElement = UIAccessibilityElement(accessibilityContainer: self).with { + $0.accessibilityLabel = "Tooltip" + $0.accessibilityValue = "expanded" + } + + open override func updateAccessibility() { + super.updateAccessibility() + + primaryAccessibilityElement.accessibilityFrameInContainerSpace = .init(origin: .zero, size: .init(width: bounds.width, height: VDSLayout.Spacing.space1X.value)) + primaryAccessibilityElement.accessibilityHint = "Click on the \(closeButtonText) button to close." + } + + override public var accessibilityElements: [Any]? { + get { + var elements: [Any] = []//[primaryAccessibilityElement] + contentStackView.arrangedSubviews.forEach{ elements.append($0) } + elements.append(closeButton) + return elements + } + set {} + } +} diff --git a/VDS/Protocols/Handlerable.swift b/VDS/Protocols/Handlerable.swift deleted file mode 100644 index 727f076e..00000000 --- a/VDS/Protocols/Handlerable.swift +++ /dev/null @@ -1,40 +0,0 @@ -// -// Handlerable.swift -// VDS -// -// Created by Matt Bruce on 7/22/22. -// - -import Foundation -import Combine -import UIKit - -public protocol Handlerable: AnyObject, Initable, Disabling, Surfaceable { - /// Set of Subscribers for any Publishers for this Control. - var subscribers: Set { get set } - /// Key of whether or not updateView() is called in setNeedsUpdate() - var shouldUpdateView: Bool { get set } - /// Function used to make changes to the View based off a change events or from local properties. - func updateView() -} - -extension Handlerable { - /// Function called when there are changes in a View based off a change events or from local properties. - public func setNeedsUpdate() { - if shouldUpdateView { - shouldUpdateView = false - updateView() - shouldUpdateView = true - } - } -} - -extension Handlerable where Self: UIControl { - /// Helper function to assign a completion block to a specific UIControl Event using Combine and stored in the subscribers. - public func addEvent(event: UIControl.Event, block: @escaping (Self)->()) { - publisher(for: event) - .sink(receiveValue: { c in - block(c) - }).store(in: &subscribers) - } -} diff --git a/VDS/Protocols/ViewProtocol.swift b/VDS/Protocols/ViewProtocol.swift index 69c13286..47b9f9b0 100644 --- a/VDS/Protocols/ViewProtocol.swift +++ b/VDS/Protocols/ViewProtocol.swift @@ -7,17 +7,39 @@ import Foundation import UIKit +import Combine + +public protocol ViewProtocol: AnyObject, Initable, Resettable, Disabling, Surfaceable { + /// Set of Subscribers for any Publishers for this Control. + var subscribers: Set { get set } + + /// Key of whether or not updateView() is called in setNeedsUpdate() + var shouldUpdateView: Bool { get set } -public protocol ViewProtocol { - /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. func setup() + /// Function used to make changes to the View based off a change events or from local properties. + func updateView() + /// Used to update any Accessibility properties. func updateAccessibility() } +extension ViewProtocol { + /// Function called when there are changes in a View based off a change events or from local properties. + public func setNeedsUpdate() { + if shouldUpdateView { + shouldUpdateView = false + updateView() + updateAccessibility() + shouldUpdateView = true + } + } +} + extension ViewProtocol where Self: UIView { + /// Helper method for removing a superview and updating Self. public func removeFromSuperview(_ view: UIView){ if view.superview != nil { @@ -26,3 +48,13 @@ extension ViewProtocol where Self: UIView { } } } + +extension ViewProtocol where Self: UIControl { + /// Helper function to assign a completion block to a specific UIControl Event using Combine and stored in the subscribers. + public func addEvent(event: UIControl.Event, block: @escaping (Self)->()) { + publisher(for: event) + .sink(receiveValue: { c in + block(c) + }).store(in: &subscribers) + } +}