// // TileContainer.swift // VDS // // Created by Matt Bruce on 12/16/22. // import Foundation import VDSColorTokens import VDSFormControlsTokens import UIKit @objc(VDSTileContainer) open class TileContainer: Control { //-------------------------------------------------- // 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: - Enums //-------------------------------------------------- public enum BackgroundColor: String, CaseIterable { case white case black case gray case transparent } public enum Padding: String, CaseIterable { case padding2X case padding4X case padding6X case padding8X case padding12X public var value: CGFloat { switch self { case .padding2X: return VDSLayout.Spacing.space2X.value case .padding4X: return VDSLayout.Spacing.space4X.value case .padding6X: return VDSLayout.Spacing.space6X.value case .padding8X: return VDSLayout.Spacing.space8X.value case .padding12X: return VDSLayout.Spacing.space12X.value } } } public enum AspectRatio: String, CaseIterable { case ratio1x1 = "1:1" case ratio3x4 = "3:4" case ratio4x3 = "4:3" case ratio2x3 = "2:3" case ratio3x2 = "3:2" case ratio9x16 = "9:16" case ratio16x9 = "16:9" case ratio1x2 = "1:2" case ratio2x1 = "2:1" case none } //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- public var backgroundImage: UIImage? { didSet{ didChange() } } public var containerView = View().with { $0.isUserInteractionEnabled = false } public var highlightView = View().with { $0.isUserInteractionEnabled = false } public var color: BackgroundColor = .white { didSet{ didChange() } } public var padding: Padding = .padding4X { didSet{ didChange() } } public var aspectRatio: AspectRatio = .ratio1x1 { didSet{ didChange() } } public var imageFallbackColor: Surface = .light { didSet{ didChange() } } private var _width: CGFloat? public var width: CGFloat? { get { return _width } set { if let newValue, newValue > 100 { _width = newValue } else { _width = nil } didChange() } } private var _height: CGFloat? public var height: CGFloat? { get { return _height } set { if let newValue, newValue > 44 { _height = newValue } else { _height = nil } didChange() } } public var showBorder: Bool = false { didSet{ didChange() } } public var showDropShadows: Bool = false { didSet{ didChange() } } //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- private var backgroundImageView = UIImageView().with { $0.translatesAutoresizingMaskIntoConstraints = false $0.contentMode = .scaleAspectFill $0.clipsToBounds = true } //-------------------------------------------------- // MARK: - Constraints //-------------------------------------------------- internal var widthConstraint: NSLayoutConstraint? internal var heightConstraint: NSLayoutConstraint? internal var heightGreaterThanConstraint: NSLayoutConstraint? internal var containerTopConstraint: NSLayoutConstraint? internal var containerBottomConstraint: NSLayoutConstraint? internal var containerLeadingConstraint: NSLayoutConstraint? internal var containerTrailingConstraint: NSLayoutConstraint? //functions //-------------------------------------------------- // MARK: - Lifecycle //-------------------------------------------------- open override func setup() { super.setup() addSubview(backgroundImageView) addSubview(containerView) addSubview(highlightView) widthConstraint = widthAnchor.constraint(equalToConstant: 0) heightGreaterThanConstraint = heightAnchor.constraint(greaterThanOrEqualToConstant: 44.0) heightGreaterThanConstraint?.isActive = false heightConstraint = heightAnchor.constraint(equalToConstant: 0) backgroundImageView.pinToSuperView() backgroundImageView.isUserInteractionEnabled = false backgroundImageView.isHidden = true containerView.backgroundColor = .clear containerTopConstraint = containerView.topAnchor.constraint(equalTo: topAnchor, constant: padding.value) containerTopConstraint?.isActive = true containerBottomConstraint = containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: padding.value) containerBottomConstraint?.isActive = true containerLeadingConstraint = containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding.value) containerLeadingConstraint?.isActive = true containerTrailingConstraint = containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: padding.value) containerTrailingConstraint?.isActive = true highlightView.pinToSuperView() highlightView.isHidden = true highlightView.backgroundColor = .clear //corner radius layer.cornerRadius = cornerRadius backgroundImageView.layer.cornerRadius = cornerRadius highlightView.layer.cornerRadius = cornerRadius } open override func reset() { super.reset() } //-------------------------------------------------- // MARK: - Configuration //-------------------------------------------------- private let cornerRadius = VDSFormControls.borderradius * 2 private var backgroundColorConfig = BackgroundColorConfiguration() private var borderColorConfig = SurfaceColorConfiguration().with { $0.lightColor = VDSColor.elementsLowContrastOnLight $0.darkColor = VDSColor.elementsLowContrastOnDark } private var imageFallbackColorConfig = SurfaceColorConfiguration().with { $0.lightColor = VDSColor.backgroundPrimaryLight $0.darkColor = VDSColor.backgroundPrimaryDark } private var hightLightViewColorConfig = SurfaceColorConfiguration().with { $0.lightColor = VDSColor.paletteWhite.withAlphaComponent(0.3) $0.darkColor = VDSColor.paletteBlack.withAlphaComponent(0.3) } //-------------------------------------------------- // MARK: - Private Functions //-------------------------------------------------- private func ratioSize(for width: CGFloat) -> CGSize { var height: CGFloat = width switch aspectRatio { case .ratio1x1: break; case .ratio3x4: height = (4 / 3) * width case .ratio4x3: height = (3 / 4) * width case .ratio2x3: height = (3 / 2) * width case .ratio3x2: height = (2 / 3) * width case .ratio9x16: height = (16 / 9) * width case .ratio16x9: height = (9 / 16) * width case .ratio1x2: height = (2 / 1) * width case .ratio2x1: height = (1 / 2) * width default: break } return CGSize(width: width, height: height) } //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- open override func updateView() { super.updateView() highlightView.backgroundColor = hightLightViewColorConfig.getColor(self) highlightView.isHidden = !isHighlighted if let backgroundImage { backgroundImageView.image = backgroundImage backgroundImageView.isHidden = false backgroundColor = imageFallbackColorConfig.getColor(self) } else { backgroundImageView.isHidden = true backgroundColor = backgroundColorConfig.getColor(self) } layer.borderColor = borderColorConfig.getColor(self).cgColor layer.borderWidth = showBorder ? VDSFormControls.widthBorder : 0 containerTopConstraint?.constant = padding.value containerLeadingConstraint?.constant = padding.value containerBottomConstraint?.constant = -padding.value containerTrailingConstraint?.constant = -padding.value if let width, aspectRatio == .none && height == nil{ widthConstraint?.constant = width widthConstraint?.isActive = true heightConstraint?.isActive = false heightGreaterThanConstraint?.isActive = true } else if let height, let width { widthConstraint?.constant = width heightConstraint?.constant = height heightConstraint?.isActive = true widthConstraint?.isActive = true heightGreaterThanConstraint?.isActive = false } else if let width { let size = ratioSize(for: width) widthConstraint?.constant = size.width heightConstraint?.constant = size.height widthConstraint?.isActive = true heightConstraint?.isActive = true heightGreaterThanConstraint?.isActive = false } else { widthConstraint?.isActive = false heightConstraint?.isActive = false } } //-------------------------------------------------- // MARK: - Public Functions //-------------------------------------------------- public func addContentView(_ view: UIView, shouldPin: Bool = true) { containerView.addSubview(view) if shouldPin { view.pinToSuperView() } } } extension TileContainer { class BackgroundColorConfiguration: ObjectColorable { typealias ObjectType = TileContainer required init() { } func getColor(_ object: TileContainer) -> UIColor { switch object.color { case .white: return VDSColor.backgroundPrimaryLight case .black: return VDSColor.backgroundPrimaryDark case .gray: return VDSColor.backgroundSecondaryLight case .transparent: return UIColor.clear } } } }