// // TileContainerViewController.swift // VDSSample // // Created by Matt Bruce on 12/16/22. // import Foundation import UIKit import VDS import VDSCoreTokens import Combine class TileContainerViewController: BaseViewController { lazy var colorPicker: UIColorPickerViewController = { let picker = UIColorPickerViewController() picker.delegate = self return picker }() lazy var backgroundColorPickerSelectorView = { PickerSelectorView(title: "white", picker: self.picker, items: BackgroundColor.allCases) }() lazy var imageFallbackColorPickerSelectorView = { SurfacePickerSelectorView(picker: self.picker) }() lazy var paddingPickerSelectorView = { PickerSelectorView(title: "16", picker: self.picker, items: Padding.allCases) }() lazy var scalingTypePickerSelectorView = { PickerSelectorView(title: "white", picker: self.picker, items: TileContainer.AspectRatio.allCases) }() lazy var backgroundEffectSelectorView = { PickerSelectorView(title: "none", picker: self.picker, items: BackgroundEffect.allCases) }() var gradientColorsFormStackView = FormSection().with { $0.isHidden = true } var backgroundColor: BackgroundColor = .secondary var padding: Padding = .padding4X var isLinkSwitch = Toggle() var clickableSwitch = Toggle() var showBackgroundImageSwitch = Toggle() var showBorderSwitch = Toggle() var showDropShadowSwitch = Toggle() var backgroundImage = UIImage(named: "backgroundTest")! var customPaddingRowView: UIView? var anyCancellable: AnyCancellable? var contentAreaBackgroundColorButton = Button().with { instance in instance.size = .small instance.use = .secondary instance.text = "Select" } var paddingTextField = NumericField().with { $0.placeholder = "Custom Padding" } var heightTextField = NumericField().with { $0.placeholder = "Minimum 100px else it will occupy full container" } var widthTextField = NumericField().with { $0.placeholder = "Minimum 100px else it will occupy full container" } var colorPickerType: ColorPickerType = .backgroundColor enum ColorPickerType { case backgroundColor, gradientColor1, gradientColor2, contentViewBackgroundColor } lazy var gradientColorView1: ColorPickerView = { return .init(with: ColorPickerType.gradientColor1) { [weak self] picker in self?.colorPickerType = picker.pickerType self?.selectedColorTapped(picker) } }() lazy var gradientColorView2: ColorPickerView = { return .init(with: ColorPickerType.gradientColor2) { [weak self] picker in self?.colorPickerType = picker.pickerType self?.selectedColorTapped(picker) } }() override func viewDidLoad() { super.viewDidLoad() addContentTopView(view: .makeWrapper(for: component)) component.width = 300 component.color = .secondary component.accessibilityLabel = "Tile Container" let level2View = View() level2View.backgroundColor = .purple let level3View = View() level3View.backgroundColor = .yellow level2View.addSubview(level3View) level3View.pinToSuperView(.uniform(15)) let level2label = Label() level2label.text = "Level 3 Label" level2label.isAccessibilityElement = true level2label.accessibilityLabel = "Level 3 Accessible Label" level3View.addSubview(level2label) level2label.pinTop().pinLeading() let level3Button = Button() level3Button.setTitle("Level 3 Button", for: .normal) level3Button.isAccessibilityElement = true level3Button.accessibilityLabel = "Level 3 Accessible Button" level3View.addSubview(level3Button) level3Button.pinTop(anchor: level2label.bottomAnchor, constant: 5) level3Button.pinLeading() let level4View = View() level4View.backgroundColor = .green level3View.addSubview(level4View) level4View.pinTop(anchor: level3Button.bottomAnchor, constant: 10) level4View.pinLeading() level4View.pinBottom() level4View.pinTrailing() let level4Label = Label() level4Label.text = "Level 4 Label" level4Label.isAccessibilityElement = true level4Label.accessibilityLabel = "Level 4 Accessible Label" level4View.addSubview(level4Label) level4Label.pinTop().pinLeading() let level4Button = Button() level4Button.setTitle("Level 4 Button", for: .normal) level4Button.isAccessibilityElement = true level4Button.accessibilityLabel = "Level 4 Accessible Button" level4View.addSubview(level4Button) level4Button.pinTop(anchor: level4Label.bottomAnchor, constant: 5) level4Button.pinLeading().pinBottom() component.addContentView(level2View) setupPicker() setupModel() } override func setupForm(){ super.setupForm() formStackView.addArrangedSubview(Label().with { $0.textStyle = .boldBodyLarge $0.text = "This object does NOT reflect normal \"surface\" changes, all properties are maually set" }) formStackView.addArrangedSubview(Label().with { $0.text = "For testing max width is limited to 85% of view's width & 65% view's height." }) addFormRow(label: "Surface", view: surfacePickerSelectorView) addFormRow(label: "Is Link", view: isLinkSwitch) addFormRow(label: "Clickable", view: clickableSwitch) addFormRow(label: "Width", view: widthTextField) addFormRow(label: "Height", view: heightTextField) addFormRow(label: "Show Border", view: showBorderSwitch) addFormRow(label: "Show Drop Shadow", view: showDropShadowSwitch) addFormRow(label: "Background Color", view: backgroundColorPickerSelectorView) addFormRow(label: "Padding", view: paddingPickerSelectorView) customPaddingRowView = addFormRow(label: "Custom Padding", view: paddingTextField) customPaddingRowView?.isHidden = true let rowView = addFormRow(label: "Content area BG color(only for padding validation)", view: contentAreaBackgroundColorButton) if let rowView = rowView as? UIStackView { rowView.alignment = .top } addFormRow(label: "Aspect Ratio", view: scalingTypePickerSelectorView) addFormRow(label: "Background Image", view: showBackgroundImageSwitch) addFormRow(label: "Image Fallback Color", view: imageFallbackColorPickerSelectorView) addFormRow(label: "Background Effect", view: backgroundEffectSelectorView) //Gradient Section gradientColorsFormStackView.addFormRow(label: "Gradient Color1", view: gradientColorView1) gradientColorsFormStackView.addFormRow(label: "Gradient Color2", view: gradientColorView2) append(section: gradientColorsFormStackView) anyCancellable = paddingTextField.textPublisher.receive(on: RunLoop.main).sink { [weak self] value in if let value = Float(value) { self?.component.padding = .custom(CGFloat(value)) } } isLinkSwitch.onChange = { [weak self] sender in guard let self else { return } self.component.accessibilityTraits = sender.isOn ? .link : .button } clickableSwitch.onChange = { [weak self] sender in guard let self else { return } if sender.isOn { self.component.onClick = { _ in print("you click on me!") } } else { self.component.onClick = nil } } showBackgroundImageSwitch.onChange = { [weak self] sender in if let image = self?.backgroundImage, sender.isOn { self?.component.backgroundImage = image } else { self?.component.backgroundImage = nil } } showBorderSwitch.onChange = { [weak self] sender in self?.component.showBorder = sender.isOn } showDropShadowSwitch.onChange = { [weak self] sender in self?.component.showDropShadow = sender.isOn } heightTextField .numberPublisher .sink { [weak self] number in guard let self else { return } if let value = number?.cgFloatValue, value >= 100 && value < self.view.frame.height * 0.65 { self.component.height = value self.component.layoutIfNeeded() } }.store(in: &subscribers) widthTextField .numberPublisher .sink { [weak self] number in guard let self else { return } if let value = number?.cgFloatValue, value >= 100 && value < self.view.frame.width * 0.85 { self.component.width = value self.component.layoutIfNeeded() } }.store(in: &subscribers) } func setupModel() { //setup UI surfacePickerSelectorView.text = component.surface.rawValue backgroundColorPickerSelectorView.text = backgroundColor.rawValue paddingPickerSelectorView.text = padding.rawValue scalingTypePickerSelectorView.text = component.aspectRatio.rawValue widthTextField.text = component.width != nil ? "\(component.width!)" : "" heightTextField.text = component.height != nil ? "\(component.height!)" : "" } func setupPicker(){ surfacePickerSelectorView.onPickerDidSelect = { [weak self] item in self?.component.surface = item self?.contentTopView.backgroundColor = item.color } backgroundColorPickerSelectorView.onPickerDidSelect = { [weak self] item in guard let self else { return } if let color = item.color { self.component.color = color } else { self.colorPickerType = .backgroundColor self.present(self.colorPicker, animated: true) } } backgroundEffectSelectorView.onPickerDidSelect = { [weak self] in guard let self else { return } if let effect = $0.effect { self.component.backgroundEffect = effect self.gradientColorsFormStackView.isHidden = true self.gradientColorView1.selectedColor = nil self.gradientColorView2.selectedColor = nil } else { self.colorPickerType = .gradientColor1 self.gradientColorsFormStackView.isHidden = false } } scalingTypePickerSelectorView.onPickerDidSelect = { [weak self] item in self?.component.aspectRatio = item self?.component.layoutIfNeeded() self?.showDebug(show: self?.debugViewSwitch.isOn ?? false) } paddingPickerSelectorView.onPickerDidSelect = { [weak self] item in if let value = item.value { self?.paddingTextField.text = "" self?.component.padding = value self?.customPaddingRowView?.isHidden = true } else { self?.customPaddingRowView?.isHidden = false } } imageFallbackColorPickerSelectorView.onPickerDidSelect = { [weak self] item in self?.component.imageFallbackColor = item } contentAreaBackgroundColorButton.onClick = { [weak self] _ in guard let self else { return } self.colorPickerType = .contentViewBackgroundColor self.colorPicker.selectedColor = self.component.contentView.backgroundColor ?? .white self.present(self.colorPicker, animated: true) } } } extension TileContainerViewController: ComponentSampleable { static func makeSample() -> ComponentSample { let component = Self.makeComponent() component.width = 150 component.color = .primary return ComponentSample(component: component, trailingPinningType: .lessThanOrEqual) } } extension TileContainerViewController: UIColorPickerViewControllerDelegate { func selectedColorTapped(_ picker: ColorPickerView) { let selectedColor = picker.selectedColor if let selectedColor { colorPicker.selectedColor = selectedColor } present(colorPicker, animated: true) } func colorPickerViewControllerDidFinish(_ viewController: UIColorPickerViewController) { dismiss(animated: true) } func colorPickerViewControllerDidSelectColor(_ viewController: UIColorPickerViewController) { let color = viewController.selectedColor switch colorPickerType { case .contentViewBackgroundColor: component.contentView.backgroundColor = color case .backgroundColor: component.color = .custom(color) case .gradientColor1: gradientColorView1.selectedColor = viewController.selectedColor updateGradientColors() case .gradientColor2: gradientColorView2.selectedColor = viewController.selectedColor updateGradientColors() } } func updateGradientColors(){ if let selectedGradient1Color = gradientColorView1.selectedColor, let selectedGradient2Color = gradientColorView2.selectedColor{ component.backgroundEffect = .gradient(selectedGradient1Color, selectedGradient2Color) } } } extension TileContainerViewController { //Created new BackgroundEffect enum for sample app only. Since we defined enum with associated value color defined in TileContainer cannot be RawRepresentable & CaseIterable enum BackgroundEffect: String, CaseIterable { case transparency case gradient case none var rawValue: String { switch self { case .gradient: "gradient (select gradient colors to apply)" default: String(describing: self) } } var effect: TileContainer.BackgroundEffect? { return switch self { case .transparency: .transparency case .gradient: nil case .none: TileContainer.BackgroundEffect.none } } } //Created new BackgroundColor enum for sample app only. Since we defined enum with associated value color defined in TileContainer cannot be RawRepresentable & CaseIterable enum BackgroundColor: String, CaseIterable { case primary, secondary, white, black, custom var color: TileContainer.BackgroundColor? { switch self { case .primary: .primary case .secondary: .secondary case .white: .white case .black: .black case .custom: nil } } } //Internal helper enum to map padding & pass custom padding values public enum Padding: String, CaseIterable { case padding3X case padding4X case padding6X case padding8X case padding12X case custom public var value: TileContainer.Padding? { return switch self { case .padding3X: .padding3X case .padding4X: .padding4X case .padding6X: .padding6X case .padding8X: .padding8X case .padding12X: .padding12X case .custom: nil } } } }