// // Icon.swift // VDS // // Created by Matt Bruce on 1/9/23. // import Foundation import UIKit import VDSCoreTokens import Combine /// An icon is a graphical element that conveys information at a glance. It helps orient /// a customer, explain functionality and draw attention to interactive elements. Icons /// should have a functional purpose and should never be used for decoration. @objcMembers @objc(VDSIcon) open class Icon: View { //-------------------------------------------------- // MARK: - Initializers //-------------------------------------------------- required public init() { super.init(frame: .zero) } public override init(frame: CGRect) { super.init(frame: .zero) } public required init?(coder: NSCoder) { super.init(coder: coder) } //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var dimensions: CGSize { guard let customSize else { return size.dimensions } return .init(width: customSize, height: customSize) } //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- /// UIImageView used to render the icon. open var imageView = UIImageView().with { $0.isAccessibilityElement = false $0.translatesAutoresizingMaskIntoConstraints = false $0.contentMode = .scaleAspectFill $0.clipsToBounds = true } /// Color of the icon. open var color: UIColor = VDSColor.paletteBlack { didSet { if let hex = color.hexString, !UIColor.isVDSColor(color: color) { print("icon.color is not a VDSColor. Hex: \(hex) is not a supported color") } colorConfiguration = SurfaceColorConfiguration(color, color) } } open var colorConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark) { didSet { setNeedsUpdate() } } /// Size of the icon. open var size: Size = .medium { didSet { setNeedsUpdate() } } /// This will be used to render the icon with corresponding name. open var name: Name? { didSet { setNeedsUpdate() } } /// A custom size of the icon. open var customSize: Int? { didSet { setNeedsUpdate() } } /// The natural size for the receiving view, considering only properties of the view itself. open override var intrinsicContentSize: CGSize { dimensions } //functions //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- /// Called once when a view is initialized and is used to Setup additional UI or other constants and configurations. open override func setup() { super.setup() setContentCompressionResistancePriority(.required, for: .vertical) setContentHuggingPriority(.required, for: .vertical) setContentCompressionResistancePriority(.required, for: .horizontal) setContentHuggingPriority(.required, for: .horizontal) addSubview(imageView) imageView.pinToSuperView() backgroundColor = .clear isAccessibilityElement = true accessibilityTraits = .none accessibilityHint = "image" bridge_accessibilityLabelBlock = { [weak self] in guard let self else { return "" } return name?.rawValue ?? "icon" } } /// Used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() //get the color for the image let imageColor = colorConfiguration.getColor(surface) //get the image name //set the image if let name, let image = UIImage.image(for: name, color: imageColor) { imageView.image = image } else { imageView.image = nil } invalidateIntrinsicContentSize() } /// Resets to default settings. open override func reset() { super.reset() color = VDSColor.paletteBlack imageView.image = nil } } extension UIImage { /// UIImage helper for finding images based on the Icon.Name which uses the internal BundleManager. /// - Parameters: /// - name: Icon.Name rawRepresentable. /// - color: Color to Tint the image with /// - renderingMode: UIImage Rendering mode. /// - Returns: UIImage for this proecess public static func image(for iconName: Icon.Name, color: UIColor? = nil, renderingMode: UIImage.RenderingMode = .alwaysOriginal) -> UIImage? { image(representing: iconName, color: color, renderingMode: renderingMode) } }