From 3ec2c984258b09d1e2552b238fb0323d364f4d58 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Wed, 31 Jul 2024 12:26:39 -0500 Subject: [PATCH 1/6] setup contentHugging and added more contraints to toggle when not in label mode Signed-off-by: Matt Bruce --- VDS/Components/Toggle/Toggle.swift | 61 +++++++++++++++++++++++------- 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/VDS/Components/Toggle/Toggle.swift b/VDS/Components/Toggle/Toggle.swift index 7cb4ef71..c147739b 100644 --- a/VDS/Components/Toggle/Toggle.swift +++ b/VDS/Components/Toggle/Toggle.swift @@ -55,6 +55,7 @@ open class Toggle: Control, Changeable, FormFieldable { private var leftConstraints: [NSLayoutConstraint] = [] private var rightConstraints: [NSLayoutConstraint] = [] private var labelConstraints: [NSLayoutConstraint] = [] + private var toggleConstraints: [NSLayoutConstraint] = [] //-------------------------------------------------- // MARK: - Configuration @@ -95,7 +96,7 @@ open class Toggle: Control, Changeable, FormFieldable { open var toggleView = ToggleView().with { $0.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) $0.isUserInteractionEnabled = false - $0.isAccessibilityElement = false + $0.isAccessibilityElement = false } /// Used in showing the on/off text. @@ -148,18 +149,6 @@ open class Toggle: Control, Changeable, FormFieldable { open var value: AnyHashable? { isOn } - /// The natural size for the receiving view, considering only properties of the view itself. - open override var intrinsicContentSize: CGSize { - if showLabel { - label.sizeToFit() - let size = CGSize(width: label.frame.width + spacingBetween + toggleContainerSize.width, - height: max(toggleContainerSize.height, label.frame.height)) - return size - } else { - return toggleContainerSize - } - } - open override var shouldHighlight: Bool { false } //-------------------------------------------------- @@ -208,6 +197,49 @@ open class Toggle: Control, Changeable, FormFieldable { label.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor) ] + // Set content hugging priority + setContentHuggingPriority(.required, for: .horizontal) + + isAccessibilityElement = true + if #available(iOS 17.0, *) { + accessibilityTraits = .toggleButton + } else { + accessibilityTraits = .button + } + addSubview(label) + addSubview(toggleView) + + // Set up initial constraints for label and switch + toggleView.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true + + //toggle + toggleConstraints = [ + toggleView.leadingAnchor.constraint(greaterThanOrEqualTo: leadingAnchor), + toggleView.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor) + ] + + //toggle and label variants + labelConstraints = [ + height(constant: toggleContainerSize.height, priority: .defaultLow), + heightGreaterThanEqualTo(constant: toggleContainerSize.height, priority: .defaultHigh), + label.topAnchor.constraint(equalTo: topAnchor), + label.bottomAnchor.constraint(equalTo: bottomAnchor), + ] + + //label-toggle + leftConstraints = [ + label.leadingAnchor.constraint(equalTo: leadingAnchor), + toggleView.leadingAnchor.constraint(equalTo: label.trailingAnchor, constant: spacingBetween), + toggleView.trailingAnchor.constraint(equalTo: trailingAnchor) + ] + + //toggle-label + rightConstraints = [ + toggleView.leadingAnchor.constraint(equalTo: leadingAnchor), + label.leadingAnchor.constraint(equalTo: toggleView.trailingAnchor, constant: spacingBetween), + label.trailingAnchor.constraint(equalTo: trailingAnchor) + ] + bridge_accessibilityValueBlock = { [weak self] in guard let self else { return "" } if showText { @@ -261,6 +293,8 @@ open class Toggle: Control, Changeable, FormFieldable { label.isHidden = !showLabel if showLabel { + NSLayoutConstraint.deactivate(toggleConstraints) + label.textAlignment = textPosition == .left ? .right : .left label.textStyle = textStyle label.text = statusText @@ -279,6 +313,7 @@ open class Toggle: Control, Changeable, FormFieldable { NSLayoutConstraint.deactivate(leftConstraints) NSLayoutConstraint.deactivate(rightConstraints) NSLayoutConstraint.deactivate(labelConstraints) + NSLayoutConstraint.activate(toggleConstraints) } } } From 0d38eb495b2134833cb16d6fb21a69348dc481f4 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 1 Aug 2024 08:00:13 -0500 Subject: [PATCH 2/6] ensure that the horizontal plane of a label that has characters doesn't get crushed. Signed-off-by: Matt Bruce --- VDS/BaseClasses/Selector/SelectorItemBase.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/VDS/BaseClasses/Selector/SelectorItemBase.swift b/VDS/BaseClasses/Selector/SelectorItemBase.swift index 44ed01b1..a15fbfbd 100644 --- a/VDS/BaseClasses/Selector/SelectorItemBase.swift +++ b/VDS/BaseClasses/Selector/SelectorItemBase.swift @@ -66,18 +66,21 @@ open class SelectorItemBase: Control, Errorable, Changea /// Label used to render labelText. open var label = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) + $0.setContentCompressionResistancePriority(.required, for: .horizontal) $0.textStyle = .boldBodyLarge } /// Label used to render childText. open var childLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) + $0.setContentCompressionResistancePriority(.required, for: .horizontal) $0.textStyle = .bodyLarge } /// Label used to render errorText. open var errorLabel = Label().with { $0.setContentCompressionResistancePriority(.required, for: .vertical) + $0.setContentCompressionResistancePriority(.required, for: .horizontal) $0.textStyle = .bodyMedium } From 1e9aa876dbe77b303c6cd5fc8024da5b9a0d1de3 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 8 Aug 2024 10:59:30 -0500 Subject: [PATCH 3/6] refactor for use in both UIControl and UIView Signed-off-by: Matt Bruce --- VDS/Protocols/Clickable.swift | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/VDS/Protocols/Clickable.swift b/VDS/Protocols/Clickable.swift index c2fc9056..fe6ff10d 100644 --- a/VDS/Protocols/Clickable.swift +++ b/VDS/Protocols/Clickable.swift @@ -9,12 +9,12 @@ import Foundation import UIKit import Combine -public protocol Clickable: ViewProtocol where Self: UIControl { +public protocol Clickable: ViewProtocol { /// Sets the primary Subscriber used for the UIControl event .touchUpInside. var onClickSubscriber: AnyCancellable? { get set } } -extension Clickable { +extension Clickable where Self: UIControl { /// Allows the setting of a completion block against the onClickSubscriber cancellable. This will /// completion block will get executed against the UIControl publisher for the 'touchUpInside' action. public var onClick: ((Self) -> ())? { @@ -23,7 +23,7 @@ extension Clickable { onClickSubscriber?.cancel() if let newValue { onClickSubscriber = publisher(for: .touchUpInside) - .sink { [weak self] c in + .sink { [weak self] c in guard let self, self.isEnabled else { return } newValue(c) } @@ -34,3 +34,24 @@ extension Clickable { } } } + +extension Clickable where Self: UIView { + /// Allows the setting of a completion block against the onClickSubscriber cancellable. This will + /// completion block will get executed against the UIControl publisher for the 'touchUpInside' action. + public var onClick: ((Self) -> ())? { + get { return nil } + set { + onClickSubscriber?.cancel() + if let newValue { + onClickSubscriber = publisher(for: UITapGestureRecognizer()) + .sink { [weak self] _ in + guard let self, self.isEnabled else { return } + newValue(self) + } + } else { + onClickSubscriber = nil + } + setNeedsUpdate() + } + } +} From 7e36bc074a04235f3c67f6b5955b828a0b941e2e Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 8 Aug 2024 10:59:45 -0500 Subject: [PATCH 4/6] added clickable Signed-off-by: Matt Bruce --- VDS/BaseClasses/View.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/VDS/BaseClasses/View.swift b/VDS/BaseClasses/View.swift index 88996ba6..8012c5aa 100644 --- a/VDS/BaseClasses/View.swift +++ b/VDS/BaseClasses/View.swift @@ -12,7 +12,7 @@ import Combine /// Base Class used to build Views. @objcMembers @objc(VDSView) -open class View: UIView, ViewProtocol, UserInfoable { +open class View: UIView, ViewProtocol, UserInfoable, Clickable { //-------------------------------------------------- // MARK: - Initializers @@ -37,6 +37,7 @@ open class View: UIView, ViewProtocol, UserInfoable { //-------------------------------------------------- open var subscribers = Set() + open var onClickSubscriber: AnyCancellable? //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- From e0d769ccf3cb332c821fec3c9ab2deb87906d1f7 Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 8 Aug 2024 11:00:02 -0500 Subject: [PATCH 5/6] refactored to now use the onClick Signed-off-by: Matt Bruce --- VDS/Components/DatePicker/DatePicker.swift | 13 +++++-------- VDS/Components/DropdownSelect/DropdownSelect.swift | 9 +++------ 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/VDS/Components/DatePicker/DatePicker.swift b/VDS/Components/DatePicker/DatePicker.swift index df7cc7c9..d65465b7 100644 --- a/VDS/Components/DatePicker/DatePicker.swift +++ b/VDS/Components/DatePicker/DatePicker.swift @@ -154,15 +154,12 @@ open class DatePicker: EntryFieldBase { selectedDateLabel.textColorConfiguration = primaryColorConfiguration.eraseToAnyColorable() // tap gesture - containerView - .publisher(for: UITapGestureRecognizer()) - .sink { [weak self] _ in - guard let self else { return } - if isEnabled && !isReadOnly { - showPopover() - } + containerView.onClick = { [weak self] _ in + guard let self else { return } + if isEnabled && !isReadOnly { + showPopover() } - .store(in: &subscribers) + } NotificationCenter.default .publisher(for: UIDevice.orientationDidChangeNotification).sink { [weak self] _ in diff --git a/VDS/Components/DropdownSelect/DropdownSelect.swift b/VDS/Components/DropdownSelect/DropdownSelect.swift index 947210f4..f0dc8084 100644 --- a/VDS/Components/DropdownSelect/DropdownSelect.swift +++ b/VDS/Components/DropdownSelect/DropdownSelect.swift @@ -153,12 +153,9 @@ open class DropdownSelect: EntryFieldBase { }() // tap gesture - containerView - .publisher(for: UITapGestureRecognizer()) - .sink { [weak self] _ in - self?.launchPicker() - } - .store(in: &subscribers) + containerView.onClick = { [weak self] _ in + self?.launchPicker() + } containerView.height(44) } From 0434b3f63ec207f22bfda1369243773acf6fc45a Mon Sep 17 00:00:00 2001 From: Matt Bruce Date: Thu, 8 Aug 2024 11:00:33 -0500 Subject: [PATCH 6/6] refactored to a View instead of Control implemented isHighlighted Signed-off-by: Matt Bruce --- .../TileContainer/TileContainer.swift | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/VDS/Components/TileContainer/TileContainer.swift b/VDS/Components/TileContainer/TileContainer.swift index f35d2614..cdfe675a 100644 --- a/VDS/Components/TileContainer/TileContainer.swift +++ b/VDS/Components/TileContainer/TileContainer.swift @@ -44,7 +44,7 @@ open class TileContainer: TileContainerBase { } } -open class TileContainerBase: Control where PaddingType.ValueType == CGFloat { +open class TileContainerBase: View where PaddingType.ValueType == CGFloat { //-------------------------------------------------- // MARK: - Initializers @@ -118,6 +118,8 @@ open class TileContainerBase: Control where Padding $0.setContentCompressionResistancePriority(.defaultHigh, for: .vertical) } + private var isHighlighted: Bool = false { didSet { setNeedsUpdate() } } + //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- @@ -337,6 +339,27 @@ open class TileContainerBase: Control where Padding set {} } + open override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) + if let onClickSubscriber { + isHighlighted = true + } + } + + open override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) + if let onClickSubscriber { + isHighlighted = false + } + } + + open override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + if let onClickSubscriber { + isHighlighted = false + } + } + //-------------------------------------------------- // MARK: - Public Methods //--------------------------------------------------