// // 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 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 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" } lazy var backgroundColorPickerSelectorView: TokenColorPickerSection = { TokenColorPickerSection(rowTitle: "Background Color", picker: self.picker).with { $0.onSelected = { [weak self] item in guard let self else { return } switch item { case .primary: component.color = .primary case .secondary: component.color = .secondary case .white: component.color = .white case .black: component.color = .black default: break; } } $0.onTokenSelected = { [weak self] color in guard let self else { return } component.color = .custom(color.uiColor) } $0.onColorSelected = { [weak self] color in guard let self else { return } component.color = .custom(color) } } }() lazy var gradientColorView1: ColorPickerView = { return .init().with { $0.onColorSelected = { [weak self] _ in guard let self else { return } updateGradientColors() } } }() lazy var gradientColorView2: ColorPickerView = { return .init().with { $0.onColorSelected = { [weak self] _ in guard let self else { return } updateGradientColors() } } }() override func viewDidLoad() { super.viewDidLoad() let mainView = View() mainView.backgroundColor = .purple ///top let topView = View() topView.backgroundColor = .yellow let topLabel = Label() topLabel.text = "Do you want to Register?" let topButton = Button() topButton.text = "Register" topButton.onClick = { c in print("clicked \(c.text!)") } ///bottom let bottomView = View() bottomView.backgroundColor = .green let bottomLabel = Label() bottomLabel.text = "Forgot your info?" let bottomButton = Button() bottomButton.text = "Forgot Password" bottomButton.onClick = { c in print("clicked \(c.text!)") } mainView.addSubview(topView) mainView.addSubview(bottomView) topView.addSubview(topLabel) topView.addSubview(topButton) bottomView.addSubview(bottomLabel) bottomView.addSubview(bottomButton) let space: CGFloat = 5 topView .pinTop(space*2) .pinLeading(space*2) .pinTrailing(space*2) .pinBottom(anchor: bottomView.topAnchor, constant: space*2) topLabel .pinTop(space) .pinLeading(space) .pinTrailing(space) .pinBottom(anchor: topButton.topAnchor, constant: space) topButton .pinBottom(space) .pinLeading(space) .pinTrailingLessThanOrEqualTo(anchor: nil, constant: space) bottomView .pinLeading(space*2) .pinTrailing(space*2) .pinBottom(space*2) bottomLabel .pinTop(space) .pinLeading(space) .pinTrailing(space) .pinBottom(anchor: bottomButton.topAnchor, constant: space) bottomButton .pinBottom(space) .pinLeading(space) .pinTrailingLessThanOrEqualTo(anchor: nil, constant: space) component.color = .secondary component.accessibilityLabel = "Tile Container" addContentTopView(view: component) component.addContentView(mainView) 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, pinTrailing: false) addFormRow(label: "Clickable", view: clickableSwitch, pinTrailing: false) addFormRow(label: "Width", view: widthTextField) addFormRow(label: "Height", view: heightTextField) addFormRow(label: "Show Border", view: showBorderSwitch, pinTrailing: false) addFormRow(label: "Show Drop Shadow", view: showDropShadowSwitch, pinTrailing: false) append(section: backgroundColorPickerSelectorView) addFormRow(label: "Padding", view: paddingPickerSelectorView) customPaddingRowView = addFormRow(label: "Custom Padding", view: paddingTextField) customPaddingRowView?.isHidden = true addFormRow(label: "Aspect Ratio", view: scalingTypePickerSelectorView) addFormRow(label: "Background Image", view: showBackgroundImageSwitch, pinTrailing: false) 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, let number = number?.cgFloatValue else { self?.heightTextField.text = "" return } if number >= 100 && number < self.view.frame.height * 0.65 { self.component.height = number } else { self.component.height = nil } if let val = heightTextField.text, val.count > 2, number < 100 { heightTextField.text = "" } }.store(in: &subscribers) widthTextField .numberPublisher .sink { [weak self] number in self?.updateComponentConstraint(pinTrailing: number == nil) guard let self, let number = number?.cgFloatValue else { self?.widthTextField.text = "" return } if number >= 100 && number < self.view.frame.width * 0.85 { self.component.width = number } else { self.component.width = nil } if let val = widthTextField.text, val.count > 2, number < 100 { widthTextField.text = "" } }.store(in: &subscribers) } func setupModel() { //setup UI surfacePickerSelectorView.text = component.surface.rawValue backgroundColorPickerSelectorView.setDefault(value: backgroundColor) 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 } 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.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 } } func updateGradientColors(){ if let selectedGradient1Color = gradientColorView1.selectedColor, let selectedGradient2Color = gradientColorView2.selectedColor{ component.backgroundEffect = .gradient(selectedGradient1Color, selectedGradient2Color) } } } 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 { //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, token, custom var color: TileContainer.BackgroundColor? { switch self { case .primary: .primary case .secondary: .secondary case .white: .white case .black: .black case .token: nil 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 } } } }