diff --git a/VDS.xcodeproj/project.pbxproj b/VDS.xcodeproj/project.pbxproj index 6e5d9e43..615218d1 100644 --- a/VDS.xcodeproj/project.pbxproj +++ b/VDS.xcodeproj/project.pbxproj @@ -100,7 +100,6 @@ EAF1FE9B29DB1A6000101452 /* Changeable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF1FE9A29DB1A6000101452 /* Changeable.swift */; }; EAF7F0952899861000B287F5 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0932899861000B287F5 /* Checkbox.swift */; }; EAF7F09A2899B17200B287F5 /* CATransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0992899B17200B287F5 /* CATransaction.swift */; }; - EAF7F09E289AAEC000B287F5 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F09D289AAEC000B287F5 /* Constants.swift */; }; EAF7F0A0289AB7EC00B287F5 /* View.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F09F289AB7EC00B287F5 /* View.swift */; }; EAF7F0A2289AFB3900B287F5 /* Errorable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0A1289AFB3900B287F5 /* Errorable.swift */; }; EAF7F0A4289B017C00B287F5 /* LabelAttributeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F0A3289B017C00B287F5 /* LabelAttributeModel.swift */; }; @@ -221,7 +220,6 @@ EAF1FE9A29DB1A6000101452 /* Changeable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Changeable.swift; sourceTree = ""; }; EAF7F0932899861000B287F5 /* Checkbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = ""; }; EAF7F0992899B17200B287F5 /* CATransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CATransaction.swift; sourceTree = ""; }; - EAF7F09D289AAEC000B287F5 /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; EAF7F09F289AB7EC00B287F5 /* View.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = View.swift; sourceTree = ""; }; EAF7F0A1289AFB3900B287F5 /* Errorable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errorable.swift; sourceTree = ""; }; EAF7F0A3289B017C00B287F5 /* LabelAttributeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelAttributeModel.swift; sourceTree = ""; }; @@ -447,7 +445,6 @@ children = ( EA985C1C296CD13600F2FF2E /* BundleManager.swift */, EAF7F0B8289C139800B287F5 /* ColorConfiguration.swift */, - EAF7F09D289AAEC000B287F5 /* Constants.swift */, EA3361B5288B2A410071C351 /* Control.swift */, EAF7F09F289AB7EC00B287F5 /* View.swift */, EA4DB18428CA967F00103EE3 /* SelectorGroupHandlerBase.swift */, @@ -853,7 +850,6 @@ 44604AD729CE196600E62B51 /* Line.swift in Sources */, EA5E3058295105A40082B959 /* Tilelet.swift in Sources */, EA89201528B56CF4006B9984 /* RadioBoxGroup.swift in Sources */, - EAF7F09E289AAEC000B287F5 /* Constants.swift in Sources */, EA985C1D296CD13600F2FF2E /* BundleManager.swift in Sources */, EA1F266528B945070033E859 /* RadioSwatch.swift in Sources */, EA4DB18528CA967F00103EE3 /* SelectorGroupHandlerBase.swift in Sources */, diff --git a/VDS/Classes/Constants.swift b/VDS/Classes/Constants.swift deleted file mode 100644 index 4928c3f1..00000000 --- a/VDS/Classes/Constants.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// VDSConstants.swift -// VDS -// -// Created by Matt Bruce on 8/3/22. -// - -import Foundation - -public struct Constants { - public static let StateDebounce = 0.0 //.001 - public static let PaddingOne = 10.0 -} diff --git a/VDS/Classes/Control.swift b/VDS/Classes/Control.swift index 13f50e7f..5296e599 100644 --- a/VDS/Classes/Control.swift +++ b/VDS/Classes/Control.swift @@ -15,7 +15,6 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab //-------------------------------------------------- // MARK: - Combine Properties //-------------------------------------------------- - public var subject = PassthroughSubject() public var subscribers = Set() public var onClickSubscriber: AnyCancellable? { willSet { @@ -30,13 +29,15 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab //-------------------------------------------------- private var initialSetupPerformed = false + open var shouldUpdateView: Bool = true + open var userInfo = [String: Primitive]() - open var surface: Surface = .light { didSet { didChange() } } + open var surface: Surface = .light { didSet { setNeedsUpdate() } } open var disabled: Bool = false { didSet { isEnabled = !disabled } } - open override var isSelected: Bool { didSet { didChange() } } + open override var isSelected: Bool { didSet { setNeedsUpdate() } } public var touchUpInsideCount: Int = 0 @@ -65,7 +66,7 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab disabled = !newValue } isUserInteractionEnabled = isEnabled - didChange() + setNeedsUpdate() } } @@ -95,8 +96,7 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab if !initialSetupPerformed { initialSetupPerformed = true setup() - setupDidChangeEvent() - updateView() + setNeedsUpdate() } } diff --git a/VDS/Classes/SelectorGroupHandlerBase.swift b/VDS/Classes/SelectorGroupHandlerBase.swift index d686f549..b2483461 100644 --- a/VDS/Classes/SelectorGroupHandlerBase.swift +++ b/VDS/Classes/SelectorGroupHandlerBase.swift @@ -48,7 +48,7 @@ open class SelectorGroupHandlerBase: Control, Changeable { } public func valueChanged() { - DispatchQueue.main.asyncAfter(deadline: .now() + Constants.StateDebounce) { [weak self] in + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in self?.sendActions(for: .valueChanged) } } diff --git a/VDS/Classes/View.swift b/VDS/Classes/View.swift index c48ff108..2372ed4b 100644 --- a/VDS/Classes/View.swift +++ b/VDS/Classes/View.swift @@ -16,7 +16,6 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable { //-------------------------------------------------- // MARK: - Combine Properties //-------------------------------------------------- - public var subject = PassthroughSubject() public var subscribers = Set() //-------------------------------------------------- @@ -24,9 +23,11 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable { //-------------------------------------------------- private var initialSetupPerformed = false + open var shouldUpdateView: Bool = true + open var userInfo = [String: Primitive]() - open var surface: Surface = .light { didSet { didChange() }} + open var surface: Surface = .light { didSet { setNeedsUpdate() }} open var disabled: Bool = false { didSet { isEnabled = !disabled } } @@ -37,7 +38,7 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable { disabled = !newValue } isUserInteractionEnabled = isEnabled - didChange() + setNeedsUpdate() } } @@ -67,8 +68,7 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable { if !initialSetupPerformed { initialSetupPerformed = true setup() - setupDidChangeEvent() - updateView() + setNeedsUpdate() } } diff --git a/VDS/Components/Badge/Badge.swift b/VDS/Components/Badge/Badge.swift index 65fc22b6..c7fed36f 100644 --- a/VDS/Components/Badge/Badge.swift +++ b/VDS/Components/Badge/Badge.swift @@ -32,13 +32,13 @@ open class Badge: View { $0.textStyle = .boldBodySmall } - open var fillColor: FillColor = .red { didSet { didChange() }} + open var fillColor: FillColor = .red { didSet { setNeedsUpdate() }} - open var text: String = "" { didSet { didChange() }} + open var text: String = "" { didSet { setNeedsUpdate() }} - open var maxWidth: CGFloat? { didSet { didChange() }} + open var maxWidth: CGFloat? { didSet { setNeedsUpdate() }} - open var numberOfLines: Int = 1 { didSet { didChange() }} + open var numberOfLines: Int = 1 { didSet { setNeedsUpdate() }} //-------------------------------------------------- // MARK: - Constraints @@ -66,16 +66,18 @@ open class Badge: View { } open override func reset() { - super.reset() + super.reset() + shouldUpdateView = false label.reset() label.lineBreakMode = .byTruncatingTail label.textPosition = .left label.textStyle = .boldBodySmall - fillColor = .red text = "" maxWidth = nil numberOfLines = 1 + shouldUpdateView = true + setNeedsUpdate() } //-------------------------------------------------- diff --git a/VDS/Components/Buttons/Button/Button.swift b/VDS/Components/Buttons/Button/Button.swift index 0b52e0fe..3ac084fa 100644 --- a/VDS/Components/Buttons/Button/Button.swift +++ b/VDS/Components/Buttons/Button/Button.swift @@ -32,11 +32,11 @@ open class Button: ButtonBase, Useable { //-------------------------------------------------- open override var availableSizes: [ButtonSize] { [.large, .small] } - open var use: Use = .primary { didSet { didChange() }} + open var use: Use = .primary { didSet { setNeedsUpdate() }} - open var size: ButtonSize = .large { didSet { didChange() }} + open var size: ButtonSize = .large { didSet { setNeedsUpdate() }} - open var width: CGFloat? { didSet { didChange() }} + open var width: CGFloat? { didSet { setNeedsUpdate() }} open override var textColor: UIColor { textColorConfiguration.getColor(self) @@ -132,9 +132,12 @@ open class Button: ButtonBase, Useable { open override func reset() { super.reset() + shouldUpdateView = false use = .primary width = nil size = .large + shouldUpdateView = true + setNeedsUpdate() } //-------------------------------------------------- diff --git a/VDS/Components/Buttons/Button/ButtonBase.swift b/VDS/Components/Buttons/Button/ButtonBase.swift index bd00e312..1e403570 100644 --- a/VDS/Components/Buttons/Button/ButtonBase.swift +++ b/VDS/Components/Buttons/Button/ButtonBase.swift @@ -28,7 +28,6 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab //-------------------------------------------------- // MARK: - Combine Properties //-------------------------------------------------- - public var subject = PassthroughSubject() public var subscribers = Set() public var onClickSubscriber: AnyCancellable? { willSet { @@ -46,13 +45,15 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- + open var shouldUpdateView: Bool = true + open var availableSizes: [ButtonSize] { [] } - open var text: String? { didSet { didChange() } } + open var text: String? { didSet { setNeedsUpdate() } } open var attributes: [any LabelAttributeModel]? { nil } - open var surface: Surface = .light { didSet { didChange() }} + open var surface: Surface = .light { didSet { setNeedsUpdate() }} open var disabled: Bool = false { didSet { isEnabled = !disabled } } @@ -90,7 +91,7 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab disabled = !newValue } isUserInteractionEnabled = isEnabled - didChange() + setNeedsUpdate() } } @@ -121,8 +122,7 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab translatesAutoresizingMaskIntoConstraints = false accessibilityCustomActions = [] setup() - setupDidChangeEvent(true) - updateView() + setNeedsUpdate() } } @@ -136,10 +136,13 @@ open class ButtonBase: UIButton, Buttonable, Handlerable, ViewProtocol, Resettab } open func reset() { + shouldUpdateView = false surface = .light disabled = false text = nil accessibilityCustomActions = [] + shouldUpdateView = true + setNeedsUpdate() } //-------------------------------------------------- diff --git a/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift b/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift index ff0b6152..9f845f96 100644 --- a/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift +++ b/VDS/Components/Buttons/ButtonGroup/ButtonGroup.swift @@ -25,16 +25,16 @@ open class ButtonGroup: View, UICollectionViewDataSource, UICollectionViewDelega // MARK: - Public Properties //-------------------------------------------------- //An object containing number of Button components per row, in each viewport - open var rowQuantityPhone: Int = 0 { didSet { didChange() } } + open var rowQuantityPhone: Int = 0 { didSet { setNeedsUpdate() } } - open var rowQuantityTablet: Int = 0 { didSet { didChange() } } + open var rowQuantityTablet: Int = 0 { didSet { setNeedsUpdate() } } public var rowQuantity: Int { UIDevice.isIPad ? rowQuantityTablet : rowQuantityPhone } //If provided, aligns TextLink/TextLinkCaret alignment when rowQuantity is set one. - open var buttonPosition: ButtonPosition = .center { didSet { didChange() }} + open var buttonPosition: ButtonPosition = .center { didSet { setNeedsUpdate() }} - open var buttons: [Buttonable] = [] { didSet { didChange() }} + open var buttons: [Buttonable] = [] { didSet { setNeedsUpdate() }} //If provided, width of Button components will be rendered based on this value. If omitted, default button widths are rendered. open var buttonWidth: CGFloat? { @@ -47,7 +47,7 @@ open class ButtonGroup: View, UICollectionViewDataSource, UICollectionViewDelega button.width = buttonWidth } } - didChange() + setNeedsUpdate() } } @@ -64,7 +64,7 @@ open class ButtonGroup: View, UICollectionViewDataSource, UICollectionViewDelega self.buttonWidth = nil } positionLayout.buttonPercentage = buttonPercentage - didChange() + setNeedsUpdate() } } diff --git a/VDS/Components/Buttons/TextLink/TextLink.swift b/VDS/Components/Buttons/TextLink/TextLink.swift index a253a406..f90f160f 100644 --- a/VDS/Components/Buttons/TextLink/TextLink.swift +++ b/VDS/Components/Buttons/TextLink/TextLink.swift @@ -22,7 +22,7 @@ open class TextLink: ButtonBase { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - open var size: ButtonSize = .large { didSet { didChange() }} + open var size: ButtonSize = .large { didSet { setNeedsUpdate() }} open override var availableSizes: [ButtonSize] { [.large, .small] } @@ -88,10 +88,14 @@ open class TextLink: ButtonBase { open override func reset() { super.reset() + shouldUpdateView = false + text = nil size = .large accessibilityCustomActions = [] isAccessibilityElement = true accessibilityTraits = .link + shouldUpdateView = true + setNeedsUpdate() } //-------------------------------------------------- diff --git a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift index f6ba09a0..2fb1cd67 100644 --- a/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift +++ b/VDS/Components/Buttons/TextLinkCaret/TextLinkCaret.swift @@ -28,11 +28,7 @@ open class TextLinkCaret: ButtonBase { TextStyle.boldBodyLarge } - private var caretView = CaretView().with { - $0.size = CaretView.Size.small(.vertical) - $0.lineWidth = 2 - } - private var imageAttribute: ImageLabelAttribute? + private var imageAttribute: ImageSpaceLabelAttribute? open override var attributes: [any LabelAttributeModel]? { guard let imageAttribute else { return nil } @@ -44,24 +40,12 @@ open class TextLinkCaret: ButtonBase { //-------------------------------------------------- public override var availableSizes: [ButtonSize] { [.large] } - open var iconPosition: IconPosition = .right { didSet { didChange() } } + open var iconPosition: IconPosition = .right { didSet { setNeedsUpdate() } } private var height: CGFloat { 44 } - - private var _text: String? - - open override var text: String? { - get{ _text } - set { - var updatedText = newValue ?? "" - updatedText = iconPosition == .right ? "\(updatedText) " : " \(updatedText)" - _text = updatedText - didChange() - } - } - + open override var textColor: UIColor { textColorConfiguration.getColor(self) } @@ -92,14 +76,12 @@ open class TextLinkCaret: ButtonBase { //-------------------------------------------------- open override func setup() { super.setup() - - let size = caretView.size!.dimensions() - caretView.frame = .init(x: 0, y: 0, width: size.width, height: size.height) } open override func reset() { super.reset() iconPosition = .right + text = nil } //-------------------------------------------------- @@ -111,204 +93,53 @@ open class TextLinkCaret: ButtonBase { var itemWidth = size.width - if let caretWidth = caretView.size?.dimensions().width { + if let caretWidth = imageAttribute?.width { itemWidth += caretWidth } return CGSize(width: itemWidth, height: size.height) } open override func updateView() { - - let updatedText = text ?? "" - caretView.surface = surface - caretView.disabled = disabled - caretView.direction = iconPosition == .right ? CaretView.Direction.right : CaretView.Direction.left - - let image = caretView.getImage() - let location = iconPosition == .right ? updatedText.count : 0 - - imageAttribute = ImageLabelAttribute(location: location, - image: image, - tintColor: textColor) - + imageAttribute = ImageSpaceLabelAttribute(tintColor: textColor, position: iconPosition) super.updateView() } } -extension UIView { - public func getImage() -> UIImage { - let renderer = UIGraphicsImageRenderer(size: self.bounds.size) - let image = renderer.image { ctx in - self.drawHierarchy(in: self.bounds, afterScreenUpdates: true) - } - return image - } -} +extension TextLinkCaret { + struct ImageSpaceLabelAttribute: LabelAttributeModel { + var id: UUID = .init() + var location: Int = 0 + var length: Int = 1 + var tintColor: UIColor + var position: IconPosition + var spacerWidth: CGFloat = 2.0 + var width: CGFloat { caretSize.width + spacerWidth } + var caretSize: CGSize { Icon.Size.xsmall.dimensions } -internal class CaretView: View { - //------------------------------------------------------ - // MARK: - Properties - //------------------------------------------------------ - private var caretPath: UIBezierPath = UIBezierPath() - - public var lineWidth: CGFloat = 1 { didSet{ didChange() } } - - public var direction: Direction = .right { didSet{ didChange() } } - - public var size: Size? { didSet{ didChange() } } - - public var colorConfiguration: AnyColorable = ViewColorConfiguration().with { - $0.setSurfaceColors(VDSColor.elementsSecondaryOnlight, VDSColor.elementsSecondaryOndark, forDisabled: true) - $0.setSurfaceColors(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark, forDisabled: false) - }.eraseToAnyColorable() - - - //------------------------------------------------------ - // MARK: - Constraints - //------------------------------------------------------ - - /// Sizes of CaretView are derived from InVision design specs. They are provided for convenience. - public enum Size { - case small(Orientation) - case medium(Orientation) - case large(Orientation) - - /// Orientation based on the longest line of the view. - public enum Orientation { - case vertical - case horizontal + init(tintColor: UIColor, position: IconPosition) { + self.tintColor = tintColor + self.position = position } - /// Dimensions of container; provided by InVision design. - func dimensions() -> CGSize { + func setAttribute(on attributedString: NSMutableAttributedString) { + let imageAttr = ImageLabelAttribute(location: location, imageName: "\(position.rawValue)-caret-bold", frame: .init(x: 0, y: 0, width: caretSize.width, height: caretSize.height), tintColor: tintColor) + let spaceAttr = ImageLabelAttribute(location: 0, imageName: "info", frame: .init(x: 0, y: 0, width: spacerWidth, height: 5.0), tintColor: .clear) - switch self { - case .small(let o): - return o == .vertical ? CGSize(width: 6.9, height: 10.96) : CGSize(width: 10.96, height: 6.9) - - case .medium(let o): - return o == .vertical ? CGSize(width: 9.9, height: 16.96) : CGSize(width: 16.96, height: 9.9) - - case .large(let o): - return o == .vertical ? CGSize(width: 14.9, height: 24.96) : CGSize(width: 24.96, height: 14.9) + guard let image = try? imageAttr.getAttachment(), + let spacer = try? spaceAttr.getAttachment() else { return } + + if position == .right { + attributedString.append(NSAttributedString(attachment: spacer)) + attributedString.append(NSAttributedString(attachment: image)) + } else { + attributedString.insert(NSAttributedString(attachment: image), at: 0) + attributedString.insert(NSAttributedString(attachment: spacer), at: 1) } } - } - - //------------------------------------------------------ - // MARK: - Initialization - //------------------------------------------------------ - - public override init(frame: CGRect) { - super.init(frame: frame) - } - - public convenience init(lineWidth: CGFloat) { - self.init(frame: .zero) - self.lineWidth = lineWidth - } - - required public init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - fatalError("CaretView xib not supported.") - } - - required public convenience init() { - self.init(frame: .zero) - } - - public convenience init(size: Size){ - let dimensions = size.dimensions() - self.init(frame: .init(x: 0, y: 0, width: dimensions.width, height: dimensions.height)) - self.size = size - } - - //------------------------------------------------------ - // MARK: - Setup - //------------------------------------------------------ - - override open func setup() { - super.setup() - defaultState() - isAccessibilityElement = true - accessibilityTraits = .link - } - - //------------------------------------------------------ - // MARK: - Drawing - //------------------------------------------------------ - - /// The direction the caret will be pointing to. - public enum Direction: Int { - case left - case right - case down - case up - } - - override func draw(_ rect: CGRect) { - super.draw(rect) - - caretPath.removeAllPoints() - caretPath.lineJoinStyle = .miter - caretPath.lineWidth = lineWidth - - let inset = lineWidth / 2 - let halfWidth = frame.size.width / 2 - let halfHeight = frame.size.height / 2 - - switch direction { - case .up: - caretPath.move(to: CGPoint(x: inset, y: frame.size.height - inset)) - caretPath.addLine(to: CGPoint(x: halfWidth, y: inset)) - caretPath.addLine(to: CGPoint(x: frame.size.width, y: frame.size.height)) - - case .right: - caretPath.move(to: CGPoint(x: inset, y: inset)) - caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: halfHeight)) - caretPath.addLine(to: CGPoint(x: inset, y: frame.size.height - inset)) - - case .down: - caretPath.move(to: CGPoint(x: inset, y: inset)) - caretPath.addLine(to: CGPoint(x: halfWidth, y: frame.size.height - inset)) - caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: inset)) - - case .left: - caretPath.move(to: CGPoint(x: frame.size.width - inset, y: inset)) - caretPath.addLine(to: CGPoint(x: inset, y: halfHeight)) - caretPath.addLine(to: CGPoint(x: frame.size.width - inset, y: frame.size.height - inset)) + func isEqual(_ equatable: ImageSpaceLabelAttribute) -> Bool { + return id == equatable.id && range == equatable.range } - - let color = colorConfiguration.getColor(self) - color.setStroke() - caretPath.stroke() - } - - override func updateView() { - setNeedsDisplay() - } - - //------------------------------------------------------ - // MARK: - Methods - //------------------------------------------------------ - public func setLineColor(_ color: UIColor) { - setNeedsDisplay() - } - - public func defaultState() { - isOpaque = false - isHidden = false - backgroundColor = .clear - } - - /// Ensure you have defined a CaretSize with Orientation before calling. - public func setConstraints() { - - guard let dimensions = size?.dimensions() else { return } - - heightAnchor.constraint(equalToConstant: dimensions.height).isActive = true - widthAnchor.constraint(equalToConstant: dimensions.width).isActive = true } } diff --git a/VDS/Components/Checkbox/Checkbox.swift b/VDS/Components/Checkbox/Checkbox.swift index f0d690cd..630b97ce 100644 --- a/VDS/Components/Checkbox/Checkbox.swift +++ b/VDS/Components/Checkbox/Checkbox.swift @@ -93,31 +93,31 @@ open class Checkbox: Control, Errorable, Changeable { $0.translatesAutoresizingMaskIntoConstraints = false } - open var isAnimated: Bool = true { didSet { didChange() }} + open var isAnimated: Bool = true { didSet { setNeedsUpdate() }} - open override var isSelected: Bool { didSet { didChange() }} + open override var isSelected: Bool { didSet { setNeedsUpdate() }} - open var labelText: String? { didSet { didChange() }} + open var labelText: String? { didSet { setNeedsUpdate() }} - open var labelTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }} + open var labelTextAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() }} open var labelAttributedText: NSAttributedString? { didSet { label.useAttributedText = !(labelAttributedText?.string.isEmpty ?? true) label.attributedText = labelAttributedText - didChange() + setNeedsUpdate() } } - open var childText: String? { didSet { didChange() }} + open var childText: String? { didSet { setNeedsUpdate() }} - open var childTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }} + open var childTextAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() }} open var childAttributedText: NSAttributedString? { didSet { childLabel.useAttributedText = !(childAttributedText?.string.isEmpty ?? true) childLabel.attributedText = childAttributedText - didChange() + setNeedsUpdate() } } @@ -127,7 +127,7 @@ open class Checkbox: Control, Errorable, Changeable { set { if !isSelected && _showError != newValue { _showError = newValue - didChange() + setNeedsUpdate() } } } @@ -142,11 +142,11 @@ open class Checkbox: Control, Errorable, Changeable { } } - open var errorText: String? { didSet { didChange() }} + open var errorText: String? { didSet { setNeedsUpdate() }} - open var inputId: String? { didSet { didChange() }} + open var inputId: String? { didSet { setNeedsUpdate() }} - open var value: AnyHashable? { didSet { didChange() }} + open var value: AnyHashable? { didSet { setNeedsUpdate() }} //-------------------------------------------------- // MARK: - Constraints @@ -252,6 +252,7 @@ open class Checkbox: Control, Errorable, Changeable { open override func reset() { super.reset() + shouldUpdateView = false label.reset() childLabel.reset() errorLabel.reset() @@ -272,7 +273,8 @@ open class Checkbox: Control, Errorable, Changeable { value = nil isSelected = false - updateSelector() + shouldUpdateView = true + setNeedsUpdate() } /// This will checkbox the state of the Selector and execute the actionBlock if provided. @@ -384,7 +386,7 @@ open class Checkbox: Control, Errorable, Changeable { shapeLayer?.removeAllAnimations() - if isAnimated { + if isAnimated && !disabled { let animateStrokeEnd = CABasicAnimation(keyPath: "strokeEnd") animateStrokeEnd.timingFunction = CAMediaTimingFunction(name: .linear) animateStrokeEnd.duration = 0.3 diff --git a/VDS/Components/Icon/Icon.swift b/VDS/Components/Icon/Icon.swift index e6443eaa..54fe428f 100644 --- a/VDS/Components/Icon/Icon.swift +++ b/VDS/Components/Icon/Icon.swift @@ -28,10 +28,10 @@ open class Icon: View { $0.clipsToBounds = true } - open var color: Color = .black { didSet { didChange() }} - open var size: Size = .medium { didSet { didChange() }} - open var name: Name? { didSet { didChange() }} - open var customSize: Int? { didSet { didChange() }} + open var color: Color = .black { didSet { setNeedsUpdate() }} + open var size: Size = .medium { didSet { setNeedsUpdate() }} + open var name: Name? { didSet { setNeedsUpdate() }} + open var customSize: Int? { didSet { setNeedsUpdate() }} //functions //-------------------------------------------------- diff --git a/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift b/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift index 5c395970..5d5baa8d 100644 --- a/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift +++ b/VDS/Components/Label/Attributes/TooltipLabelAttribute.swift @@ -72,7 +72,7 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable addHandler(on: attributedString) } - public init(id: UUID = UUID(), action: PassthroughSubject = PassthroughSubject(), subscriber: AnyCancellable? = nil, surface: Surface, accessibleText: String? = nil, closeButtonText: String, title: String, content: String) { + public init(id: UUID = UUID(), action: PassthroughSubject = PassthroughSubject(), subscriber: AnyCancellable? = nil, surface: Surface, accessibleText: String? = nil, closeButtonText: String = "Close", title: String, content: String) { self.id = id self.action = action self.subscriber = subscriber diff --git a/VDS/Components/Label/Label.swift b/VDS/Components/Label/Label.swift index 762e9daa..2bab6ec6 100644 --- a/VDS/Components/Label/Label.swift +++ b/VDS/Components/Label/Label.swift @@ -16,26 +16,26 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { //-------------------------------------------------- // MARK: - Combine Properties //-------------------------------------------------- - public var subject = PassthroughSubject() public var subscribers = Set() - public var hasChanged: Bool = false //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- private var initialSetupPerformed = false + open var shouldUpdateView: Bool = true + open var useAttributedText: Bool = false - open var surface: Surface = .light { didSet { didChange() }} + open var surface: Surface = .light { didSet { setNeedsUpdate() }} open var disabled: Bool = false { didSet { isEnabled = !disabled } } - open var attributes: [any LabelAttributeModel]? { didSet { didChange() }} + open var attributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() }} - open var textStyle: TextStyle = .defaultStyle { didSet { didChange() }} + open var textStyle: TextStyle = .defaultStyle { didSet { setNeedsUpdate() }} - open var textPosition: TextPosition = .left { didSet { didChange() }} + open var textPosition: TextPosition = .left { didSet { setNeedsUpdate() }} open var userInfo = [String: Primitive]() @@ -46,14 +46,14 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { disabled = !newValue } isUserInteractionEnabled = isEnabled - didChange() + setNeedsUpdate() } } override open var text: String? { didSet { attributes = nil - didChange() + setNeedsUpdate() } } @@ -95,14 +95,14 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { accessibilityCustomActions = [] accessibilityTraits = .staticText setup() - setupDidChangeEvent(true) - updateView() + setNeedsUpdate() } } open func setup() {} open func reset() { + shouldUpdateView = false surface = .light disabled = false attributes = nil @@ -112,6 +112,8 @@ open class Label: UILabel, Handlerable, ViewProtocol, Resettable, UserInfoable { attributedText = nil numberOfLines = 0 backgroundColor = .clear + shouldUpdateView = true + setNeedsUpdate() } //-------------------------------------------------- diff --git a/VDS/Components/Line/Line.swift b/VDS/Components/Line/Line.swift index a25a1497..2e74f9c4 100644 --- a/VDS/Components/Line/Line.swift +++ b/VDS/Components/Line/Line.swift @@ -26,7 +26,7 @@ open class Line: View { $0.translatesAutoresizingMaskIntoConstraints = false } - open var style: Style = .primary { didSet { didChange() } } + open var style: Style = .primary { didSet { setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Lifecycle diff --git a/VDS/Components/Notification/Notification.swift b/VDS/Components/Notification/Notification.swift index ce6f3260..eacb55d1 100644 --- a/VDS/Components/Notification/Notification.swift +++ b/VDS/Components/Notification/Notification.swift @@ -117,18 +117,18 @@ open class Notification: View { } //Text - open var title: String = "" { didSet{didChange()}} + open var title: String = "" { didSet{setNeedsUpdate()}} - open var subTitle: String? { didSet{didChange()}} + open var subTitle: String? { didSet{setNeedsUpdate()}} //Buttons - open var primaryButtonModel: ButtonModel? { didSet{didChange()}} + open var primaryButtonModel: ButtonModel? { didSet{setNeedsUpdate()}} open var primaryButton = Button().with { $0.size = .small $0.use = .secondary } - open var secondaryButtonModel: ButtonModel? { didSet{didChange()}} + open var secondaryButtonModel: ButtonModel? { didSet{setNeedsUpdate()}} open var secondaryButton = Button().with { $0.size = .small $0.use = .secondary @@ -152,11 +152,11 @@ open class Notification: View { // MARK: - Properties //-------------------------------------------------- - open var hideCloseButton: Bool = false { didSet{didChange()}} + open var hideCloseButton: Bool = false { didSet{setNeedsUpdate()}} - open var type: Style = .info { didSet{didChange()}} + open var type: Style = .info { didSet{setNeedsUpdate()}} - open var fullBleed: Bool = false { didSet {didChange()}} + open var fullBleed: Bool = false { didSet {setNeedsUpdate()}} var _layout: Layout = .vertical open var layout: Layout { @@ -164,7 +164,7 @@ open class Notification: View { if !UIDevice.isIPad, newValue == .horizontal { return } _layout = newValue buttonsView.buttonPosition = _layout == .horizontal ? .center : .left - didChange() + setNeedsUpdate() } get { _layout } } @@ -231,6 +231,8 @@ open class Notification: View { open override func reset() { super.reset() + shouldUpdateView = false + titleLabel.reset() titleLabel.text = "" titleLabel.textStyle = UIDevice.isIPad ? .boldBodyLarge : .boldBodySmall @@ -255,6 +257,9 @@ open class Notification: View { layout = .vertical hideCloseButton = false fullBleed = false + + shouldUpdateView = true + setNeedsUpdate() } //-------------------------------------------------- diff --git a/VDS/Components/RadioBox/RadioBox.swift b/VDS/Components/RadioBox/RadioBox.swift index f5f8d9b9..f27a8809 100644 --- a/VDS/Components/RadioBox/RadioBox.swift +++ b/VDS/Components/RadioBox/RadioBox.swift @@ -86,47 +86,47 @@ open class RadioBox: Control, Changeable { $0.translatesAutoresizingMaskIntoConstraints = false } - open var text: String = "Default Text" { didSet { didChange() }} + open var text: String = "Default Text" { didSet { setNeedsUpdate() }} - open var textAttributes: [any LabelAttributeModel]? { didSet { didChange() }} + open var textAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() }} open var textAttributedText: NSAttributedString? { didSet { textLabel.useAttributedText = !(textAttributedText?.string.isEmpty ?? true) textLabel.attributedText = textAttributedText - didChange() + setNeedsUpdate() } } - open var subText: String? { didSet { didChange() }} + open var subText: String? { didSet { setNeedsUpdate() }} - open var subTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }} + open var subTextAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() }} open var subTextAttributedText: NSAttributedString? { didSet { subTextLabel.useAttributedText = !(subTextAttributedText?.string.isEmpty ?? true) subTextLabel.attributedText = subTextAttributedText - didChange() + setNeedsUpdate() } } - open var subTextRight: String? { didSet { didChange() }} + open var subTextRight: String? { didSet { setNeedsUpdate() }} - open var subTextRightAttributes: [any LabelAttributeModel]? { didSet { didChange() }} + open var subTextRightAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() }} open var subTextRightAttributedText: NSAttributedString? { didSet { subTextRightLabel.useAttributedText = !(subTextRightAttributedText?.string.isEmpty ?? true) subTextRightLabel.attributedText = subTextRightAttributedText - didChange() + setNeedsUpdate() } } - open var strikethrough: Bool = false { didSet { didChange() }} + open var strikethrough: Bool = false { didSet { setNeedsUpdate() }} - open var inputId: String? { didSet { didChange() }} + open var inputId: String? { didSet { setNeedsUpdate() }} - open var value: AnyHashable? { didSet { didChange() }} + open var value: AnyHashable? { didSet { setNeedsUpdate() }} //functions //-------------------------------------------------- @@ -204,6 +204,7 @@ open class RadioBox: Control, Changeable { open override func reset() { super.reset() + shouldUpdateView = false textLabel.reset() subTextLabel.reset() subTextRightLabel.reset() @@ -227,7 +228,8 @@ open class RadioBox: Control, Changeable { isSelected = false - updateSelector() + shouldUpdateView = true + setNeedsUpdate() } /// This will radioBox the state of the Selector and execute the actionBlock if provided. diff --git a/VDS/Components/RadioButton/RadioButton.swift b/VDS/Components/RadioButton/RadioButton.swift index b3b35f3e..705af90e 100644 --- a/VDS/Components/RadioButton/RadioButton.swift +++ b/VDS/Components/RadioButton/RadioButton.swift @@ -92,31 +92,31 @@ open class RadioButton: Control, Errorable, Changeable { $0.translatesAutoresizingMaskIntoConstraints = false } - open var labelText: String? { didSet { didChange() }} + open var labelText: String? { didSet { setNeedsUpdate() }} - open var labelTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }} + open var labelTextAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() }} open var labelAttributedText: NSAttributedString? { didSet { label.useAttributedText = !(labelAttributedText?.string.isEmpty ?? true) label.attributedText = labelAttributedText - didChange() + setNeedsUpdate() } } - open var childText: String? { didSet { didChange() }} + open var childText: String? { didSet { setNeedsUpdate() }} - open var childTextAttributes: [any LabelAttributeModel]? { didSet { didChange() }} + open var childTextAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() }} open var childAttributedText: NSAttributedString? { didSet { childLabel.useAttributedText = !(childAttributedText?.string.isEmpty ?? true) childLabel.attributedText = childAttributedText - didChange() + setNeedsUpdate() } } - open var showError: Bool = false { didSet { didChange() }} + open var showError: Bool = false { didSet { setNeedsUpdate() }} open override var state: UIControl.State { get { @@ -128,11 +128,11 @@ open class RadioButton: Control, Errorable, Changeable { } } - open var errorText: String? { didSet { didChange() }} + open var errorText: String? { didSet { setNeedsUpdate() }} - open var inputId: String? { didSet { didChange() }} + open var inputId: String? { didSet { setNeedsUpdate() }} - open var value: AnyHashable? { didSet { didChange() }} + open var value: AnyHashable? { didSet { setNeedsUpdate() }} //-------------------------------------------------- // MARK: - Constraints @@ -240,6 +240,7 @@ open class RadioButton: Control, Errorable, Changeable { open override func reset() { super.reset() + shouldUpdateView = false label.reset() childLabel.reset() errorLabel.reset() @@ -261,7 +262,9 @@ open class RadioButton: Control, Errorable, Changeable { isSelected = false - updateSelector() + shouldUpdateView = true + setNeedsUpdate() + } /// This will checkbox the state of the Selector and execute the actionBlock if provided. diff --git a/VDS/Components/RadioSwatch/RadioSwatch.swift b/VDS/Components/RadioSwatch/RadioSwatch.swift index 211a3ff5..8178f971 100644 --- a/VDS/Components/RadioSwatch/RadioSwatch.swift +++ b/VDS/Components/RadioSwatch/RadioSwatch.swift @@ -41,19 +41,19 @@ open class RadioSwatch: Control { $0.contentMode = .scaleAspectFit } - open var fillImage: UIImage? { didSet { didChange() }} + open var fillImage: UIImage? { didSet { setNeedsUpdate() }} - open var text: String = "" { didSet { didChange() }} + open var text: String = "" { didSet { setNeedsUpdate() }} - open var primaryColor: UIColor? { didSet { didChange() }} + open var primaryColor: UIColor? { didSet { setNeedsUpdate() }} - open var secondaryColor: UIColor? { didSet { didChange() }} + open var secondaryColor: UIColor? { didSet { setNeedsUpdate() }} - open var strikethrough: Bool = false { didSet { didChange() }} + open var strikethrough: Bool = false { didSet { setNeedsUpdate() }} - open var inputId: String? { didSet { didChange() }} + open var inputId: String? { didSet { setNeedsUpdate() }} - open var value: AnyHashable? { didSet { didChange() }} + open var value: AnyHashable? { didSet { setNeedsUpdate() }} //functions //-------------------------------------------------- @@ -91,7 +91,7 @@ open class RadioSwatch: Control { open override func reset() { super.reset() - + shouldUpdateView = false fillImage = nil text = "" primaryColor = nil @@ -99,7 +99,8 @@ open class RadioSwatch: Control { strikethrough = false inputId = nil value = nil - + shouldUpdateView = true + setNeedsUpdate() setNeedsDisplay() } diff --git a/VDS/Components/RadioSwatch/RadioSwatchGroup.swift b/VDS/Components/RadioSwatch/RadioSwatchGroup.swift index 0a5b15da..e5211163 100644 --- a/VDS/Components/RadioSwatch/RadioSwatchGroup.swift +++ b/VDS/Components/RadioSwatch/RadioSwatchGroup.swift @@ -183,7 +183,7 @@ open class RadioSwatchGroup: SelectorGroupSelectedHandlerBase, UICo selectedHandler?.toggle() selector.toggle() label.text = selector.text - didChange() + setNeedsUpdate() valueChanged() } } diff --git a/VDS/Components/TextFields/EntryField/EntryField.swift b/VDS/Components/TextFields/EntryField/EntryField.swift index ad31d7c2..b57b733b 100644 --- a/VDS/Components/TextFields/EntryField/EntryField.swift +++ b/VDS/Components/TextFields/EntryField/EntryField.swift @@ -106,11 +106,10 @@ open class EntryField: Control, Changeable { } } - open var titleLabel = TrailingTooltipLabel().with { + open var titleLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) - $0.labelTextPosition = .left - $0.labelTextStyle = .bodySmall - $0.tooltipYOffset = -2 + $0.textPosition = .left + $0.textStyle = .bodySmall } open var errorLabel = Label().with { @@ -130,11 +129,11 @@ open class EntryField: Control, Changeable { $0.size = .small } - open var labelText: String? { didSet { didChange() }} + open var labelText: String? { didSet { setNeedsUpdate() }} - open var helperText: String? { didSet { didChange() }} + open var helperText: String? { didSet { setNeedsUpdate() }} - open var showError: Bool = false { didSet { didChange() }} + open var showError: Bool = false { didSet { setNeedsUpdate() }} open override var state: UIControl.State { get { @@ -146,27 +145,27 @@ open class EntryField: Control, Changeable { } } - open var errorText: String? { didSet { didChange() }} + open var errorText: String? { didSet { setNeedsUpdate() }} - open var tooltipTitle: String? { didSet { didChange() }} + open var tooltipTitle: String? { didSet { setNeedsUpdate() }} - open var tooltipContent: String? { didSet { didChange() }} + open var tooltipContent: String? { didSet { setNeedsUpdate() }} - open var transparentBackground: Bool = false { didSet { didChange() }} + open var transparentBackground: Bool = false { didSet { setNeedsUpdate() }} - open var width: CGFloat? { didSet { didChange() }} + open var width: CGFloat? { didSet { setNeedsUpdate() }} - open var maxLength: Int? { didSet { didChange() }} + open var maxLength: Int? { didSet { setNeedsUpdate() }} - open var inputId: String? { didSet { didChange() }} + open var inputId: String? { didSet { setNeedsUpdate() }} - open var value: AnyHashable? { didSet { didChange() }} + open var value: AnyHashable? { didSet { setNeedsUpdate() }} - open var defaultValue: AnyHashable? { didSet { didChange() }} + open var defaultValue: AnyHashable? { didSet { setNeedsUpdate() }} - open var required: Bool = false { didSet { didChange() }} + open var required: Bool = false { didSet { setNeedsUpdate() }} - open var readOnly: Bool = false { didSet { didChange() }} + open var readOnly: Bool = false { didSet { setNeedsUpdate() }} //-------------------------------------------------- // MARK: - Constraints @@ -234,8 +233,8 @@ open class EntryField: Control, Changeable { errorLabel.reset() helperLabel.reset() - titleLabel.labelTextPosition = .left - titleLabel.labelTextStyle = .bodySmall + titleLabel.textPosition = .left + titleLabel.textStyle = .bodySmall errorLabel.textPosition = .left errorLabel.textStyle = .bodySmall helperLabel.textPosition = .left @@ -283,19 +282,23 @@ open class EntryField: Control, Changeable { //dealing with the "Optional" addition to the text if let oldText = updatedLabelText, !required, !oldText.hasSuffix("Optional") { - let optionColorAttr = ColorLabelAttribute(location: oldText.count + 2, - length: 8, - color: .red) - - updatedLabelText = "\(oldText) Optional" - attributes.append(optionColorAttr) - } + if !disabled { + let optionColorAttr = ColorLabelAttribute(location: oldText.count + 2, + length: 8, + color: VDSColor.elementsSecondaryOnlight) + attributes.append(optionColorAttr) + } + updatedLabelText = "\(oldText) Optional" + } + + if let tooltipTitle, let tooltipContent { + attributes.append(TooltipLabelAttribute(surface: surface, title: tooltipTitle, content: tooltipContent)) + } + //set the titleLabel - titleLabel.labelText = updatedLabelText - titleLabel.labelAttributes = attributes - titleLabel.tooltipTitle = tooltipTitle ?? "" - titleLabel.tooltipContent = tooltipContent ?? "" + titleLabel.text = updatedLabelText + titleLabel.attributes = attributes titleLabel.surface = surface titleLabel.disabled = disabled diff --git a/VDS/Components/TextFields/InputField/InputField.swift b/VDS/Components/TextFields/InputField/InputField.swift index 9832810f..2257ca89 100644 --- a/VDS/Components/TextFields/InputField/InputField.swift +++ b/VDS/Components/TextFields/InputField/InputField.swift @@ -51,7 +51,7 @@ open class InputField: EntryField, UITextFieldDelegate { // MARK: - Public Properties //-------------------------------------------------- - open var type: FieldType = .text { didSet { didChange() }} + open var type: FieldType = .text { didSet { setNeedsUpdate() }} var _showError: Bool = false open override var showError: Bool { @@ -59,7 +59,7 @@ open class InputField: EntryField, UITextFieldDelegate { set { if !showSuccess && _showError != newValue { _showError = newValue - didChange() + setNeedsUpdate() } } } @@ -70,7 +70,7 @@ open class InputField: EntryField, UITextFieldDelegate { set { if !showError && _showSuccess != newValue { _showSuccess = newValue - didChange() + setNeedsUpdate() } } } @@ -85,9 +85,9 @@ open class InputField: EntryField, UITextFieldDelegate { } } - open var successText: String? { didSet { didChange() }} + open var successText: String? { didSet { setNeedsUpdate() }} - open var helperTextPlacement: HelperTextPlacement = .bottom { didSet { didChange() }} + open var helperTextPlacement: HelperTextPlacement = .bottom { didSet { setNeedsUpdate() }} //-------------------------------------------------- // MARK: - Private Properties diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index 79928baf..f5d284a1 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -77,7 +77,7 @@ open class TileContainer: Control { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- - public var backgroundImage: UIImage? { didSet{ didChange() } } + public var backgroundImage: UIImage? { didSet{ setNeedsUpdate() } } public var containerView = View().with { $0.isUserInteractionEnabled = false @@ -87,13 +87,13 @@ open class TileContainer: Control { $0.isUserInteractionEnabled = false } - public var color: BackgroundColor = .white { didSet{ didChange() } } + public var color: BackgroundColor = .white { didSet{ setNeedsUpdate() } } - public var padding: Padding = .padding4X { didSet{ didChange() } } + public var padding: Padding = .padding4X { didSet{ setNeedsUpdate() } } - public var aspectRatio: AspectRatio = .ratio1x1 { didSet{ didChange() } } + public var aspectRatio: AspectRatio = .ratio1x1 { didSet{ setNeedsUpdate() } } - public var imageFallbackColor: Surface = .light { didSet{ didChange() } } + public var imageFallbackColor: Surface = .light { didSet{ setNeedsUpdate() } } private var _width: CGFloat? public var width: CGFloat? { @@ -104,7 +104,7 @@ open class TileContainer: Control { } else { _width = nil } - didChange() + setNeedsUpdate() } } @@ -117,13 +117,13 @@ open class TileContainer: Control { } else { _height = nil } - didChange() + setNeedsUpdate() } } - public var showBorder: Bool = false { didSet{ didChange() } } + public var showBorder: Bool = false { didSet{ setNeedsUpdate() } } - public var showDropShadows: Bool = false { didSet{ didChange() } } + public var showDropShadows: Bool = false { didSet{ setNeedsUpdate() } } //-------------------------------------------------- // MARK: - Private Properties @@ -191,7 +191,17 @@ open class TileContainer: Control { open override func reset() { super.reset() - + shouldUpdateView = false + color = .white + padding = .padding4X + aspectRatio = .ratio1x1 + imageFallbackColor = .light + width = nil + height = nil + showBorder = false + showDropShadows = false + shouldUpdateView = true + setNeedsUpdate() } //-------------------------------------------------- diff --git a/VDS/Components/Tilelet/Tilelet.swift b/VDS/Components/Tilelet/Tilelet.swift index c6d36858..99bc633b 100644 --- a/VDS/Components/Tilelet/Tilelet.swift +++ b/VDS/Components/Tilelet/Tilelet.swift @@ -153,7 +153,7 @@ open class Tilelet: TileContainer { } else { _textWidth = nil } - didChange() + setNeedsUpdate() } } @@ -170,16 +170,16 @@ open class Tilelet: TileContainer { } else { _textPercentage = nil } - didChange() + setNeedsUpdate() } } - open var textPostion: TextPosition = .top { didSet { didChange() }} + open var textPostion: TextPosition = .top { didSet { setNeedsUpdate() }} //models - public var badgeModel: BadgeModel? { didSet { didChange() }} - public var titleModel: TitleModel? { didSet { didChange() }} - public var subTitleModel: SubTitleModel? { didSet { didChange() }} + public var badgeModel: BadgeModel? { didSet { setNeedsUpdate() }} + public var titleModel: TitleModel? { didSet { setNeedsUpdate() }} + public var subTitleModel: SubTitleModel? { didSet { setNeedsUpdate() }} //only 1 Icon can be active private var _descriptiveIconModel: DescriptiveIcon? @@ -188,7 +188,7 @@ open class Tilelet: TileContainer { set { _descriptiveIconModel = newValue; _directionalIconModel = nil - didChange() + setNeedsUpdate() } } @@ -198,7 +198,7 @@ open class Tilelet: TileContainer { set { _directionalIconModel = newValue; _descriptiveIconModel = nil - didChange() + setNeedsUpdate() } } //icons @@ -256,15 +256,17 @@ open class Tilelet: TileContainer { } open override func reset() { + shouldUpdateView = false aspectRatio = .none color = .black - //models badgeModel = nil titleModel = nil subTitleModel = nil descriptiveIconModel = nil - directionalIconModel = nil + directionalIconModel = nil + shouldUpdateView = true + setNeedsUpdate() } //-------------------------------------------------- diff --git a/VDS/Components/TitleLockup/TitleLockup.swift b/VDS/Components/TitleLockup/TitleLockup.swift index ff48c217..b64132f4 100644 --- a/VDS/Components/TitleLockup/TitleLockup.swift +++ b/VDS/Components/TitleLockup/TitleLockup.swift @@ -173,28 +173,32 @@ open class TitleLockup: View { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- - open var textPosition: TextPosition = .left { didSet { didChange() }} + open var textPosition: TextPosition = .left { didSet { setNeedsUpdate() }} //style - open var otherTextStyle: OtherTextStyle = UIDevice.isIPad ? .bodyLarge : .bodyMedium { didSet { didChange() }} + open var otherTextStyle: OtherTextStyle = UIDevice.isIPad ? .bodyLarge : .bodyMedium { + didSet { + setNeedsUpdate() + } + } //first row open var eyebrowLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) } - open var eyebrowModel: EyebrowModel? { didSet { didChange() }} + open var eyebrowModel: EyebrowModel? { didSet { setNeedsUpdate() }} //second row open var titleLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) } - open var titleModel: TitleModel? { didSet { didChange() }} + open var titleModel: TitleModel? { didSet { setNeedsUpdate() }} //third row open var subTitleLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) } - open var subTitleModel: SubTitleModel? { didSet { didChange() }} + open var subTitleModel: SubTitleModel? { didSet { setNeedsUpdate() }} //-------------------------------------------------- // MARK: - Lifecycle @@ -218,17 +222,14 @@ open class TitleLockup: View { open override func reset() { super.reset() - titleLabel.reset() - eyebrowLabel.reset() - subTitleLabel.reset() - + shouldUpdateView = false textPosition = .left - eyebrowModel = nil titleModel = nil subTitleModel = nil - otherTextStyle = .bodyLarge - + otherTextStyle = UIDevice.isIPad ? .bodyLarge : .bodyMedium + shouldUpdateView = true + setNeedsUpdate() } //-------------------------------------------------- @@ -241,7 +242,7 @@ open class TitleLockup: View { var eyebrowTextIsEmpty = true var titleTextIsEmpty = true var subTitleTextIsEmpty = true - + if let eyebrowModel, !eyebrowModel.text.isEmpty { eyebrowTextIsEmpty = false eyebrowLabel.textPosition = allLabelsTextPosition diff --git a/VDS/Components/Toggle/Toggle.swift b/VDS/Components/Toggle/Toggle.swift index cd3e3e4e..634fc192 100644 --- a/VDS/Components/Toggle/Toggle.swift +++ b/VDS/Components/Toggle/Toggle.swift @@ -125,27 +125,27 @@ open class Toggle: Control, Changeable { if isSelected != newValue { isSelected = newValue } - didChange() + setNeedsUpdate() } } - open var isAnimated: Bool = true { didSet { didChange() }} + open var isAnimated: Bool = true { didSet { setNeedsUpdate() }} - open var showText: Bool = false { didSet { didChange() }} + open var showText: Bool = false { didSet { setNeedsUpdate() }} - open var onText: String = "On" { didSet { didChange() }} + open var onText: String = "On" { didSet { setNeedsUpdate() }} - open var offText: String = "Off" { didSet { didChange() }} + open var offText: String = "Off" { didSet { setNeedsUpdate() }} - open var textSize: TextSize = .small { didSet { didChange() }} + open var textSize: TextSize = .small { didSet { setNeedsUpdate() }} - open var textWeight: TextWeight = .regular { didSet { didChange() }} + open var textWeight: TextWeight = .regular { didSet { setNeedsUpdate() }} - open var textPosition: TextPosition = .left { didSet { didChange() }} + open var textPosition: TextPosition = .left { didSet { setNeedsUpdate() }} - open var inputId: String? { didSet { didChange() }} + open var inputId: String? { didSet { setNeedsUpdate() }} - open var value: AnyHashable? { didSet { didChange() }} + open var value: AnyHashable? { didSet { setNeedsUpdate() }} //-------------------------------------------------- // MARK: - Constraints @@ -272,9 +272,9 @@ open class Toggle: Control, Changeable { open override func reset() { super.reset() + shouldUpdateView = false label.reset() isOn = false - isAnimated = true showText = false onText = "On" @@ -284,9 +284,10 @@ open class Toggle: Control, Changeable { textPosition = .left inputId = nil value = nil - toggleView.backgroundColor = toggleColorConfiguration.getColor(self) knobView.backgroundColor = knobColorConfiguration.getColor(self) + shouldUpdateView = true + setNeedsUpdate() } /// This will toggle the state of the Toggle and execute the actionBlock if provided. diff --git a/VDS/Components/Tooltip/Tooltip.swift b/VDS/Components/Tooltip/Tooltip.swift index 316c87bb..2ebb5247 100644 --- a/VDS/Components/Tooltip/Tooltip.swift +++ b/VDS/Components/Tooltip/Tooltip.swift @@ -44,15 +44,15 @@ open class Tooltip: Control, TooltipLaunchable { $0.clipsToBounds = true } - open var closeButtonText: String = "Close" { didSet { didChange() }} + open var closeButtonText: String = "Close" { didSet { setNeedsUpdate() }} - open var fillColor: FillColor = .primary { didSet { didChange() }} + open var fillColor: FillColor = .primary { didSet { setNeedsUpdate() }} - open var size: Size = .medium { didSet { didChange() }} + open var size: Size = .medium { didSet { setNeedsUpdate() }} - open var title: String = "" { didSet { didChange() }} + open var title: String = "" { didSet { setNeedsUpdate() }} - open var content: String = "" { didSet { didChange() }} + open var content: String = "" { didSet { setNeedsUpdate() }} //-------------------------------------------------- // MARK: - Configuration @@ -138,12 +138,15 @@ open class Tooltip: Control, TooltipLaunchable { open override func reset() { super.reset() + shouldUpdateView = false size = .medium title = "" content = "" fillColor = .primary closeButtonText = "Close" imageView.image = nil + shouldUpdateView = true + setNeedsUpdate() } open override func updateView() { diff --git a/VDS/Components/Tooltip/TrailingTooltipLabel.swift b/VDS/Components/Tooltip/TrailingTooltipLabel.swift index 5441d6eb..9c7fc628 100644 --- a/VDS/Components/Tooltip/TrailingTooltipLabel.swift +++ b/VDS/Components/Tooltip/TrailingTooltipLabel.swift @@ -22,25 +22,24 @@ open class TrailingTooltipLabel: View, TooltipLaunchable { //-------------------------------------------------- open var label = Label() - open var labelText: String? { didSet { didChange() }} + open var labelText: String? { didSet { setNeedsUpdate() }} - open var labelAttributes: [any LabelAttributeModel]? { didSet { didChange() } } + open var labelAttributes: [any LabelAttributeModel]? { didSet { setNeedsUpdate() } } - open var labelTextStyle: TextStyle = .defaultStyle { didSet { didChange() } } + open var labelTextStyle: TextStyle = .defaultStyle { didSet { setNeedsUpdate() } } - open var labelTextPosition: TextPosition = .left { didSet { didChange() } } + open var labelTextPosition: TextPosition = .left { didSet { setNeedsUpdate() } } public lazy var textColorConfiguration: AnyColorable = { label.textColorConfiguration - }() { didSet { didChange() }} + }() { didSet { setNeedsUpdate() }} - open var tooltipCloseButtonText: String = "Close" { didSet { didChange() } } + open var tooltipCloseButtonText: String = "Close" { didSet { setNeedsUpdate() } } - open var tooltipTitle: String = "" { didSet { didChange() } } + open var tooltipTitle: String = "" { didSet { setNeedsUpdate() } } - open var tooltipContent: String = "" { didSet { didChange() } } + open var tooltipContent: String = "" { didSet { setNeedsUpdate() } } - open var tooltipYOffset: CGFloat = 0 { didSet { didChange() } } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- @@ -75,6 +74,20 @@ open class TrailingTooltipLabel: View, TooltipLaunchable { label.addTooltip(model: .init(surface: surface, closeButtonText: tooltipCloseButtonText, title: tooltipTitle, content: tooltipContent)) } } + + open override func reset() { + super.reset() + shouldUpdateView = false + labelText = nil + labelAttributes = nil + labelTextStyle = .defaultStyle + labelTextPosition = .left + tooltipCloseButtonText = "Close" + tooltipTitle = "" + tooltipContent = "" + shouldUpdateView = true + setNeedsUpdate() + } } diff --git a/VDS/Protocols/Handlerable.swift b/VDS/Protocols/Handlerable.swift index cefb150f..14246735 100644 --- a/VDS/Protocols/Handlerable.swift +++ b/VDS/Protocols/Handlerable.swift @@ -10,38 +10,21 @@ import Combine import UIKit public protocol Handlerable: AnyObject, Initable, Disabling, Surfaceable { - var subject: PassthroughSubject { get set } var subscribers: Set { get set } + var shouldUpdateView: Bool { get set } func updateView() } extension Handlerable { - - public func setupDidChangeEvent(_ debounce: Bool = false) { - handlerPublisher(debounce) - .sink { [weak self] _ in - self?.updateView() - }.store(in: &subscribers) - } - - public func handlerPublisher(_ debounce: Bool = false) -> AnyPublisher { - if debounce { - return subject - .debounce(for: .seconds(Constants.StateDebounce), scheduler: RunLoop.main) - .eraseToAnyPublisher() - } else { - return subject - .eraseToAnyPublisher() + public func setNeedsUpdate() { + if shouldUpdateView { + shouldUpdateView = false + updateView() + shouldUpdateView = true } } } -extension Handlerable where Self: UIView { - public func didChange() { - subject.send() - } -} - extension Handlerable where Self: UIControl { public func addEvent(event: UIControl.Event, block: @escaping (Self)->()) { publisher(for: event)