diff --git a/VDS/Components/Tabs/Tabs.swift b/VDS/Components/Tabs/Tabs.swift index 0888feea..8170a292 100644 --- a/VDS/Components/Tabs/Tabs.swift +++ b/VDS/Components/Tabs/Tabs.swift @@ -9,7 +9,7 @@ import Foundation import UIKit import VDSColorTokens -public class Tabs: View { +open class Tabs: View { //-------------------------------------------------- // MARK: - Enums @@ -61,49 +61,77 @@ public class Tabs: View { //-------------------------------------------------- // MARK: - Public Properties //-------------------------------------------------- - public var onTabChange: ((Int) -> Void)? - public var orientation: Orientation = .horizontal { didSet { updateTabItems() } } - public var borderLine: Bool = true { didSet { setNeedsUpdate() } } - public var fillContainer: Bool = false { didSet { updateTabItems() } } - public var indicatorFillTab: Bool = false { didSet { setNeedsUpdate() } } - public var indicatorPosition: IndicatorPosition = .bottom { didSet { setNeedsUpdate() } } - public var minWidth: CGFloat = 44.0 { didSet { setNeedsUpdate() } } - public var overflow: Overflow = .scroll { didSet { updateTabItems() } } - public var selectedIndex: Int = 0 { didSet { setNeedsUpdate() } } - public var size: Size = .medium { didSet { setNeedsUpdate() } } - public var sticky: Bool = false { didSet { setNeedsUpdate() } } - public var tabModels: [TabModel] = [] { didSet { updateTabItems() } } + ///An optional callback that is called when the selectedIndex changes. Passes parameters (event, tabIndex). + open var onTabChange: ((Int) -> Void)? + + //Determines the layout of the Tabs, defaults to horizontal + open var orientation: Orientation = .horizontal { didSet { if oldValue != orientation { updateTabItems() } } } + + ///When true, Tabs will have border line. If false is passed then the border line won't be visible. + open var borderLine: Bool = true { didSet { setNeedsUpdate() } } + + ///It will fill the Tabs to the width of the compoent and all Tabs will be in equal width when orientation is horizontal. This is recommended when there are no more than 2-3 tabs. + open var fillContainer: Bool = false { didSet { updateTabItems() } } + + ///When true, Tabs will be sticky to top of page, when orientation is vertical. + open var indicatorFillTab: Bool = false { didSet { setNeedsUpdate() } } + + ///Sets the Position of the Selected/Hover Border Accent for All Tabs, only for Horizontal Orientation + open var indicatorPosition: IndicatorPosition = .bottom { didSet { setNeedsUpdate() } } + + ///Minimum Width for All Tabs, when orientation is horizontal. + open var minWidth: CGFloat = 44.0 { didSet { setNeedsUpdate() } } + + ///If set to 'scroll', Tabs can be overflow and scrollable. With 'none', tabs will not overflow and labels will be wrapped to multiple lines if the label text is long. + open var overflow: Overflow = .scroll { didSet { updateTabItems() } } + + ///The initial Selected Tab's index and is set once a Tab is clicked + open var selectedIndex: Int = 0 { didSet { setNeedsUpdate() } } - //rules for width - private var _width: Width = .percentage(0.25) - public var width: Width { - get { - return _width - } - set { - switch newValue { - case .percentage(let percentage): - if percentage >= 0 && percentage <= 1 { - _width = newValue - setNeedsUpdate() - } else { - print("Invalid percentage value. It should be between 0 and 1.") - } - case .value(let value): - if value >= minWidth { - _width = newValue - setNeedsUpdate() - } else { - print("Invalid value. It should be greater than or equal to \(minWidth).") - } - } - } - } + ///Determines the size of the Tabs TextStyle + open var size: Size = .medium { didSet { updateTabItems() } } + + ///When true, Tabs will be sticky to top of page, when orientation is vertical. + open var sticky: Bool = false { didSet { setNeedsUpdate() } } + + ///Model of the Tabs you are wanting to show. + open var tabModels: [TabModel] = [] { didSet { updateTabItems() } } + +// //rules for width +// private var _width: Width? = .percentage(0.25) +// +// ///Width of all Tabs when orientation is vertical, defaults to 25%. +// open var width: Width? { +// get { +// return _width +// } +// set { +// if let newValue { +// switch newValue { +// case .percentage(let percentage): +// if percentage >= 0 && percentage <= 1 { +// _width = newValue +// setNeedsUpdate() +// } else { +// print("Invalid percentage value. It should be between 0 and 1.") +// } +// case .value(let value): +// if value >= minWidth { +// _width = newValue +// setNeedsUpdate() +// } else { +// print("Invalid value. It should be greater than or equal to \(minWidth).") +// } +// } +// } +// } +// } + + open var tabItems: [TabItem] = [] //-------------------------------------------------- // MARK: - Private Properties //-------------------------------------------------- - private var tabItems: [TabItem] = [] private var tabStackView: UIStackView! private var scrollView: UIScrollView! private var contentView: View! @@ -162,7 +190,6 @@ public class Tabs: View { tabItem.removeFromSuperview() } tabItems.removeAll() - // Create new tab items from the models for model in models { let tabItem = TabItem() @@ -195,38 +222,43 @@ public class Tabs: View { open override func updateView() { super.updateView() + + // Update the stackview properties if orientation == .horizontal && fillContainer { tabStackView.distribution = .fillEqually } else { - tabStackView.distribution = .fill - } - - // Update tab appearance based on properties - for (index, tabItem) in tabItems.enumerated() { - tabItem.selected = selectedIndex == index - tabItem.size = size - tabItem.orientation = orientation - tabItem.surface = surface - tabItem.indicatorPosition = indicatorPosition - tabItem.width = tabWidth(for: tabItem) + tabStackView.distribution = .fillProportionally } tabStackView.axis = orientation == .horizontal ? .horizontal : .vertical tabStackView.alignment = orientation == .horizontal ? .fill : .leading tabStackView.spacing = orientation == .horizontal ? VDSLayout.Spacing.space6X.value : VDSLayout.Spacing.space4X.value + // Update tab appearance based on properties + for (index, tabItem) in tabItems.enumerated() { + tabItem.selected = selectedIndex == index + tabItem.index = index + tabItem.minWidth = minWidth + tabItem.size = size + tabItem.textPosition = orientation == .horizontal && fillContainer ? .center : .left + tabItem.orientation = orientation + tabItem.surface = surface + tabItem.indicatorPosition = indicatorPosition + } + // Deactivate old constraint contentViewWidthConstraint?.isActive = false // Apply width if orientation == .vertical { scrollView.isScrollEnabled = false - switch width { - case .percentage(let amount): - contentViewWidthConstraint = contentView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: amount) - case .value(let amount): - contentViewWidthConstraint = contentView.widthAnchor.constraint(equalToConstant: amount) - } +// switch width { +// case .percentage(let amount): +// contentViewWidthConstraint = contentView.widthAnchor.constraint(equalTo: widthAnchor, multiplier: amount) +// case .value(let amount): +// contentViewWidthConstraint = contentView.widthAnchor.constraint(equalToConstant: amount) +// } + contentViewWidthConstraint = contentView.widthAnchor.constraint(equalTo: widthAnchor) } else { // Apply overflow if overflow == .scroll && !fillContainer { @@ -278,20 +310,20 @@ public class Tabs: View { scrollToSelectedIndex(animated: true) } - //-------------------------------------------------- - // MARK: - Private Methods - //-------------------------------------------------- - private func tabWidth(for item: TabItem) -> CGFloat? { - guard orientation == .vertical else { return item.width } - var calculated: CGFloat - switch width { - case .percentage(let percent): - calculated = (bounds.width * percent) - tabStackView.spacing - case .value(let value): - calculated = value - tabStackView.spacing - } - - return calculated > minWidth ? calculated : minWidth - } +// //-------------------------------------------------- +// // MARK: - Private Methods +// //-------------------------------------------------- +// private func tabWidth(for item: TabItem) -> CGFloat? { +// guard orientation == .vertical else { return item.width } +// var calculated: CGFloat +// switch width { +// case .percentage(let percent): +// calculated = (bounds.width * percent) - tabStackView.spacing +// case .value(let value): +// calculated = value - tabStackView.spacing +// } +// +// return calculated > minWidth ? calculated : minWidth +// } }