adding tabs initial classes
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
This commit is contained in:
parent
3625db96bf
commit
e4efd296bb
@ -43,6 +43,8 @@
|
||||
EA4DB18528CA967F00103EE3 /* SelectorGroupHandlerBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB18428CA967F00103EE3 /* SelectorGroupHandlerBase.swift */; };
|
||||
EA4DB2FD28D3D0CA00103EE3 /* AnyEquatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB2FC28D3D0CA00103EE3 /* AnyEquatable.swift */; };
|
||||
EA4DB30228DCBCA500103EE3 /* Badge.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA4DB30128DCBCA500103EE3 /* Badge.swift */; };
|
||||
EA596ABD2A16B4EC00300C4B /* Tab.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA596ABC2A16B4EC00300C4B /* Tab.swift */; };
|
||||
EA596ABF2A16B4F500300C4B /* Tabs.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA596ABE2A16B4F500300C4B /* Tabs.swift */; };
|
||||
EA5E304C294CBDD00082B959 /* TileContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5E304B294CBDD00082B959 /* TileContainer.swift */; };
|
||||
EA5E30532950DDA60082B959 /* TitleLockup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5E30522950DDA60082B959 /* TitleLockup.swift */; };
|
||||
EA5E3058295105A40082B959 /* Tilelet.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA5E3057295105A40082B959 /* Tilelet.swift */; };
|
||||
@ -164,6 +166,8 @@
|
||||
EA4DB18428CA967F00103EE3 /* SelectorGroupHandlerBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectorGroupHandlerBase.swift; sourceTree = "<group>"; };
|
||||
EA4DB2FC28D3D0CA00103EE3 /* AnyEquatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyEquatable.swift; sourceTree = "<group>"; };
|
||||
EA4DB30128DCBCA500103EE3 /* Badge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Badge.swift; sourceTree = "<group>"; };
|
||||
EA596ABC2A16B4EC00300C4B /* Tab.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tab.swift; sourceTree = "<group>"; };
|
||||
EA596ABE2A16B4F500300C4B /* Tabs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tabs.swift; sourceTree = "<group>"; };
|
||||
EA5E304B294CBDD00082B959 /* TileContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileContainer.swift; sourceTree = "<group>"; };
|
||||
EA5E30522950DDA60082B959 /* TitleLockup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TitleLockup.swift; sourceTree = "<group>"; };
|
||||
EA5E3057295105A40082B959 /* Tilelet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tilelet.swift; sourceTree = "<group>"; };
|
||||
@ -384,6 +388,7 @@
|
||||
EA89200B28B530F0006B9984 /* RadioBox */,
|
||||
EAF7F11428A1470D00B287F5 /* RadioButton */,
|
||||
EA1F265F28B945070033E859 /* RadioSwatch */,
|
||||
EA596ABB2A16B4D500300C4B /* Tabs */,
|
||||
EAC925852911C9DE00091998 /* TextFields */,
|
||||
EA5E304A294CBDBB0082B959 /* TileContainer */,
|
||||
EA5E3056295105930082B959 /* Tilelet */,
|
||||
@ -511,6 +516,15 @@
|
||||
path = Badge;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EA596ABB2A16B4D500300C4B /* Tabs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EA596ABC2A16B4EC00300C4B /* Tab.swift */,
|
||||
EA596ABE2A16B4F500300C4B /* Tabs.swift */,
|
||||
);
|
||||
path = Tabs;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EA5E304A294CBDBB0082B959 /* TileContainer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -827,6 +841,7 @@
|
||||
EAB5FEF5292D371F00998C17 /* ButtonBase.swift in Sources */,
|
||||
EA978EC5291D6AFE00ACC883 /* AnyLabelAttribute.swift in Sources */,
|
||||
EA33622C2891E73B0071C351 /* FontProtocol.swift in Sources */,
|
||||
EA596ABD2A16B4EC00300C4B /* Tab.swift in Sources */,
|
||||
EAF7F11728A1475A00B287F5 /* RadioButton.swift in Sources */,
|
||||
EA985BEE2968A92400F2FF2E /* TitleLockupSubTitleModel.swift in Sources */,
|
||||
EA985BF22968B5BB00F2FF2E /* TitleLockupTextStyle.swift in Sources */,
|
||||
@ -893,6 +908,7 @@
|
||||
EA3361A8288B23300071C351 /* UIColor.swift in Sources */,
|
||||
EAC9257D29119B5400091998 /* TextLink.swift in Sources */,
|
||||
EA1F266628B945070033E859 /* RadioSwatchGroup.swift in Sources */,
|
||||
EA596ABF2A16B4F500300C4B /* Tabs.swift in Sources */,
|
||||
EA985BEC2968A91200F2FF2E /* TitleLockupTitleModel.swift in Sources */,
|
||||
5FC35BE328D51405004EBEAC /* Button.swift in Sources */,
|
||||
);
|
||||
|
||||
@ -6,3 +6,426 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import VDSColorTokens
|
||||
|
||||
import Combine
|
||||
|
||||
//@objc(VDSCollectionViewCell)
|
||||
//open class CollectionViewCell: UICollectionViewCell, Handlerable, ViewProtocol, Resettable {
|
||||
//
|
||||
// //--------------------------------------------------
|
||||
// // MARK: - Combine Properties
|
||||
// //--------------------------------------------------
|
||||
// public var subscribers = Set<AnyCancellable>()
|
||||
//
|
||||
// //--------------------------------------------------
|
||||
// // MARK: - Properties
|
||||
// //--------------------------------------------------
|
||||
// private var initialSetupPerformed = false
|
||||
//
|
||||
// open var surface: Surface = .light { didSet { setNeedsUpdate() }}
|
||||
//
|
||||
// open var disabled: Bool = false { didSet { setNeedsUpdate() } }
|
||||
//
|
||||
// public var shouldUpdateView: Bool = true
|
||||
//
|
||||
// //--------------------------------------------------
|
||||
// // 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()
|
||||
// }
|
||||
//
|
||||
// //--------------------------------------------------
|
||||
// // MARK: - Public Functions
|
||||
// //--------------------------------------------------
|
||||
// open func initialSetup() {
|
||||
// if !initialSetupPerformed {
|
||||
// setup()
|
||||
// setNeedsUpdate()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// open func setup() {}
|
||||
//
|
||||
// open func reset() {}
|
||||
//
|
||||
// open func updateView() {
|
||||
// updateAccessibilityLabel()
|
||||
// }
|
||||
//
|
||||
// open func updateAccessibilityLabel() {}
|
||||
//
|
||||
// open override func prepareForReuse() {
|
||||
// surface = .light
|
||||
// disabled = false
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//@objc(VDSTabsDelegate)
|
||||
//public protocol TabsDelegate {
|
||||
// func shouldSelectItem(_ indexPath: IndexPath, tabs: Tabs) -> Bool
|
||||
// func didSelectItem(_ indexPath: IndexPath, tabs: Tabs)
|
||||
//}
|
||||
//
|
||||
//@objc(VDSTabs)
|
||||
//open class Tabs: View {
|
||||
//
|
||||
// private let layout = UICollectionViewFlowLayout()
|
||||
// public lazy var collectionView: UICollectionView = {
|
||||
// let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout)
|
||||
// collectionView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// collectionView.register(TabItemCell.self, forCellWithReuseIdentifier: TabCellId)
|
||||
// collectionView.backgroundColor = .clear
|
||||
// collectionView.showsVerticalScrollIndicator = false
|
||||
// collectionView.showsHorizontalScrollIndicator = false
|
||||
// collectionView.dataSource = self
|
||||
// collectionView.delegate = self
|
||||
// return collectionView
|
||||
// }()
|
||||
//
|
||||
// open var tabItemModels = [TabItemModel]()
|
||||
//
|
||||
// let bottomScrollView = UIScrollView(frame: .zero)
|
||||
// let bottomContentView = View()
|
||||
// let bottomLine = Line()
|
||||
// let selectionLine = View()
|
||||
// var selectionLineLeadingConstraint: NSLayoutConstraint?
|
||||
// var selectionLineWidthConstraint: NSLayoutConstraint?
|
||||
//
|
||||
// private var widthCell = TabItemCell()
|
||||
//
|
||||
// //delegate
|
||||
// weak public var delegate: TabsDelegate?
|
||||
//
|
||||
// //control var
|
||||
// public var selectedIndex: Int = 0
|
||||
// public var paddingBeforeFirstTab: Bool = true
|
||||
//
|
||||
// //constant
|
||||
// let TabCellId = "TabCell"
|
||||
// public let itemSpacing: CGFloat = 20.0
|
||||
// public let cellHeight: CGFloat = 28.0
|
||||
// public let selectionLineHeight: CGFloat = 4.0
|
||||
// public let minimumItemWidth: CGFloat = 32.0
|
||||
// public let selectionLineMovingTime: TimeInterval = 0.2
|
||||
//
|
||||
// //-------------------------------------------------
|
||||
// // MARK:- Layout Views
|
||||
// //-------------------------------------------------
|
||||
//
|
||||
// open override func reset() {
|
||||
// super.reset()
|
||||
// selectedIndex = 0
|
||||
// paddingBeforeFirstTab = true
|
||||
// }
|
||||
//
|
||||
// open override func setup() {
|
||||
// super.setup()
|
||||
// backgroundColor = VDSColor.backgroundPrimaryLight
|
||||
// addSubview(bottomLine)
|
||||
// setupCollectionView()
|
||||
// setupSelectionLine()
|
||||
// setupConstraints()
|
||||
// }
|
||||
//
|
||||
// func setupCollectionView () {
|
||||
// layout.scrollDirection = .horizontal
|
||||
// layout.minimumLineSpacing = 0
|
||||
// addSubview(collectionView)
|
||||
// }
|
||||
//
|
||||
// func setupSelectionLine() {
|
||||
// bottomScrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
// bottomScrollView.delegate = self
|
||||
// addSubview(bottomScrollView)
|
||||
// bottomScrollView.addSubview(bottomContentView)
|
||||
// selectionLine.backgroundColor = VDSColor.paletteRed
|
||||
// bottomContentView.addSubview(selectionLine)
|
||||
// bringSubviewToFront(bottomScrollView)
|
||||
// }
|
||||
//
|
||||
// func setupConstraints() {
|
||||
// //collection view
|
||||
// collectionView
|
||||
// .pinTop()
|
||||
// .pinLeading()
|
||||
// .pinTrailing()
|
||||
//
|
||||
// collectionView.heightAnchor.constraint(greaterThanOrEqualToConstant: cellHeight).isActive = true
|
||||
//
|
||||
// //selection line
|
||||
// bottomScrollView.heightAnchor.constraint(equalToConstant: selectionLineHeight).isActive = true
|
||||
// bottomScrollView.topAnchor.constraint(equalTo: collectionView.bottomAnchor).isActive = true
|
||||
// bottomScrollView
|
||||
// .pinLeading()
|
||||
// .pinTrailing()
|
||||
//
|
||||
// selectionLine.heightAnchor.constraint(equalToConstant: selectionLineHeight).isActive = true
|
||||
// selectionLine
|
||||
// .pinTop()
|
||||
// .pinBottom()
|
||||
//
|
||||
// selectionLineLeadingConstraint = selectionLine.leadingAnchor.constraint(equalTo: bottomContentView.leadingAnchor)
|
||||
// selectionLineLeadingConstraint?.isActive = true
|
||||
// selectionLineWidthConstraint = selectionLine.widthAnchor.constraint(equalToConstant: minimumItemWidth)
|
||||
// selectionLineWidthConstraint?.isActive = true
|
||||
//
|
||||
// bottomContentView.pinToSuperView()
|
||||
//
|
||||
// //bottom line
|
||||
// bottomLine.topAnchor.constraint(equalTo: bottomScrollView.bottomAnchor).isActive = true
|
||||
// bottomLine
|
||||
// .pinBottom()
|
||||
// .pinLeading()
|
||||
// .pinTrailing()
|
||||
// }
|
||||
//
|
||||
// //-------------------------------------------------
|
||||
// // MARK:- Control Methods
|
||||
// //-------------------------------------------------
|
||||
//
|
||||
// public func selectIndex(_ index: Int, animated: Bool) {
|
||||
// guard tabItemModels.count > 0 else {
|
||||
// selectedIndex = index
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// DispatchQueue.main.async { [weak self] in
|
||||
// guard let self else { return }
|
||||
// let currentIndex = self.selectedIndex
|
||||
// self.selectedIndex = index
|
||||
// self.deselect(indexPath: IndexPath(row: currentIndex, section: 0))
|
||||
// self.selectItem(atIndexPath: IndexPath(row: index, section: 0), animated: animated)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public func reloadData() {
|
||||
// collectionView.reloadData()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
////-------------------------------------------------
|
||||
//// MARK:- Collection View Methods
|
||||
////-------------------------------------------------
|
||||
//
|
||||
//extension Tabs: UICollectionViewDataSource {
|
||||
// public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
// tabItemModels.count
|
||||
// }
|
||||
//
|
||||
// public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
// guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TabCellId, for: indexPath) as? TabItemCell else {
|
||||
// return UICollectionViewCell()
|
||||
// }
|
||||
// let model = tabItemModels[indexPath.row]
|
||||
// cell.tabsCount = tabItemModels.count
|
||||
// cell.tabSelected = indexPath.row == selectedIndex
|
||||
// cell.text = model.text
|
||||
// cell.onClick = model.onClick
|
||||
// return cell
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//extension Tabs: UICollectionViewDelegateFlowLayout {
|
||||
//
|
||||
// public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
|
||||
// guard self.collectionView(collectionView, numberOfItemsInSection: indexPath.section) != 2 else {
|
||||
// // If two tabs, take up the screen
|
||||
// let insets = self.collectionView(collectionView, layout: collectionViewLayout, insetForSectionAt: indexPath.section)
|
||||
// let width = (collectionView.bounds.width / 2.0) - insets.left - insets.right
|
||||
// return CGSize(width: width, height: cellHeight)
|
||||
// }
|
||||
// return CGSize(width: max(minimumItemWidth, getLabelWidth(tabItemModels[indexPath.row]).width), height: cellHeight)
|
||||
// }
|
||||
//
|
||||
// //pre calculate the width of the collection cell
|
||||
// //when user select tabs, it will reload related collectionview, if we use autosize, it would relayout the width, need to keep the cell width constant.
|
||||
// func getLabelWidth(_ model: TabItemModel) -> CGSize {
|
||||
// widthCell.text = model.text
|
||||
// let cgSize = widthCell.label.intrinsicContentSize
|
||||
// widthCell.label.reset()
|
||||
// return cgSize
|
||||
// }
|
||||
//
|
||||
// public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
|
||||
// guard section == 0 else {
|
||||
// return UIEdgeInsets(top: 0, left: itemSpacing, bottom: 0, right: 0)
|
||||
// }
|
||||
// guard paddingBeforeFirstTab else {
|
||||
// return .zero
|
||||
// }
|
||||
// return UIEdgeInsets(top: 0, left: VDSLayout.Spacing.space6X.value, bottom: 0, right: 0)
|
||||
// }
|
||||
//
|
||||
// public func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
|
||||
// // If two tabs, take up the screen, no space between items
|
||||
// guard self.collectionView(collectionView, numberOfItemsInSection: section) != 2 else {
|
||||
// return 0
|
||||
// }
|
||||
// return itemSpacing
|
||||
// }
|
||||
//
|
||||
// public func collectionView(_ collectionView: UICollectionView, shouldSelectItemAt indexPath: IndexPath) -> Bool {
|
||||
// return delegate?.shouldSelectItem(indexPath, tabs: self) ?? true
|
||||
// }
|
||||
//
|
||||
// public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
// selectIndex(indexPath.row, animated: true)
|
||||
// }
|
||||
//
|
||||
// public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
||||
// guard let tabCell = cell as? TabItemCell else { return }
|
||||
// if indexPath.row == selectedIndex {
|
||||
// DispatchQueue.main.async {
|
||||
// self.moveSelectionLine(toIndex: indexPath, animated: false, cell: tabCell)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func deselect(indexPath:IndexPath) {
|
||||
// collectionView.deselectItem(at: indexPath, animated: false)
|
||||
// collectionView.reloadItems(at: [indexPath])
|
||||
// }
|
||||
//
|
||||
// func selectItem(atIndexPath indexPath: IndexPath, animated: Bool) {
|
||||
//
|
||||
// guard tabItemModels.count > 0 else { return }
|
||||
//
|
||||
// collectionView.selectItem(at: indexPath, animated: animated, scrollPosition: .centeredHorizontally)
|
||||
// guard let tabCell = collectionView.cellForItem(at: indexPath) as? TabItemCell else { return }
|
||||
// moveSelectionLine(toIndex: indexPath, animated: animated, cell: tabCell)
|
||||
// tabCell.tabSelected = true
|
||||
// tabCell.setNeedsDisplay()
|
||||
// tabCell.setNeedsLayout()
|
||||
// tabCell.layoutIfNeeded()
|
||||
// if let delegate = delegate {
|
||||
// delegate.didSelectItem(indexPath, tabs: self)
|
||||
// } else if let action = tabItemModels[selectedIndex].onClick {
|
||||
// action()
|
||||
// }
|
||||
// if UIAccessibility.isVoiceOverRunning {
|
||||
// UIAccessibility.post(notification: .layoutChanged, argument: tabCell)
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
//extension Tabs: UIScrollViewDelegate {
|
||||
// public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
// /*bottomScrollview is subview of self, it's not belongs to collectionview.
|
||||
// When collectionview is scrolling, bottomScrollView will stay without moving
|
||||
// Adding collectionview's offset to bottomScrollView, will make the bottomScrollview looks like scrolling with the selected tab item.
|
||||
// */
|
||||
// let offsetX = collectionView.contentOffset.x
|
||||
// bottomScrollView.setContentOffset(CGPoint(x: offsetX, y: bottomScrollView.contentOffset.y), animated: false)
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//
|
||||
////-------------------------------------------------
|
||||
//// MARK:- Bottom Line Methods
|
||||
////-------------------------------------------------
|
||||
//extension Tabs {
|
||||
// func moveSelectionLine(toIndex indexPath: IndexPath, animated: Bool, cell: TabItemCell) {
|
||||
// let size = collectionView(collectionView, layout: layout, sizeForItemAt: indexPath)
|
||||
// let animationBlock = {
|
||||
// [weak self] in
|
||||
// self?.selectionLineWidthConstraint?.constant = size.width
|
||||
// self?.selectionLineLeadingConstraint?.constant = cell.frame.origin.x
|
||||
// self?.bottomContentView.layoutIfNeeded()
|
||||
// }
|
||||
// if animated {
|
||||
// UIView.animate(withDuration: selectionLineMovingTime, animations: animationBlock)
|
||||
// } else {
|
||||
// animationBlock()
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// /// Adjust the line based on the percentage
|
||||
// func progress(from index: Int, toIndex: Int, percentage: CGFloat) {
|
||||
// let fromIndexPath = IndexPath(row: index, section: 0)
|
||||
// let toIndexPath = IndexPath(row: toIndex, section: 0)
|
||||
// guard let fromCell = collectionView.cellForItem(at: fromIndexPath),
|
||||
// let toCell = collectionView.cellForItem(at: toIndexPath) else { return }
|
||||
//
|
||||
// // setting the width for percentage
|
||||
// selectionLineWidthConstraint?.constant = (toCell.bounds.width - fromCell.bounds.width) * percentage + fromCell.bounds.width
|
||||
//
|
||||
// // setting the x for percentage
|
||||
// let originalX = fromCell.frame.origin.x
|
||||
// let toX = toCell.frame.origin.x
|
||||
// let xDifference = toX - originalX
|
||||
// let finalX = (xDifference * percentage) + originalX
|
||||
// selectionLineLeadingConstraint?.constant = finalX
|
||||
//
|
||||
// bottomContentView.layoutIfNeeded()
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//@objc(VDSTabItemCell)
|
||||
//open class TabItemCell: CollectionViewCell {
|
||||
// //--------------------------------------------------
|
||||
// // MARK: - Properties
|
||||
// //--------------------------------------------------
|
||||
// open var label = Label()
|
||||
// open var text: String = "" { didSet { setNeedsUpdate() }}
|
||||
// open var textStyle: TextStyle = .bodyLarge { didSet { setNeedsUpdate() }}
|
||||
// open var tabSelected: Bool = false { didSet { setNeedsUpdate() }}
|
||||
// open var tabsCount: Int = 0 { didSet { setNeedsUpdate() }}
|
||||
// open var onClick: (()->())?
|
||||
//
|
||||
// open override func setup() {
|
||||
// super.setup()
|
||||
// contentView.addSubview(label)
|
||||
// label
|
||||
// .pinLeading()
|
||||
// .pinTrailing()
|
||||
// .pinBottom(6)
|
||||
// label.baselineAdjustment = .alignCenters
|
||||
// }
|
||||
//
|
||||
// open var selectedColorConfiguration: AnyColorable = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark).eraseToAnyColorable()
|
||||
//
|
||||
// open var unSelectedColorConfiguration: AnyColorable = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark).eraseToAnyColorable()
|
||||
//
|
||||
// open override func updateView() {
|
||||
// super.updateView()
|
||||
// label.text = text
|
||||
// label.textStyle = textStyle
|
||||
// label.surface = surface
|
||||
// label.textColorConfiguration = tabSelected ? selectedColorConfiguration : unSelectedColorConfiguration
|
||||
// }
|
||||
//
|
||||
// open override func reset() {
|
||||
// super.reset()
|
||||
// label.reset()
|
||||
// label.textStyle = .bodyLarge
|
||||
// }
|
||||
//
|
||||
// open override func updateAccessibilityLabel() {
|
||||
//
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//public struct TabItemModel {
|
||||
// public var text: String
|
||||
// public var onClick: (() -> ())?
|
||||
//
|
||||
// public init(text: String, onClick: (() -> Void)? = nil) {
|
||||
// self.text = text
|
||||
// self.onClick = onClick
|
||||
// }
|
||||
//}
|
||||
|
||||
@ -6,3 +6,349 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import VDSColorTokens
|
||||
|
||||
public struct TabItemModel {
|
||||
public var text: String
|
||||
public var onClick: (() -> Void)?
|
||||
public var width: CGFloat?
|
||||
|
||||
public init(text: String, onClick: (() -> Void)? = nil, width: CGFloat? = nil) {
|
||||
self.text = text
|
||||
self.onClick = onClick
|
||||
self.width = width
|
||||
}
|
||||
}
|
||||
|
||||
public class Tabs: View {
|
||||
public enum Orientation {
|
||||
case vertical
|
||||
case horizontal
|
||||
}
|
||||
|
||||
public enum IndicatorPosition {
|
||||
case top
|
||||
case bottom
|
||||
}
|
||||
|
||||
public enum Overflow {
|
||||
case scroll
|
||||
case none
|
||||
}
|
||||
|
||||
public enum Size {
|
||||
case medium
|
||||
case large
|
||||
|
||||
public var textStyle: TextStyle {
|
||||
if self == .medium {
|
||||
return .boldBodyLarge
|
||||
} else {
|
||||
return .boldTitleSmall
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum Width {
|
||||
case percentage(CGFloat)
|
||||
case value(CGFloat)
|
||||
}
|
||||
|
||||
public var orientation: Orientation = .horizontal {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var borderLine: Bool = true {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var fillContainer: Bool = true {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var indicatorFillTab: Bool = false {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var indicatorPosition: IndicatorPosition = .bottom {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var minWidth: CGFloat = 44.0 {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var onTabChange: ((Int) -> Void)?
|
||||
|
||||
public var overflow: Overflow = .none {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var selectedIndex: Int = 0 {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var size: Size = .medium {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var sticky: Bool = false {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
public var width: Width = .percentage(0.25) {
|
||||
didSet { setNeedsLayout() }
|
||||
}
|
||||
|
||||
private var tabItems: [TabItem] = []
|
||||
private var tabStackView: UIStackView!
|
||||
private var scrollView: UIScrollView!
|
||||
private var contentView: View!
|
||||
private var borderlineColorConfig = SurfaceColorConfiguration(VDSColor.elementsLowcontrastOnlight, VDSColor.elementsLowcontrastOndark)
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
public convenience required init() {
|
||||
self.init(frame: .zero)
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
commonInit()
|
||||
}
|
||||
|
||||
private func commonInit() {
|
||||
scrollView = UIScrollView()
|
||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.showsHorizontalScrollIndicator = false
|
||||
scrollView.showsVerticalScrollIndicator = false
|
||||
addSubview(scrollView)
|
||||
|
||||
contentView = View()
|
||||
contentView.translatesAutoresizingMaskIntoConstraints = false
|
||||
scrollView.addSubview(contentView)
|
||||
|
||||
tabStackView = UIStackView()
|
||||
tabStackView.axis = .horizontal
|
||||
tabStackView.distribution = .fill
|
||||
tabStackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
contentView.addSubview(tabStackView)
|
||||
|
||||
scrollView.pinToSuperView()
|
||||
contentView.pinToSuperView()
|
||||
tabStackView.pinToSuperView()
|
||||
|
||||
contentView.heightAnchor.constraint(equalTo: scrollView.heightAnchor).isActive = true
|
||||
}
|
||||
|
||||
public func updateTabItems(with models: [TabItemModel]) {
|
||||
// Clear existing tab items
|
||||
for tabItem in tabItems {
|
||||
tabItem.removeFromSuperview()
|
||||
}
|
||||
tabItems.removeAll()
|
||||
|
||||
// Create new tab items from the models
|
||||
for model in models {
|
||||
let tabItem = TabItem()
|
||||
tabItem.text = model.text
|
||||
tabItem.onClick = model.onClick
|
||||
tabItem.width = model.width
|
||||
tabItem.textStyle = size.textStyle
|
||||
tabItems.append(tabItem)
|
||||
tabStackView.addArrangedSubview(tabItem)
|
||||
|
||||
// Add tap gesture recognizer to handle tab selection
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tabItemTapped(_:)))
|
||||
tabItem.isUserInteractionEnabled = true
|
||||
tabItem.addGestureRecognizer(tapGesture)
|
||||
}
|
||||
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
@objc private func tabItemTapped(_ gesture: UITapGestureRecognizer) {
|
||||
guard let tabItem = gesture.view as? TabItem else { return }
|
||||
|
||||
if let selectedIndex = tabItems.firstIndex(of: tabItem) {
|
||||
self.selectedIndex = selectedIndex
|
||||
onTabChange?(selectedIndex)
|
||||
}
|
||||
}
|
||||
|
||||
public override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
// Update tab appearance based on properties
|
||||
|
||||
for (index, tabItem) in tabItems.enumerated() {
|
||||
if selectedIndex == index {
|
||||
// Apply selected style to the current tab
|
||||
if orientation == .vertical {
|
||||
tabItem.indicatorPosition = .left
|
||||
} else {
|
||||
if indicatorPosition == .top {
|
||||
tabItem.indicatorPosition = .top
|
||||
} else {
|
||||
tabItem.indicatorPosition = .bottom
|
||||
}
|
||||
}
|
||||
tabItem.selected = true
|
||||
} else {
|
||||
// Apply default style to other tabs
|
||||
tabItem.indicatorPosition = nil
|
||||
tabItem.selected = false
|
||||
}
|
||||
|
||||
if orientation == .horizontal {
|
||||
tabItem.textPosition = .center
|
||||
} else {
|
||||
tabItem.textPosition = .left
|
||||
}
|
||||
tabItem.surface = surface
|
||||
}
|
||||
|
||||
// Apply border line
|
||||
layer.remove(layerName: "borderLineLayer")
|
||||
|
||||
if borderLine {
|
||||
let borderLineLayer = CALayer()
|
||||
borderLineLayer.name = "borderLineLayer"
|
||||
borderLineLayer.backgroundColor = borderlineColorConfig.getColor(self).cgColor
|
||||
|
||||
if orientation == .horizontal {
|
||||
borderLineLayer.frame = CGRect(x: 0, y: bounds.height - 1, width: bounds.width, height: 1)
|
||||
} else {
|
||||
borderLineLayer.frame = CGRect(x: bounds.width - 1, y: 0, width: 1, height: bounds.height)
|
||||
}
|
||||
|
||||
layer.addSublayer(borderLineLayer)
|
||||
}
|
||||
|
||||
// Apply fill container
|
||||
tabStackView.alignment = fillContainer && orientation == .horizontal ? .fill : .leading
|
||||
|
||||
// Apply indicator fill tab
|
||||
if orientation == .vertical {
|
||||
if indicatorFillTab {
|
||||
tabItems.forEach { $0.label.textAlignment = .left }
|
||||
} else {
|
||||
tabItems.forEach { $0.label.textAlignment = .center }
|
||||
}
|
||||
}
|
||||
|
||||
// Apply indicator position
|
||||
if orientation == .horizontal {
|
||||
if indicatorPosition == .top {
|
||||
tabStackView.alignment = .top
|
||||
} else if indicatorPosition == .bottom {
|
||||
tabStackView.alignment = .bottom
|
||||
}
|
||||
}
|
||||
|
||||
// Apply sticky
|
||||
if sticky && orientation == .vertical {
|
||||
scrollView.pinTop()
|
||||
} else {
|
||||
scrollView.pinTop(layoutMargins.top)
|
||||
}
|
||||
|
||||
// Apply width
|
||||
if orientation == .vertical {
|
||||
switch width {
|
||||
case .percentage(let amount):
|
||||
contentView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: amount).isActive = true
|
||||
case .value(let amount):
|
||||
contentView.widthAnchor.constraint(equalToConstant: amount).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
// Apply overflow
|
||||
if orientation == .horizontal && overflow == .scroll {
|
||||
let contentWidth = tabStackView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).width
|
||||
contentView.widthAnchor.constraint(equalToConstant: contentWidth).isActive = true
|
||||
} else {
|
||||
contentView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
|
||||
}
|
||||
|
||||
// Enable scrolling if necessary
|
||||
let contentWidth = contentView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize).width
|
||||
scrollView.contentSize = CGSize(width: contentWidth, height: scrollView.bounds.height)
|
||||
}
|
||||
}
|
||||
|
||||
public class TabItem: View {
|
||||
public var label: Label = Label()
|
||||
public var onClick: (() -> Void)? { didSet { setNeedsUpdate() } }
|
||||
public var width: CGFloat? { didSet { setNeedsUpdate() } }
|
||||
public var selected: Bool = false { didSet { setNeedsUpdate() } }
|
||||
public var text: String? { didSet { setNeedsUpdate() } }
|
||||
public var textStyle: TextStyle = .bodyMedium
|
||||
public var textPosition: TextPosition = .center { didSet { setNeedsUpdate() } }
|
||||
public var indicatorPosition: UIRectEdge? { didSet { setNeedsUpdate() } }
|
||||
private var labelMinWidthConstraint: NSLayoutConstraint?
|
||||
private var labelWidthConstraint: NSLayoutConstraint?
|
||||
|
||||
private var textColorConfiguration: SurfaceColorConfiguration { selected ? textColorSelectedConfiguration : textColorNonSelectedConfiguration }
|
||||
private var textColorNonSelectedConfiguration = SurfaceColorConfiguration(VDSColor.elementsSecondaryOnlight , VDSColor.elementsSecondaryOnlight)
|
||||
private var textColorSelectedConfiguration = SurfaceColorConfiguration(VDSColor.elementsPrimaryOnlight, VDSColor.elementsPrimaryOndark)
|
||||
|
||||
|
||||
public override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
addSubview(label)
|
||||
backgroundColor = .clear
|
||||
label.backgroundColor = .clear
|
||||
|
||||
label.translatesAutoresizingMaskIntoConstraints = false
|
||||
label
|
||||
.pinTop(5)
|
||||
.pinLeading(5)
|
||||
.pinTrailing(5)
|
||||
.pinBottom(6)
|
||||
labelMinWidthConstraint = label.widthAnchor.constraint(greaterThanOrEqualToConstant: 44.0)
|
||||
labelMinWidthConstraint?.isActive = true
|
||||
labelWidthConstraint = label.widthAnchor.constraint(equalToConstant: 44.0)
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(tabItemTapped))
|
||||
addGestureRecognizer(tapGesture)
|
||||
}
|
||||
|
||||
public required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
required public convenience init() {
|
||||
self.init(frame: .zero)
|
||||
}
|
||||
|
||||
@objc private func tabItemTapped() {
|
||||
onClick?()
|
||||
}
|
||||
|
||||
public override func updateView() {
|
||||
label.textPosition = textPosition
|
||||
label.text = text
|
||||
label.textStyle = textStyle
|
||||
label.textColor = textColorConfiguration.getColor(self)
|
||||
if let width {
|
||||
labelMinWidthConstraint?.isActive = false
|
||||
labelWidthConstraint?.constant = width
|
||||
labelWidthConstraint?.isActive = true
|
||||
} else {
|
||||
labelWidthConstraint?.isActive = false
|
||||
labelMinWidthConstraint?.isActive = true
|
||||
}
|
||||
|
||||
if let indicatorPosition {
|
||||
addBorder(side: indicatorPosition, width: 4.0, color: .red)
|
||||
} else {
|
||||
removeBorders()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user