Merge branch 'release/1.0.19' into 'develop'

build for bugfix

See merge request BPHV_MIPS/vds_ios!74
This commit is contained in:
Bruce, Matt R 2023-05-31 19:06:16 +00:00
commit 495051f04b
16 changed files with 232 additions and 75 deletions

View File

@ -95,6 +95,7 @@
EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FEF4292D371F00998C17 /* ButtonBase.swift */; }; EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FEF4292D371F00998C17 /* ButtonBase.swift */; };
EAB5FEF829393A7200998C17 /* ButtonGroupConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FEF729393A7200998C17 /* ButtonGroupConstants.swift */; }; EAB5FEF829393A7200998C17 /* ButtonGroupConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FEF729393A7200998C17 /* ButtonGroupConstants.swift */; };
EAB5FF0129424ACB00998C17 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FF0029424ACB00998C17 /* UIControl.swift */; }; EAB5FF0129424ACB00998C17 /* UIControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FF0029424ACB00998C17 /* UIControl.swift */; };
EABFEB642A26473700C4C106 /* NSAttributedString.swift in Sources */ = {isa = PBXBuildFile; fileRef = EABFEB632A26473700C4C106 /* NSAttributedString.swift */; };
EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */; }; EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */; };
EAC9257D29119B5400091998 /* TextLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC9257C29119B5400091998 /* TextLink.swift */; }; EAC9257D29119B5400091998 /* TextLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC9257C29119B5400091998 /* TextLink.swift */; };
EAC925832911B35400091998 /* TextLinkCaret.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC925822911B35300091998 /* TextLinkCaret.swift */; }; EAC925832911B35400091998 /* TextLinkCaret.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC925822911B35300091998 /* TextLinkCaret.swift */; };
@ -222,6 +223,7 @@
EAB5FEF4292D371F00998C17 /* ButtonBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonBase.swift; sourceTree = "<group>"; }; EAB5FEF4292D371F00998C17 /* ButtonBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonBase.swift; sourceTree = "<group>"; };
EAB5FEF729393A7200998C17 /* ButtonGroupConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupConstants.swift; sourceTree = "<group>"; }; EAB5FEF729393A7200998C17 /* ButtonGroupConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupConstants.swift; sourceTree = "<group>"; };
EAB5FF0029424ACB00998C17 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = "<group>"; }; EAB5FF0029424ACB00998C17 /* UIControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIControl.swift; sourceTree = "<group>"; };
EABFEB632A26473700C4C106 /* NSAttributedString.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSAttributedString.swift; sourceTree = "<group>"; };
EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupCollectionViewCell.swift; sourceTree = "<group>"; }; EAC846F2294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroupCollectionViewCell.swift; sourceTree = "<group>"; };
EAC9257C29119B5400091998 /* TextLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLink.swift; sourceTree = "<group>"; }; EAC9257C29119B5400091998 /* TextLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLink.swift; sourceTree = "<group>"; };
EAC925822911B35300091998 /* TextLinkCaret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLinkCaret.swift; sourceTree = "<group>"; }; EAC925822911B35300091998 /* TextLinkCaret.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextLinkCaret.swift; sourceTree = "<group>"; };
@ -418,6 +420,7 @@
children = ( children = (
EAF7F0992899B17200B287F5 /* CATransaction.swift */, EAF7F0992899B17200B287F5 /* CATransaction.swift */,
EA33622D2891EA3C0071C351 /* DispatchQueue+Once.swift */, EA33622D2891EA3C0071C351 /* DispatchQueue+Once.swift */,
EABFEB632A26473700C4C106 /* NSAttributedString.swift */,
EAB2376529E9952D00AABE9A /* UIApplication.swift */, EAB2376529E9952D00AABE9A /* UIApplication.swift */,
EA3361A7288B23300071C351 /* UIColor.swift */, EA3361A7288B23300071C351 /* UIColor.swift */,
EA81410F2A127066004F60D2 /* UIColor+VDSColor.swift */, EA81410F2A127066004F60D2 /* UIColor+VDSColor.swift */,
@ -871,6 +874,7 @@
EAF7F0B1289B177F00B287F5 /* ColorLabelAttribute.swift in Sources */, EAF7F0B1289B177F00B287F5 /* ColorLabelAttribute.swift in Sources */,
EAC9258F2911C9DE00091998 /* EntryField.swift in Sources */, EAC9258F2911C9DE00091998 /* EntryField.swift in Sources */,
EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */, EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */,
EABFEB642A26473700C4C106 /* NSAttributedString.swift in Sources */,
EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */, EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */,
EA0FC2C62914222900DF80B4 /* ButtonGroup.swift in Sources */, EA0FC2C62914222900DF80B4 /* ButtonGroup.swift in Sources */,
EA89200628B526D6006B9984 /* CheckboxGroup.swift in Sources */, EA89200628B526D6006B9984 /* CheckboxGroup.swift in Sources */,
@ -1070,7 +1074,7 @@
buildSettings = { buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES; BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 19;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;
@ -1103,7 +1107,7 @@
buildSettings = { buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES; BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18; CURRENT_PROJECT_VERSION = 19;
DEFINES_MODULE = YES; DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = ""; DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_COMPATIBILITY_VERSION = 1;

View File

@ -8,6 +8,8 @@
import Foundation import Foundation
import UIKit import UIKit
/// Bundle Manager keeps all bundles together for ease of use for searching within any of them for a specific asset
public class BundleManager { public class BundleManager {
public static var shared = BundleManager() public static var shared = BundleManager()
@ -28,6 +30,8 @@ public class BundleManager {
}) })
} }
/// With take a location and if found append to the local array for use later for asset searching.
/// - Parameter path: Location of the bundle
public static func register(_ path: String) { public static func register(_ path: String) {
if !shared.paths.contains(where: {$0 == path}) { if !shared.paths.contains(where: {$0 == path}) {
if let bundle = Bundle(path: path) { if let bundle = Bundle(path: path) {
@ -40,6 +44,9 @@ public class BundleManager {
extension BundleManager { extension BundleManager {
/// Searches through all registered bundles for an image
/// - Parameter name: Name of the image
/// - Returns: Will find an image or not.
public func image(for name: String) -> UIImage? { public func image(for name: String) -> UIImage? {
var foundImage: UIImage? var foundImage: UIImage?

View File

@ -21,7 +21,9 @@ public typealias ObjectColorable = Colorable & Initable & ObjectWithable
/// config.darkColor = .white /// config.darkColor = .white
/// ///
/// let textColor = config.getColor(model) //returns .black /// let textColor = config.getColor(model) //returns .black
///
/// You can pass in a Surfaceable object and it will give you the color responding.
open class SurfaceColorConfiguration: ObjectColorable { open class SurfaceColorConfiguration: ObjectColorable {
public typealias ObjectType = Surfaceable public typealias ObjectType = Surfaceable
public var lightColor: UIColor = .clear public var lightColor: UIColor = .clear
@ -29,15 +31,27 @@ open class SurfaceColorConfiguration: ObjectColorable {
required public init(){} required public init(){}
/// Inititialization
/// - Parameters:
/// - lightColor: Color used for a light surface
/// - darkColor: Color used for a dark surface
public init(_ lightColor: UIColor, _ darkColor: UIColor) { public init(_ lightColor: UIColor, _ darkColor: UIColor) {
self.lightColor = lightColor self.lightColor = lightColor
self.darkColor = darkColor self.darkColor = darkColor
} }
/// Will get the UIColor for the surface passed in
/// - Parameter surface: surface you currently have
/// - Returns: color corresponding to the surface you passed
public func getColor(_ surface: Surface) -> UIColor { public func getColor(_ surface: Surface) -> UIColor {
return surface == .light ? lightColor : darkColor return surface == .light ? lightColor : darkColor
} }
/// Colorable Generic method that will only take in any object that has a Surfacable implementation
/// - Parameter object: any Surfacable object type
/// - Returns: UIColor for the object type you passed in
public func getColor(_ object: any ObjectType) -> UIColor { public func getColor(_ object: any ObjectType) -> UIColor {
return getColor(object.surface) return getColor(object.surface)
} }
@ -57,16 +71,30 @@ public protocol KeyColorConfigurable: ObjectColorable {
} }
extension KeyColorConfigurable { extension KeyColorConfigurable {
/// Generic Method to set a KeyColorConfiguration with the parameters given
/// - Parameters:
/// - lightColor: Color used for a light
/// - darkColor: Color used for a dark
/// - key: <#key description#>
public func setSurfaceColors(_ lightColor: UIColor, _ darkColor: UIColor, forKey key: KeyType) { public func setSurfaceColors(_ lightColor: UIColor, _ darkColor: UIColor, forKey key: KeyType) {
keyColors.append(.init(key: key, surfaceConfig: .init(lightColor, darkColor))) keyColors.append(.init(key: key, surfaceConfig: .init(lightColor, darkColor)))
} }
/// Removes all keyColors
public func reset() { public func reset() {
keyColors.removeAll() keyColors.removeAll()
} }
} }
extension KeyColorConfigurable where ObjectType: Surfaceable { extension KeyColorConfigurable where ObjectType: Surfaceable {
/// Default Implementation for when the ObjectType is a Surfacable
/// - Parameters:
/// - object: Surfaceable ObjectType
/// - key: KeyPath to the property of the ObjectType
/// - Returns: UIColor corresponding the the key and surface
public func getColor(for object: ObjectType, with key: KeyType) -> UIColor { public func getColor(for object: ObjectType, with key: KeyType) -> UIColor {
if let keyColor = keyColors.first(where: {$0.key == key }) { if let keyColor = keyColors.first(where: {$0.key == key }) {
return keyColor.surfaceConfig.getColor(object) return keyColor.surfaceConfig.getColor(object)
@ -77,6 +105,8 @@ extension KeyColorConfigurable where ObjectType: Surfaceable {
} }
/// ColorConfiguration for UIControls and will implement KeyColorConfigurale. This will then use the
/// register a SurfaceColorConfiguration for each state.
public class ControlColorConfiguration: KeyColorConfigurable { public class ControlColorConfiguration: KeyColorConfigurable {
public typealias KeyType = UIControl.State public typealias KeyType = UIControl.State
public typealias ObjectType = Surfaceable & UIControl public typealias ObjectType = Surfaceable & UIControl
@ -84,10 +114,19 @@ public class ControlColorConfiguration: KeyColorConfigurable {
private var lastKeyColor: KeyColorConfiguration<KeyType>? private var lastKeyColor: KeyColorConfiguration<KeyType>?
public required init() { } public required init() { }
/// Helper method to set the SurfaceColorConfiguration for a state
/// - Parameters:
/// - lightColor: Color used for a light
/// - darkColor: Color used for a dark
/// - state: UIControlState you are keying off of.
public func setSurfaceColors(_ lightColor: UIColor, _ darkColor: UIColor, forState state: KeyType) { public func setSurfaceColors(_ lightColor: UIColor, _ darkColor: UIColor, forState state: KeyType) {
setSurfaceColors(lightColor, darkColor, forKey: state) setSurfaceColors(lightColor, darkColor, forKey: state)
} }
/// Colorable implementation for getColor that where the object passed in will be of a UIControl as well as implementing Surfaceable.
/// - Parameter object: Object that is of UIControl and implements Surfacable
/// - Returns: UIColor corresponding to the UIControl.state and surface property of this object.
public func getColor(_ object: any ObjectType) -> UIColor { public func getColor(_ object: any ObjectType) -> UIColor {
let state = object.state let state = object.state
let surface = object.surface let surface = object.surface
@ -115,6 +154,7 @@ public class ControlColorConfiguration: KeyColorConfigurable {
} }
} }
///Meant to be used with any object that implements Surfaceable and Disabling. More than likely this is any View.
public class ViewColorConfiguration: KeyColorConfigurable { public class ViewColorConfiguration: KeyColorConfigurable {
public typealias KeyType = Bool public typealias KeyType = Bool
public typealias ObjectType = Surfaceable & Disabling public typealias ObjectType = Surfaceable & Disabling
@ -122,10 +162,18 @@ public class ViewColorConfiguration: KeyColorConfigurable {
public required init() { } public required init() { }
/// Helper method to set the SurfaceColorConfiguration based on whether the object is disabled or not.
/// - Parameters:
/// - lightColor: Color used for a light
/// - darkColor: Color used for a dark
/// - disabled: True/False for disabled property
public func setSurfaceColors(_ lightColor: UIColor, _ darkColor: UIColor, forDisabled disabled: KeyType) { public func setSurfaceColors(_ lightColor: UIColor, _ darkColor: UIColor, forDisabled disabled: KeyType) {
setSurfaceColors(lightColor, darkColor, forKey: disabled) setSurfaceColors(lightColor, darkColor, forKey: disabled)
} }
/// Colorable implementation for getColor that where the object passed in will any object that implementing Surfaceable and Disabling.
/// - Parameter object: Object that implements Surfaceable and Disabling
/// - Returns: UIColor correspoding to either true/false for the disabled state and surface
public func getColor(_ object: ObjectType) -> UIColor { public func getColor(_ object: ObjectType) -> UIColor {
if let keyColor = keyColors.first(where: {$0.key == object.disabled }) { if let keyColor = keyColors.first(where: {$0.key == object.disabled }) {
return keyColor.surfaceConfig.getColor(object) return keyColor.surfaceConfig.getColor(object)
@ -154,11 +202,17 @@ public class KeyedColorConfiguration<ObjectType: Surfaceable, KeyType: Equatable
self.keyPath = keyPath self.keyPath = keyPath
} }
/// Finds the KeyColorConfiguration for the Object passed in.
/// - Parameter object: Object should be of Surfaceable
/// - Returns: KeyType for the Object passed in
public func getKeyValue(_ object: ObjectType) -> KeyType? { public func getKeyValue(_ object: ObjectType) -> KeyType? {
guard let keyPath else { fatalError("keyPath must not be empty, make sure you initialize this class using init(keyPath: \\Object.property) method") } guard let keyPath else { fatalError("keyPath must not be empty, make sure you initialize this class using init(keyPath: \\Object.property) method") }
return object[keyPath: keyPath] return object[keyPath: keyPath]
} }
/// Colorable implementation for getColor that where the object passed in will any object that implementing Surfaceable for the KeyPath registered.
/// - Parameter object: Object matching the type registered
/// - Returns: UIColor for the Object pertaining to the KeyPath it was registered
public func getColor(_ object: ObjectType) -> UIColor { public func getColor(_ object: ObjectType) -> UIColor {
if let key = getKeyValue(object), let keyColor = keyColors.first(where: {$0.key == key }) { if let key = getKeyValue(object), let keyColor = keyColors.first(where: {$0.key == key }) {
return keyColor.surfaceConfig.getColor(object) return keyColor.surfaceConfig.getColor(object)

View File

@ -15,7 +15,11 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Combine Properties // MARK: - Combine Properties
//-------------------------------------------------- //--------------------------------------------------
/// Set of Subscribers for any Publishers for this Control
public var subscribers = Set<AnyCancellable>() public var subscribers = Set<AnyCancellable>()
/// Sets the primary Subscriber used for the TouchUpInside
public var onClickSubscriber: AnyCancellable? { public var onClickSubscriber: AnyCancellable? {
willSet { willSet {
if let onClickSubscriber { if let onClickSubscriber {
@ -29,29 +33,37 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab
//-------------------------------------------------- //--------------------------------------------------
private var initialSetupPerformed = false private var initialSetupPerformed = false
/// Key of whether or not updateView() is called in setNeedsUpdate()
open var shouldUpdateView: Bool = true open var shouldUpdateView: Bool = true
/// Dictionary for keeping information for this Control use only Primitives
open var userInfo = [String: Primitive]() open var userInfo = [String: Primitive]()
/// Current Surface used within this Control and used to passdown to child views
open var surface: Surface = .light { didSet { setNeedsUpdate() } } open var surface: Surface = .light { didSet { setNeedsUpdate() } }
/// Control is disabled or not
open var disabled: Bool = false { didSet { isEnabled = !disabled } } open var disabled: Bool = false { didSet { isEnabled = !disabled } }
/// Override for isSelected to handle setNeedsUpdate() on changes
open override var isSelected: Bool { didSet { setNeedsUpdate() } } open override var isSelected: Bool { didSet { setNeedsUpdate() } }
/// Reference count use to be used in isHighlighted when a subscriber is listening for TouchUpInside.
public var touchUpInsideCount: Int = 0 public var touchUpInsideCount: Int = 0
var isHighlightAnimating = false var isHighlightAnimating = false
/// Override to deal with only calling setNeedsUpdate() if needed
open override var isHighlighted: Bool { open override var isHighlighted: Bool {
didSet { didSet {
if isHighlightAnimating == false && touchUpInsideCount > 0 { if isHighlightAnimating == false && touchUpInsideCount > 0 {
isHighlightAnimating = true isHighlightAnimating = true
UIView.animate(withDuration: 0.1, animations: { [weak self] in UIView.animate(withDuration: 0.1, animations: { [weak self] in
self?.updateView() self?.setNeedsUpdate()
}) { [weak self] _ in }) { [weak self] _ in
//you update the view since this is typically a quick change //you update the view since this is typically a quick change
UIView.animate(withDuration: 0.1, animations: { [weak self] in UIView.animate(withDuration: 0.1, animations: { [weak self] in
self?.updateView() self?.setNeedsUpdate()
self?.isHighlightAnimating = false self?.isHighlightAnimating = false
}) })
} }
@ -59,6 +71,7 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab
} }
} }
/// Override to deal with setNeedsUpdate()
open override var isEnabled: Bool { open override var isEnabled: Bool {
get { !disabled } get { !disabled }
set { set {
@ -92,6 +105,7 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab
// MARK: - Setup // MARK: - Setup
//-------------------------------------------------- //--------------------------------------------------
/// Executed on initialization for this Control
open func initialSetup() { open func initialSetup() {
if !initialSetupPerformed { if !initialSetupPerformed {
initialSetupPerformed = true initialSetupPerformed = true
@ -100,6 +114,8 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab
} }
} }
///Override to deal with sending actions for accessibility
/// - Returns: Based on whether the userInteraction is enabled
override open func accessibilityActivate() -> Bool { override open func accessibilityActivate() -> Bool {
// Hold state in case User wanted isAnimated to remain off. // Hold state in case User wanted isAnimated to remain off.
guard isUserInteractionEnabled else { return false } guard isUserInteractionEnabled else { return false }
@ -110,22 +126,25 @@ open class Control: UIControl, Handlerable, ViewProtocol, Resettable, UserInfoab
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Overrides // MARK: - Overrides
//-------------------------------------------------- //--------------------------------------------------
/// Update this view based off of property changes
open func updateView() { open func updateView() {
updateAccessibilityLabel() updateAccessibilityLabel()
} }
/// Used to update any Accessibility properties
open func updateAccessibilityLabel() { open func updateAccessibilityLabel() {
} }
/// Resets to the Controls default values
open func reset() { open func reset() {
backgroundColor = .clear backgroundColor = .clear
surface = .light surface = .light
disabled = false disabled = false
} }
// MARK: - ViewProtocol /// Will be called only once and should be overridden in subclasses to setup UI or defaults
/// Will be called only once.
open func setup() { open func setup() {
backgroundColor = .clear backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false translatesAutoresizingMaskIntoConstraints = false

View File

@ -9,13 +9,17 @@ import Foundation
import UIKit import UIKit
import Combine import Combine
/// Base Class used for any Grouped Form Control of a Selector Type
open class SelectorGroupHandlerBase<HandlerType: Control>: Control, Changeable { open class SelectorGroupHandlerBase<HandlerType: Control>: Control, Changeable {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Public Properties // MARK: - Public Properties
//-------------------------------------------------- //--------------------------------------------------
/// Array of the HandlerType registered
public var selectorViews: [HandlerType] = [] public var selectorViews: [HandlerType] = []
/// The primary subscriber for onChange or the UIControl valueChanged event.
public var onChangeSubscriber: AnyCancellable? { public var onChangeSubscriber: AnyCancellable? {
willSet { willSet {
if let onChangeSubscriber { if let onChangeSubscriber {
@ -27,6 +31,8 @@ open class SelectorGroupHandlerBase<HandlerType: Control>: Control, Changeable {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Overrides // MARK: - Overrides
//-------------------------------------------------- //--------------------------------------------------
/// Override to update the child SelectorViews disabled property for this group
override public var disabled: Bool { override public var disabled: Bool {
didSet { didSet {
selectorViews.forEach { handler in selectorViews.forEach { handler in
@ -35,6 +41,7 @@ open class SelectorGroupHandlerBase<HandlerType: Control>: Control, Changeable {
} }
} }
/// Override to update the child SelectorViews surface property for this group
override public var surface: Surface { override public var surface: Surface {
didSet { didSet {
selectorViews.forEach { handler in selectorViews.forEach { handler in
@ -43,16 +50,20 @@ open class SelectorGroupHandlerBase<HandlerType: Control>: Control, Changeable {
} }
} }
/// Handler for the Group to override on a select event
/// - Parameter selectedControl: Selected Control the user interacted
open func didSelect(_ selectedControl: HandlerType) { open func didSelect(_ selectedControl: HandlerType) {
fatalError("Must override didSelect") fatalError("Must override didSelect")
} }
/// Helper method to execute the valueChanged event
public func valueChanged() { public func valueChanged() {
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(100)) { [weak self] in
self?.sendActions(for: .valueChanged) self?.sendActions(for: .valueChanged)
} }
} }
/// Override to update the child SelectorViews reset method for this group
open override func reset() { open override func reset() {
super.reset() super.reset()
selectorViews.forEach{ $0.reset() } selectorViews.forEach{ $0.reset() }
@ -60,6 +71,8 @@ open class SelectorGroupHandlerBase<HandlerType: Control>: Control, Changeable {
} }
open class SelectorGroupSelectedHandlerBase<HandlerType: Control>: SelectorGroupHandlerBase<HandlerType>{ open class SelectorGroupSelectedHandlerBase<HandlerType: Control>: SelectorGroupHandlerBase<HandlerType>{
/// Current Selected Control for this group
public var selectedHandler: HandlerType? { public var selectedHandler: HandlerType? {
return selectorViews.filter { $0.isSelected == true }.first return selectorViews.filter { $0.isSelected == true }.first
} }

View File

@ -9,12 +9,19 @@ import Foundation
import UIKit import UIKit
@objc(VDSSelfSizingCollectionView) @objc(VDSSelfSizingCollectionView)
/// UICollectionView subclassed used to deal with Changing the size of itself based on its children and layout and changes of its contentSize
public final class SelfSizingCollectionView: UICollectionView { public final class SelfSizingCollectionView: UICollectionView {
private var contentSizeObservation: NSKeyValueObservation? private var contentSizeObservation: NSKeyValueObservation?
//--------------------------------------------------
// MARK: - Lifecycle // MARK: - Lifecycle
//--------------------------------------------------
/// Initializer
/// - Parameters:
/// - frame: Frame needed
/// - layout: Layout used for this CollectionView
public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) { public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout) super.init(frame: frame, collectionViewLayout: layout)
self.setupContentSizeObservation() self.setupContentSizeObservation()
@ -25,7 +32,10 @@ public final class SelfSizingCollectionView: UICollectionView {
self.setupContentSizeObservation() self.setupContentSizeObservation()
} }
//--------------------------------------------------
// MARK: - UIView // MARK: - UIView
//--------------------------------------------------
public override var intrinsicContentSize: CGSize { public override var intrinsicContentSize: CGSize {
let contentSize = self.contentSize let contentSize = self.contentSize
@ -33,6 +43,7 @@ public final class SelfSizingCollectionView: UICollectionView {
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height) return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
} }
/// Overridden to deal with Appearance Changes
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
//print(type(of: self), #function) //print(type(of: self), #function)
super.traitCollectionDidChange(previousTraitCollection) super.traitCollectionDidChange(previousTraitCollection)
@ -43,13 +54,9 @@ public final class SelfSizingCollectionView: UICollectionView {
} }
} }
public override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize { //--------------------------------------------------
let size = super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
//print(type(of: self), #function, targetSize, "->", size)
return size
}
// MARK: - Private // MARK: - Private
//--------------------------------------------------
private func setupContentSizeObservation() { private func setupContentSizeObservation() {
// Observing the value of contentSize seems to be the only reliable way to get the contentSize after the collection view lays out its subviews. // Observing the value of contentSize seems to be the only reliable way to get the contentSize after the collection view lays out its subviews.
@ -64,6 +71,9 @@ public final class SelfSizingCollectionView: UICollectionView {
extension UITraitCollection { extension UITraitCollection {
/// Used within SelfSizingCollectionView to determine if there is an appearance change
/// - Parameter traitCollection: TraitCollection to compare
/// - Returns: True/False based on the trailCollection passed in
public func hasDifferentTextAppearance(comparedTo traitCollection: UITraitCollection?) -> Bool { public func hasDifferentTextAppearance(comparedTo traitCollection: UITraitCollection?) -> Bool {
var result = self.preferredContentSizeCategory != traitCollection?.preferredContentSizeCategory var result = self.preferredContentSizeCategory != traitCollection?.preferredContentSizeCategory
result = result || self.legibilityWeight != traitCollection?.legibilityWeight result = result || self.legibilityWeight != traitCollection?.legibilityWeight

View File

@ -23,14 +23,19 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable {
//-------------------------------------------------- //--------------------------------------------------
private var initialSetupPerformed = false private var initialSetupPerformed = false
/// Key of whether or not updateView() is called in setNeedsUpdate()
open var shouldUpdateView: Bool = true open var shouldUpdateView: Bool = true
/// Dictionary for keeping information for this Control use only Primitives
open var userInfo = [String: Primitive]() open var userInfo = [String: Primitive]()
open var surface: Surface = .light { didSet { setNeedsUpdate() }} /// Current Surface used within this Control and used to passdown to child views
open var surface: Surface = .light { didSet { setNeedsUpdate() } }
/// Control is disabled or not
open var disabled: Bool = false { didSet { isEnabled = !disabled } } open var disabled: Bool = false { didSet { isEnabled = !disabled } }
/// Override to deal with setNeedsUpdate()
open var isEnabled: Bool { open var isEnabled: Bool {
get { !disabled } get { !disabled }
set { set {
@ -64,6 +69,7 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable {
// MARK: - Setup // MARK: - Setup
//-------------------------------------------------- //--------------------------------------------------
/// Executed on initialization for this View
open func initialSetup() { open func initialSetup() {
if !initialSetupPerformed { if !initialSetupPerformed {
initialSetupPerformed = true initialSetupPerformed = true
@ -75,22 +81,25 @@ open class View: UIView, Handlerable, ViewProtocol, Resettable, UserInfoable {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Overrides // MARK: - Overrides
//-------------------------------------------------- //--------------------------------------------------
/// Update this view based off of property changes
open func updateView() { open func updateView() {
updateAccessibilityLabel() updateAccessibilityLabel()
} }
/// Used to update any Accessibility properties
open func updateAccessibilityLabel() { open func updateAccessibilityLabel() {
} }
/// Resets to the Views default values
open func reset() { open func reset() {
backgroundColor = .clear backgroundColor = .clear
surface = .light surface = .light
disabled = false disabled = false
} }
// MARK: - ViewProtocol /// Will be called only once and should be overridden in subclasses to setup UI or defaults
/// Will be called only once.
open func setup() { open func setup() {
backgroundColor = .clear backgroundColor = .clear
translatesAutoresizingMaskIntoConstraints = false translatesAutoresizingMaskIntoConstraints = false

View File

@ -28,7 +28,7 @@ open class TextLinkCaret: ButtonBase {
TextStyle.boldBodyLarge TextStyle.boldBodyLarge
} }
private var imageAttribute: ImageSpaceLabelAttribute? private var imageAttribute: CaretLabelAttribute?
open override var attributes: [any LabelAttributeModel]? { open override var attributes: [any LabelAttributeModel]? {
guard let imageAttribute else { return nil } guard let imageAttribute else { return nil }
@ -100,20 +100,20 @@ open class TextLinkCaret: ButtonBase {
} }
open override func updateView() { open override func updateView() {
imageAttribute = ImageSpaceLabelAttribute(tintColor: textColor, position: iconPosition) imageAttribute = CaretLabelAttribute(tintColor: textColor, position: iconPosition)
super.updateView() super.updateView()
} }
} }
extension TextLinkCaret { extension TextLinkCaret {
struct ImageSpaceLabelAttribute: LabelAttributeModel { struct CaretLabelAttribute: LabelAttributeModel {
var id: UUID = .init() var id: UUID = .init()
var location: Int = 0 var location: Int = 0
var length: Int = 1 var length: Int = 1
var tintColor: UIColor var tintColor: UIColor
var position: IconPosition var position: IconPosition
var spacerWidth: CGFloat = 2.0 var spacerWidth: CGFloat = 4.0
var width: CGFloat { caretSize.width + spacerWidth } var width: CGFloat { caretSize.width + spacerWidth }
var caretSize: CGSize { Icon.Size.xsmall.dimensions } var caretSize: CGSize { Icon.Size.xsmall.dimensions }
@ -124,22 +124,22 @@ extension TextLinkCaret {
func setAttribute(on attributedString: NSMutableAttributedString) { 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 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) let spacer = NSAttributedString.spacer(for: spacerWidth)
guard let image = try? imageAttr.getAttachment(), guard let image = try? imageAttr.getAttachment() else { return }
let spacer = try? spaceAttr.getAttachment() else { return }
if position == .right { if position == .right {
attributedString.append(NSAttributedString(attachment: spacer)) attributedString.append(spacer)
attributedString.append(NSAttributedString(attachment: image)) attributedString.append(NSAttributedString(attachment: image))
} else { } else {
attributedString.insert(NSAttributedString(attachment: image), at: 0) attributedString.insert(NSAttributedString(attachment: image), at: 0)
attributedString.insert(NSAttributedString(attachment: spacer), at: 1) attributedString.insert(spacer, at: 1)
} }
} }
func isEqual(_ equatable: ImageSpaceLabelAttribute) -> Bool { func isEqual(_ equatable: CaretLabelAttribute) -> Bool {
return id == equatable.id && range == equatable.range return id == equatable.id && range == equatable.range
} }
} }
} }

View File

@ -51,23 +51,16 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable
} }
} }
//create the frame in which to hold the icon
let spacerframe = CGRect(x: 0, y: 0, width: VDSLayout.Spacing.space1X.value, height: size.value.dimensions.height)
//create the image icon and match the color of the text //create the image icon and match the color of the text
let tooltipAttribute = ImageLabelAttribute(location: location, let tooltipAttribute = ImageLabelAttribute(location: location,
imageName: "info", imageName: "info",
frame: frame, frame: frame,
tintColor: imageTintColor) tintColor: imageTintColor)
let spacerAttribute = ImageLabelAttribute(location: location, let spacer = NSAttributedString.spacer(for: VDSLayout.Spacing.space1X.value)
imageName: "info",
frame: spacerframe,
tintColor: .clear)
guard let tooltip = try? tooltipAttribute.getAttachment(), guard let tooltip = try? tooltipAttribute.getAttachment() else { return }
let spacer = try? spacerAttribute.getAttachment() else { return } attributedString.append(spacer)
attributedString.append(NSAttributedString(attachment: spacer))
attributedString.append(NSAttributedString(attachment: tooltip)) attributedString.append(NSAttributedString(attachment: tooltip))
addHandler(on: attributedString) addHandler(on: attributedString)
} }

View File

@ -0,0 +1,19 @@
//
// NSAttributedString.swift
// VDS
//
// Created by Matt Bruce on 5/30/23.
//
import Foundation
import UIKit
extension NSAttributedString {
public static func spacer(for width: CGFloat) -> NSAttributedString {
let spacerImage = UIImage()
let spacerAttachment = NSTextAttachment()
spacerAttachment.bounds = .init(x: 0, y: 0, width: width, height: 1)
spacerAttachment.image = spacerImage
return NSAttributedString(attachment: spacerAttachment)
}
}

View File

@ -10,6 +10,9 @@ import UIKit
extension UIApplication { extension UIApplication {
/// Helper method to find the top most viewcontroller in the app
/// - Parameter controller: UIViewController to test against
/// - Returns: Found top most UIViewController
public class func topViewController(controller: UIViewController? = UIApplication.shared.windows.first?.rootViewController) -> UIViewController? { public class func topViewController(controller: UIViewController? = UIApplication.shared.windows.first?.rootViewController) -> UIViewController? {
if let nav = controller as? UINavigationController { if let nav = controller as? UINavigationController {

View File

@ -9,6 +9,8 @@ import Foundation
import VDSColorTokens import VDSColorTokens
extension UIColor { extension UIColor {
/// Since VDSColorTokens is just a Class with Static Properties, this is an Enum for each property of that class for each of use within the VDS Library
public enum VDSColor: String, CaseIterable { public enum VDSColor: String, CaseIterable {
case paletteBlack case paletteBlack
case paletteWhite case paletteWhite
@ -96,7 +98,7 @@ extension UIColor {
case elementsLowcontrastOnlight case elementsLowcontrastOnlight
case elementsLowcontrastOndark case elementsLowcontrastOndark
// Map each color name to its corresponding UIColor object. /// Map each color name to its corresponding UIColor object.
public var uiColor: UIColor { public var uiColor: UIColor {
do { do {
let color = try VDSColorTokens.VDSColor.getTokenByString(tokenName: "VDSColor.\(rawValue)") let color = try VDSColorTokens.VDSColor.getTokenByString(tokenName: "VDSColor.\(rawValue)")
@ -108,6 +110,9 @@ extension UIColor {
} }
} }
/// Helper method to see if a UIColor exists in the VDSColor enumeration
/// - Parameter color: UIColor needed to compare
/// - Returns: True if found, false in not found in VDSColor enumeration
public static func isVDSColor(color: UIColor) -> Bool { public static func isVDSColor(color: UIColor) -> Bool {
guard let hex = color.hexString else { return false } guard let hex = color.hexString else { return false }
let found = VDSColor.allCases.first{ $0.uiColor.hexString == hex } let found = VDSColor.allCases.first{ $0.uiColor.hexString == hex }

View File

@ -14,26 +14,45 @@ extension UIColor {
// MARK: - Functions // MARK: - Functions
//-------------------------------------------------- //--------------------------------------------------
/// Convenience to get a grayscale UIColor where the same value is used for red, green, and blue. /// Convenience to get a grayscale UIColor where the same value is used for red, green, and blue
/// - Parameters:
/// - rgb: red, green, and blue
/// - alpha: alphay
/// - Returns: UIColor that will be grayscale
public class func grayscale(rgb: Int, alpha: CGFloat = 1.0) -> UIColor { public class func grayscale(rgb: Int, alpha: CGFloat = 1.0) -> UIColor {
let grayscale = CGFloat(rgb) / 255.0 let grayscale = CGFloat(rgb) / 255.0
return UIColor(red: grayscale, green: grayscale, blue: grayscale, alpha: alpha) return UIColor(red: grayscale, green: grayscale, blue: grayscale, alpha: alpha)
} }
/// Convenience to get a UIColor. /// Convenience to get an 8-Bit UIColor based on RGB values
/// - Parameters:
/// - red: Value for Red
/// - green: Value for Green
/// - blue: Value for Blue
/// - alpha: Value for Alpha
/// - Returns: UIColor for the values above
public class func color8Bits(red: Int, green: Int, blue: Int, alpha: CGFloat = 1.0) -> UIColor { public class func color8Bits(red: Int, green: Int, blue: Int, alpha: CGFloat = 1.0) -> UIColor {
return UIColor(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: alpha) return UIColor(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: alpha)
} }
/// Convenience to get a UIColor. /// Convenience to get an UIColor based on RGB values
/// - Parameters:
/// - red: Value for Red
/// - green: Value for Green
/// - blue: Value for Blue
/// - alpha: Value for Alpha
/// - Returns: UIColor for the values above
public class func color8Bits(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1.0) -> UIColor { public class func color8Bits(red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat = 1.0) -> UIColor {
return UIColor(red: red / 255.0, green: green / 255.0, blue: blue / 255.0, alpha: alpha) return UIColor(red: red / 255.0, green: green / 255.0, blue: blue / 255.0, alpha: alpha)
} }
/// Gets UIColor via an 8 digit hex string. /// Gets UIColor via an 8 digit hex string.
/// - Parameters:
/// - hex: HexCode string
/// - Returns: UIColor for the values above
public class func getColorBy(hex: String) -> UIColor { public class func getColorBy(hex: String) -> UIColor {
var hexint: UInt64 = 0 var hexint: UInt64 = 0
@ -48,7 +67,10 @@ extension UIColor {
alpha: 1) alpha: 1)
} }
/// Gets UIColor via an 8 digit hex string. The last two being the alpha channel. /// Gets UIColor via an 8 digit hex string that includes transparency.
/// - Parameters:
/// - hex: HexCode string
/// - Returns: UIColor for the values above
public class func getColorWithTransparencyBy(hex: String) -> UIColor { public class func getColorWithTransparencyBy(hex: String) -> UIColor {
var hexint: UInt64 = 0 var hexint: UInt64 = 0
@ -63,6 +85,9 @@ extension UIColor {
alpha: (CGFloat(hexint & 0xFF)) / 255) alpha: (CGFloat(hexint & 0xFF)) / 255)
} }
/// Generates a gradient color for the color passed in
/// - Parameter color: Primary color for the gradient
/// - Returns: UIColor that is gradient
public class func gradientColor(_ color: UIColor?) -> UIColor { public class func gradientColor(_ color: UIColor?) -> UIColor {
var h: CGFloat = 0 var h: CGFloat = 0
@ -77,7 +102,9 @@ extension UIColor {
return .white return .white
} }
/// - parameter color: The UIColor intended to retrieve its hex value. /// Gets the hex code value for a UIColor
/// - Parameter color: UIColor intended to retrieve its hex value
/// - Returns: Hex Code
public class func hexString(for color: UIColor) -> String? { public class func hexString(for color: UIColor) -> String? {
guard let components = color.cgColor.components else { return nil } guard let components = color.cgColor.components else { return nil }
@ -111,7 +138,8 @@ extension UIColor {
return nil return nil
} }
/// - parameter color: The UIColor intended to retrieve its hex value. /// Gets the hex code value for the current UIColor
/// - Returns: Hex Code string
public var hexString: String? { public var hexString: String? {
guard let components = cgColor.components else { return nil } guard let components = cgColor.components else { return nil }
@ -145,6 +173,8 @@ extension UIColor {
return nil return nil
} }
/// Initialize a UIColor with a HexCode string
/// - Parameter hexString: HexCode string to convert to a UIColor
public convenience init(hexString: String) { public convenience init(hexString: String) {
let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int = UInt64() var int = UInt64()
@ -163,23 +193,3 @@ extension UIColor {
self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255) self.init(red: CGFloat(r) / 255, green: CGFloat(g) / 255, blue: CGFloat(b) / 255, alpha: CGFloat(a) / 255)
} }
} }
extension UIColor {
convenience init(
light lightModeColor: @escaping @autoclosure () -> UIColor,
dark darkModeColor: @escaping @autoclosure () -> UIColor
) {
self.init { traitCollection in
switch traitCollection.userInterfaceStyle {
case .light:
return lightModeColor()
case .dark:
return darkModeColor()
case .unspecified:
return lightModeColor()
@unknown default:
return lightModeColor()
}
}
}
}

View File

@ -9,6 +9,8 @@ import Foundation
import UIKit import UIKit
extension UIDevice { extension UIDevice {
/// Helper property to see if your current device running is an iPad or not
public static var isIPad: Bool { public static var isIPad: Bool {
UIDevice.current.userInterfaceIdiom == .pad UIDevice.current.userInterfaceIdiom == .pad
} }

View File

@ -10,6 +10,11 @@ import UIKit
extension UITapGestureRecognizer { extension UITapGestureRecognizer {
/// Determines if the touch event has a action attribute within the range given
/// - Parameters:
/// - label: UILabel in question
/// - targetRange: Range to look within
/// - Returns: Wether the range in the label has an action
public func didTapActionInLabel(_ label: UILabel, inRange targetRange: NSRange) -> Bool { public func didTapActionInLabel(_ label: UILabel, inRange targetRange: NSRange) -> Bool {
guard let attributedText = label.attributedText else { return false } guard let attributedText = label.attributedText else { return false }

View File

@ -1,3 +1,7 @@
1.0.19
=======
- CXTDT-419731 - TextLinkCaret - Spacing issue
1.0.18 1.0.18
======= =======
- CXTDT-412383 - Badge Corner Radius / Color issue (resolved in previous build) - CXTDT-412383 - Badge Corner Radius / Color issue (resolved in previous build)