// // RadioSwatchGroup.swift // VDS // // Created by Matt Bruce on 8/25/22. // import Foundation import UIKit import Combine @objc(VDSRadioSwatchGroup) open class RadioSwatchGroup: SelectorGroupSelectedHandlerBase, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- public override var selectorViews: [RadioSwatch] { didSet { collectionView.reloadData() } } open var selectorModels: [RadioSwatchModel]? { didSet { if let selectorModels { selectorViews = selectorModels.map { model in return RadioSwatch().with { $0.accessibilityLabel = model.accessibileText $0.text = model.text $0.fillImage = model.fillImage $0.primaryColor = model.primaryColor $0.secondaryColor = model.secondaryColor $0.strikethrough = model.strikethrough $0.disabled = model.disabled $0.surface = model.surface $0.inputId = model.inputId $0.value = model.value $0.isSelected = model.selected } } } } } //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- open var label = Label() private let cellSize: CGFloat = 48.0 private let labelSpacing: CGFloat = 24.0 private let labelHeight: CGFloat = 16.0 private let lineSpacing: CGFloat = 12.0 private let itemSpacing: CGFloat = 16.0 fileprivate lazy var collectionView: SelfSizingCollectionView = { let layout = UICollectionViewFlowLayout().with { $0.minimumLineSpacing = lineSpacing $0.minimumInteritemSpacing = itemSpacing } return SelfSizingCollectionView(frame: .zero, collectionViewLayout: layout).with { $0.backgroundColor = .clear $0.showsHorizontalScrollIndicator = false $0.showsVerticalScrollIndicator = false $0.isScrollEnabled = false $0.translatesAutoresizingMaskIntoConstraints = false $0.register(UICollectionViewCell.self, forCellWithReuseIdentifier: "collectionViewCell") } }() //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- /// Whether this object is disabled or not override public var disabled: Bool { didSet { for selector in selectorViews { selector.disabled = disabled } collectionView.reloadData() } } /// Current Surface and this is used to pass down to child objects that implement Surfacable override public var surface: Surface { didSet { for selector in selectorViews { selector.surface = surface } collectionView.reloadData() } } open override func setup() { super.setup() addSubview(label) addSubview(collectionView) label .pinTop() .pinLeading() .pinTrailing() .height(labelHeight) collectionView .pinTop(label.bottomAnchor, labelSpacing) .pinLeading() .pinTrailing() .pinBottom() } open override func layoutSubviews() { super.layoutSubviews() // Accounts for any collection size changes DispatchQueue.main.async { self.collectionView.collectionViewLayout.invalidateLayout() } } public override func initialSetup() { super.initialSetup() collectionView.delegate = self collectionView.dataSource = self } /// Function used to make changes to the View based off a change events or from local properties. open override func updateView() { super.updateView() label.textPosition = .left label.textStyle = .bodySmall label.text = selectedHandler?.text ?? " " label.surface = surface label.disabled = disabled collectionView.reloadData() } public func reload() { collectionView.reloadData() } //-------------------------------------------------- // MARK: - UICollectionViewDelegateFlowLayout //-------------------------------------------------- open func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { return CGSize(width: cellSize, height: cellSize) } //-------------------------------------------------- // MARK: - UICollectionViewDelegate //-------------------------------------------------- open func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool { return !selectorViews[indexPath.row].disabled } open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { didSelect(selector: selectorViews[indexPath.row]) } //-------------------------------------------------- // MARK: - UICollectionViewDataSource //-------------------------------------------------- public func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return selectorViews.count } var cellsubs: [Int: AnyCancellable] = [:] public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) let handler = selectorViews[indexPath.row] handler.onClick = { [weak self] handler in self?.didSelect(selector: handler) } cell.subviews.forEach { $0.removeFromSuperview() } cell.addSubview(handler) handler.pinToSuperView() return cell } public func didSelect(selector: RadioSwatch) { selectedHandler?.toggle() selector.toggle() label.text = selector.text setNeedsUpdate() valueChanged() } } extension RadioSwatchGroup { public struct RadioSwatchModel: Surfaceable, Disabling, Initable { /// Whether this object is disabled or not public var disabled: Bool = false /// Current Surface and this is used to pass down to child objects that implement Surfacable public var surface: Surface public var inputId: String? public var value: AnyHashable? public var selected: Bool = false public var text: String public var fillImage: UIImage? public var primaryColor: UIColor? public var secondaryColor: UIColor? public var strikethrough: Bool = false public var accessibileText: String? public init(disabled: Bool, surface: Surface = .light, inputId: String? = nil, value: AnyHashable? = nil, selected: Bool = false, text: String = "", fillImage: UIImage? = nil, primaryColor: UIColor? = nil, secondaryColor: UIColor? = nil, strikethrough: Bool = false, accessibileText: String? = nil) { self.disabled = disabled self.surface = surface self.inputId = inputId self.value = value self.selected = selected self.text = text self.fillImage = fillImage self.primaryColor = primaryColor self.secondaryColor = secondaryColor self.strikethrough = strikethrough self.accessibileText = accessibileText } public init() { self.init(disabled: false) } } }