// // 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) initialSetup() } public override init(frame: CGRect) { super.init(frame: .zero) initialSetup() } public required init?(coder: NSCoder) { super.init(coder: coder) initialSetup() } public enum ContainerBackgroundColor: String, CaseIterable { case white case black case gray case transparent } public enum ContainerPadding: String, EnumSubset { case spacing2X case spacing4X case spacing6X case spacing8X case spacing12X public var defaultValue: VDSLayout.Spacing { .space4X } } public enum ContainerScalingType: 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() public var highlightView = View() public var containerBackgroundColor: ContainerBackgroundColor = .white { didSet{ didChange() } } public var containerPadding: ContainerPadding = .spacing4X { didSet{ didChange() } } public var aspectRatio: ContainerScalingType = .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.isUserInteractionEnabled = false containerView.backgroundColor = .clear containerTopConstraint = containerView.topAnchor.constraint(equalTo: topAnchor, constant: containerPadding.value.doubleValue) containerTopConstraint?.isActive = true containerBottomConstraint = containerView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: containerPadding.value.doubleValue) containerBottomConstraint?.isActive = true containerLeadingConstraint = containerView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: containerPadding.value.doubleValue) containerLeadingConstraint?.isActive = true containerTrailingConstraint = containerView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: containerPadding.value.doubleValue) containerTrailingConstraint?.isActive = true highlightView.pinToSuperView() highlightView.isUserInteractionEnabled = false highlightView.isHidden = true highlightView.backgroundColor = .clear //corner radius layer.cornerRadius = cornerRadius backgroundImageView.layer.cornerRadius = cornerRadius highlightView.layer.cornerRadius = cornerRadius } public 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: - State //-------------------------------------------------- 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) } 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 = containerPadding.value.doubleValue containerLeadingConstraint?.constant = containerPadding.value.doubleValue containerBottomConstraint?.constant = -containerPadding.value.doubleValue containerTrailingConstraint?.constant = -containerPadding.value.doubleValue 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 } } public func addContentView(_ view: UIView, shouldPin: Bool = true) { containerView.addSubview(view) if shouldPin { view.pinToSuperView() } } class BackgroundColorConfiguration: ObjectColorable { typealias ObjectType = TileContainer required init() { } func getColor(_ object: TileContainer) -> UIColor { switch object.containerBackgroundColor { case .white: return VDSColor.backgroundPrimaryLight case .black: return VDSColor.backgroundPrimaryDark case .gray: return VDSColor.backgroundSecondaryLight case .transparent: return UIColor.clear } } } }