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 */; };
EAB5FEF829393A7200998C17 /* ButtonGroupConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB5FEF729393A7200998C17 /* ButtonGroupConstants.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 */; };
EAC9257D29119B5400091998 /* TextLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAC9257C29119B5400091998 /* TextLink.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>"; };
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>"; };
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>"; };
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>"; };
@ -418,6 +420,7 @@
children = (
EAF7F0992899B17200B287F5 /* CATransaction.swift */,
EA33622D2891EA3C0071C351 /* DispatchQueue+Once.swift */,
EABFEB632A26473700C4C106 /* NSAttributedString.swift */,
EAB2376529E9952D00AABE9A /* UIApplication.swift */,
EA3361A7288B23300071C351 /* UIColor.swift */,
EA81410F2A127066004F60D2 /* UIColor+VDSColor.swift */,
@ -871,6 +874,7 @@
EAF7F0B1289B177F00B287F5 /* ColorLabelAttribute.swift in Sources */,
EAC9258F2911C9DE00091998 /* EntryField.swift in Sources */,
EAB1D2EA28AE84AA00DAE764 /* UIControlPublisher.swift in Sources */,
EABFEB642A26473700C4C106 /* NSAttributedString.swift in Sources */,
EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */,
EA0FC2C62914222900DF80B4 /* ButtonGroup.swift in Sources */,
EA89200628B526D6006B9984 /* CheckboxGroup.swift in Sources */,
@ -1070,7 +1074,7 @@
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18;
CURRENT_PROJECT_VERSION = 19;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;
@ -1103,7 +1107,7 @@
buildSettings = {
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 18;
CURRENT_PROJECT_VERSION = 19;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = "";
DYLIB_COMPATIBILITY_VERSION = 1;

View File

@ -8,6 +8,8 @@
import Foundation
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 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) {
if !shared.paths.contains(where: {$0 == path}) {
if let bundle = Bundle(path: path) {
@ -40,6 +44,9 @@ public class 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? {
var foundImage: UIImage?

View File

@ -21,7 +21,9 @@ public typealias ObjectColorable = Colorable & Initable & ObjectWithable
/// config.darkColor = .white
///
/// 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 {
public typealias ObjectType = Surfaceable
public var lightColor: UIColor = .clear
@ -29,15 +31,27 @@ open class SurfaceColorConfiguration: ObjectColorable {
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) {
self.lightColor = lightColor
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 {
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 {
return getColor(object.surface)
}
@ -57,16 +71,30 @@ public protocol KeyColorConfigurable: ObjectColorable {
}
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) {
keyColors.append(.init(key: key, surfaceConfig: .init(lightColor, darkColor)))
}
/// Removes all keyColors
public func reset() {
keyColors.removeAll()
}
}
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 {
if let keyColor = keyColors.first(where: {$0.key == key }) {
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 typealias KeyType = UIControl.State
public typealias ObjectType = Surfaceable & UIControl
@ -84,10 +114,19 @@ public class ControlColorConfiguration: KeyColorConfigurable {
private var lastKeyColor: KeyColorConfiguration<KeyType>?
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) {
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 {
let state = object.state
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 typealias KeyType = Bool
public typealias ObjectType = Surfaceable & Disabling
@ -122,10 +162,18 @@ public class ViewColorConfiguration: KeyColorConfigurable {
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) {
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 {
if let keyColor = keyColors.first(where: {$0.key == object.disabled }) {
return keyColor.surfaceConfig.getColor(object)
@ -154,11 +202,17 @@ public class KeyedColorConfiguration<ObjectType: Surfaceable, KeyType: Equatable
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? {
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]
}
/// 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 {
if let key = getKeyValue(object), let keyColor = keyColors.first(where: {$0.key == key }) {
return keyColor.surfaceConfig.getColor(object)

View File

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

View File

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

View File

@ -9,12 +9,19 @@ import Foundation
import UIKit
@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 {
private var contentSizeObservation: NSKeyValueObservation?
//--------------------------------------------------
// MARK: - Lifecycle
//--------------------------------------------------
/// Initializer
/// - Parameters:
/// - frame: Frame needed
/// - layout: Layout used for this CollectionView
public override init(frame: CGRect, collectionViewLayout layout: UICollectionViewLayout) {
super.init(frame: frame, collectionViewLayout: layout)
self.setupContentSizeObservation()
@ -25,14 +32,18 @@ public final class SelfSizingCollectionView: UICollectionView {
self.setupContentSizeObservation()
}
//--------------------------------------------------
// MARK: - UIView
//--------------------------------------------------
public override var intrinsicContentSize: CGSize {
let contentSize = self.contentSize
//print(#function, contentSize)
return CGSize(width: UIView.noIntrinsicMetric, height: contentSize.height)
}
/// Overridden to deal with Appearance Changes
public override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
//print(type(of: self), #function)
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
//--------------------------------------------------
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.
@ -63,7 +70,10 @@ public final class SelfSizingCollectionView: UICollectionView {
}
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 {
var result = self.preferredContentSizeCategory != traitCollection?.preferredContentSizeCategory
result = result || self.legibilityWeight != traitCollection?.legibilityWeight

View File

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

View File

@ -28,7 +28,7 @@ open class TextLinkCaret: ButtonBase {
TextStyle.boldBodyLarge
}
private var imageAttribute: ImageSpaceLabelAttribute?
private var imageAttribute: CaretLabelAttribute?
open override var attributes: [any LabelAttributeModel]? {
guard let imageAttribute else { return nil }
@ -100,20 +100,20 @@ open class TextLinkCaret: ButtonBase {
}
open override func updateView() {
imageAttribute = ImageSpaceLabelAttribute(tintColor: textColor, position: iconPosition)
imageAttribute = CaretLabelAttribute(tintColor: textColor, position: iconPosition)
super.updateView()
}
}
extension TextLinkCaret {
struct ImageSpaceLabelAttribute: LabelAttributeModel {
struct CaretLabelAttribute: 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 spacerWidth: CGFloat = 4.0
var width: CGFloat { caretSize.width + spacerWidth }
var caretSize: CGSize { Icon.Size.xsmall.dimensions }
@ -124,22 +124,22 @@ extension TextLinkCaret {
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)
let spacer = NSAttributedString.spacer(for: spacerWidth)
guard let image = try? imageAttr.getAttachment(),
let spacer = try? spaceAttr.getAttachment() else { return }
guard let image = try? imageAttr.getAttachment() else { return }
if position == .right {
attributedString.append(NSAttributedString(attachment: spacer))
attributedString.append(spacer)
attributedString.append(NSAttributedString(attachment: image))
} else {
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
}
}
}

View File

@ -50,24 +50,17 @@ public class TooltipLabelAttribute: ActionLabelAttributeModel, TooltipLaunchable
frame = CGRect(x: 0, y: -1, width: size.value.dimensions.width, height: size.value.dimensions.height)
}
}
//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
let tooltipAttribute = ImageLabelAttribute(location: location,
imageName: "info",
frame: frame,
tintColor: imageTintColor)
let spacerAttribute = ImageLabelAttribute(location: location,
imageName: "info",
frame: spacerframe,
tintColor: .clear)
let spacer = NSAttributedString.spacer(for: VDSLayout.Spacing.space1X.value)
guard let tooltip = try? tooltipAttribute.getAttachment(),
let spacer = try? spacerAttribute.getAttachment() else { return }
attributedString.append(NSAttributedString(attachment: spacer))
guard let tooltip = try? tooltipAttribute.getAttachment() else { return }
attributedString.append(spacer)
attributedString.append(NSAttributedString(attachment: tooltip))
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 {
/// 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? {
if let nav = controller as? UINavigationController {

View File

@ -9,6 +9,8 @@ import Foundation
import VDSColorTokens
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 {
case paletteBlack
case paletteWhite
@ -96,7 +98,7 @@ extension UIColor {
case elementsLowcontrastOnlight
case elementsLowcontrastOndark
// Map each color name to its corresponding UIColor object.
/// Map each color name to its corresponding UIColor object.
public var uiColor: UIColor {
do {
let color = try VDSColorTokens.VDSColor.getTokenByString(tokenName: "VDSColor.\(rawValue)")
@ -107,7 +109,10 @@ 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 {
guard let hex = color.hexString else { return false }
let found = VDSColor.allCases.first{ $0.uiColor.hexString == hex }

View File

@ -14,26 +14,45 @@ extension UIColor {
// 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 {
let grayscale = CGFloat(rgb) / 255.0
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 {
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 {
return UIColor(red: red / 255.0, green: green / 255.0, blue: blue / 255.0, alpha: alpha)
}
/// 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 {
var hexint: UInt64 = 0
@ -48,7 +67,10 @@ extension UIColor {
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 {
var hexint: UInt64 = 0
@ -63,6 +85,9 @@ extension UIColor {
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 {
var h: CGFloat = 0
@ -77,7 +102,9 @@ extension UIColor {
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? {
guard let components = color.cgColor.components else { return nil }
@ -111,7 +138,8 @@ extension UIColor {
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? {
guard let components = cgColor.components else { return nil }
@ -145,6 +173,8 @@ extension UIColor {
return nil
}
/// Initialize a UIColor with a HexCode string
/// - Parameter hexString: HexCode string to convert to a UIColor
public convenience init(hexString: String) {
let hex = hexString.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
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)
}
}
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
extension UIDevice {
/// Helper property to see if your current device running is an iPad or not
public static var isIPad: Bool {
UIDevice.current.userInterfaceIdiom == .pad
}

View File

@ -10,6 +10,11 @@ import UIKit
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 {
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
=======
- CXTDT-412383 - Badge Corner Radius / Color issue (resolved in previous build)