// // RadioSwatchGroup.swift // VDS // // Created by Matt Bruce on 8/25/22. // import Foundation import UIKit public class RadioSwatchGroup: RadioSwatchGroupBase { public override func didSelect(selector: DefaultRadioSwatchModel) { //reset the old model //see if there is a selected one and then get the cached version if let selectedModel { let oldSelectedModel = selectedModel.copyWith { $0.selected = false } replace(viewModel: oldSelectedModel) } //set the new model let newSelectedModel = selector.copyWith { $0.selected = true } label.text = newSelectedModel.text replace(viewModel: newSelectedModel) sendActions(for: .valueChanged) } } public class RadioSwatchGroupBase & UIControl>: Control, Changable, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, UICollectionViewDelegate { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- public var selectedModel: ModelHandlerType.ModelType? { return model.selectedModel } public var onChange: Blocks.ActionBlock? //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- public 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 private var collectionViewHeight: NSLayoutConstraint? private var collectionViewWidth: NSLayoutConstraint? private lazy var collectionView: UICollectionView = { let layout = UICollectionViewFlowLayout().with { $0.minimumLineSpacing = lineSpacing $0.minimumInteritemSpacing = itemSpacing } return UICollectionView(frame: .zero, collectionViewLayout: layout).with { $0.backgroundColor = .clear $0.showsHorizontalScrollIndicator = false $0.showsVerticalScrollIndicator = false $0.isScrollEnabled = false $0.translatesAutoresizingMaskIntoConstraints = false $0.register(CollectionViewCell.self, forCellWithReuseIdentifier: "collectionViewCell") } }() //-------------------------------------------------- // MARK: - Overrides //-------------------------------------------------- override public var disabled: Bool { didSet { updateSelectors() } } override public var surface: Surface { didSet { updateSelectors() } } open override func setup() { super.setup() isAccessibilityElement = true accessibilityTraits = .button addSubview(label) addSubview(collectionView) NSLayoutConstraint.activate([ label.topAnchor.constraint(equalTo: topAnchor), label.leadingAnchor.constraint(equalTo: leadingAnchor), label.trailingAnchor.constraint(equalTo: trailingAnchor), label.heightAnchor.constraint(equalToConstant: labelHeight), collectionView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: labelSpacing), collectionView.leadingAnchor.constraint(equalTo: leadingAnchor), collectionView.trailingAnchor.constraint(equalTo: trailingAnchor), collectionView.bottomAnchor.constraint(equalTo: bottomAnchor), ]) //TODO: Look at this width stuff, we should NOT need it! collectionViewWidth = collectionView.widthAnchor.constraint(equalToConstant: cellSize * 20) collectionViewWidth?.isActive = true collectionViewHeight = collectionView.heightAnchor.constraint(equalToConstant: cellSize) collectionViewHeight?.isActive = true } open override func layoutSubviews() { super.layoutSubviews() // Accounts for any collection size changes setHeight() DispatchQueue.main.async { self.collectionView.collectionViewLayout.invalidateLayout() } } open func setHeight() { let swatches = model.selectors guard swatches.count > 0 else { collectionViewHeight?.constant = 0 return } // Calculate the height let swatchesInRow = floor(CGFloat(collectionView.bounds.width/(cellSize + itemSpacing))) let numberOfRows = ceil(CGFloat(swatches.count)/swatchesInRow) let height = (numberOfRows * cellSize) + (itemSpacing * (numberOfRows-1)) collectionViewHeight?.constant = CGFloat(height) } open override func shouldUpdateView(viewModel: ModelType) -> Bool { return viewModel != model } public override func initialSetup() { super.initialSetup() collectionView.delegate = self collectionView.dataSource = self } open override func updateView(viewModel: ModelType) { label.set(with: viewModel.labelModel) collectionView.reloadData() setNeedsLayout() } //Refactor into new CollectionView Selector protocol public func updateSelectors(){ let selectors = model.selectors.compactMap { existing in return existing.copyWith { $0.disabled = disabled $0.surface = surface } } model.selectors = selectors } public func replace(viewModel: ModelHandlerType.ModelType){ if let index = model.selectors.firstIndex(where: { element in return element.inputId == viewModel.inputId }) { model.selectors[index] = viewModel } } //-------------------------------------------------- // 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 !model.selectors[indexPath.row].disabled } open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let cell = collectionView.cellForItem(at: indexPath) as? CollectionViewCell else { return } didSelect(selector: cell.model) } open func collectionView(_ collectionView: UICollectionView, didDeselectItemAt indexPath: IndexPath) { guard let cell = collectionView.cellForItem(at: indexPath) as? CollectionViewCell else { return } cell.isSelected = false } //-------------------------------------------------- // MARK: - UICollectionViewDataSource //-------------------------------------------------- public func numberOfSections(in collectionView: UICollectionView) -> Int { return 1 } public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return model.selectors.count } public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionViewCell", for: indexPath) as? CollectionViewCell let model = model.selectors[indexPath.row] cell?.modelHandler.isUserInteractionEnabled = false cell?.set(with: model) return cell ?? UICollectionViewCell() } open func didSelect(selector: ModelHandlerType.ModelType) { fatalError("Must override didSelect") } }