Compare commits
5 Commits
develop
...
feature/ti
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1e7b3acc42 | ||
|
|
4484034b4c | ||
|
|
2e519eee28 | ||
|
|
c3c133cac7 | ||
|
|
b974e63931 |
@ -210,6 +210,8 @@
|
||||
EAF7F11728A1475A00B287F5 /* RadioButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F11528A1475A00B287F5 /* RadioButtonItem.swift */; };
|
||||
EAF7F13328A2A16500B287F5 /* AttachmentLabelAttributeModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF7F13228A2A16500B287F5 /* AttachmentLabelAttributeModel.swift */; };
|
||||
EAF978212A99035B00C2FEA9 /* Enabling.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAF978202A99035B00C2FEA9 /* Enabling.swift */; };
|
||||
EAFD5AA02CB5CA5300C87DE1 /* TileletGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAFD5A9F2CB5CA5300C87DE1 /* TileletGroup.swift */; };
|
||||
EAFD5AA22CB5CA7900C87DE1 /* TileletGroupFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAFD5AA12CB5CA7900C87DE1 /* TileletGroupFlowLayout.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@ -459,6 +461,8 @@
|
||||
EAF7F11528A1475A00B287F5 /* RadioButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RadioButtonItem.swift; sourceTree = "<group>"; };
|
||||
EAF7F13228A2A16500B287F5 /* AttachmentLabelAttributeModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttachmentLabelAttributeModel.swift; sourceTree = "<group>"; };
|
||||
EAF978202A99035B00C2FEA9 /* Enabling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Enabling.swift; sourceTree = "<group>"; };
|
||||
EAFD5A9F2CB5CA5300C87DE1 /* TileletGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileletGroup.swift; sourceTree = "<group>"; };
|
||||
EAFD5AA12CB5CA7900C87DE1 /* TileletGroupFlowLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TileletGroupFlowLayout.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -957,6 +961,7 @@
|
||||
EA5E3056295105930082B959 /* Tilelet */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EAFD5A9E2CB5C9ED00C87DE1 /* TileletGroup */,
|
||||
EA5E3057295105A40082B959 /* Tilelet.swift */,
|
||||
EA985BE529688F6A00F2FF2E /* TileletBadgeModel.swift */,
|
||||
71ACE89D2BA1CC1700FB6ADC /* TiletEyebrowModel.swift */,
|
||||
@ -1171,6 +1176,15 @@
|
||||
path = RadioButton;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
EAFD5A9E2CB5C9ED00C87DE1 /* TileletGroup */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
EAFD5A9F2CB5CA5300C87DE1 /* TileletGroup.swift */,
|
||||
EAFD5AA12CB5CA7900C87DE1 /* TileletGroupFlowLayout.swift */,
|
||||
);
|
||||
path = TileletGroup;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXHeadersBuildPhase section */
|
||||
@ -1382,6 +1396,7 @@
|
||||
EAB1D2CD28ABE76100DAE764 /* Withable.swift in Sources */,
|
||||
71FC86DC2B96F4C800700965 /* PaginationCellItem.swift in Sources */,
|
||||
EAC846F3294B95CE00F685BA /* ButtonGroupCollectionViewCell.swift in Sources */,
|
||||
EAFD5AA02CB5CA5300C87DE1 /* TileletGroup.swift in Sources */,
|
||||
EAF7F0952899861000B287F5 /* CheckboxItem.swift in Sources */,
|
||||
EA985BE82968951C00F2FF2E /* TileletTitleModel.swift in Sources */,
|
||||
71FC86DE2B9738B900700965 /* SurfaceConfigurationValue.swift in Sources */,
|
||||
@ -1460,6 +1475,7 @@
|
||||
EA336171288B19200071C351 /* VDS.docc in Sources */,
|
||||
EA985BF02968A93600F2FF2E /* TitleLockupEyebrowModel.swift in Sources */,
|
||||
EA5E30532950DDA60082B959 /* TitleLockup.swift in Sources */,
|
||||
EAFD5AA22CB5CA7900C87DE1 /* TileletGroupFlowLayout.swift in Sources */,
|
||||
EAD062B02A3B873E0015965D /* BadgeIndicator.swift in Sources */,
|
||||
183B16F32C78CF7C00BA6A10 /* CarouselSlotCell.swift in Sources */,
|
||||
44A952DD2BE3DA820009F874 /* TableFlowLayout.swift in Sources */,
|
||||
|
||||
187
VDS/Components/Tilelet/TileletGroup/TileletGroup.swift
Normal file
187
VDS/Components/Tilelet/TileletGroup/TileletGroup.swift
Normal file
@ -0,0 +1,187 @@
|
||||
//
|
||||
// TileletGroup.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 10/8/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import VDSCoreTokens
|
||||
import Combine
|
||||
|
||||
open class TileletGroup: View {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
/// CollectionView to show the rows and columns
|
||||
private lazy var matrixView = SelfSizingCollectionView(frame: .zero, collectionViewLayout: flowLayout).with {
|
||||
$0.register(TileletGroupCellItem.self, forCellWithReuseIdentifier: TileletGroupCellItem.Identifier)
|
||||
$0.dataSource = self
|
||||
$0.delegate = self
|
||||
$0.translatesAutoresizingMaskIntoConstraints = false
|
||||
$0.allowsSelection = false
|
||||
$0.showsVerticalScrollIndicator = false
|
||||
$0.showsHorizontalScrollIndicator = false
|
||||
$0.isAccessibilityElement = true
|
||||
$0.backgroundColor = .clear
|
||||
}
|
||||
|
||||
/// Custom flow layout to manage the height of the cells
|
||||
private lazy var flowLayout = TileletGroupFlowLayout().with {
|
||||
$0.delegate = self
|
||||
$0.scrollDirection = .horizontal
|
||||
}
|
||||
|
||||
/// Parameter to show the all table rows
|
||||
private var rows: [[Tilelet]] = [] { didSet { setNeedsUpdate() } }
|
||||
//--------------------------------------------------
|
||||
// MARK: - Enums
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Enums used to define the padding for the cell edge spacing.
|
||||
public enum Padding: String, CaseIterable {
|
||||
case standard, compact
|
||||
|
||||
var value: CGFloat {
|
||||
switch self {
|
||||
case .standard:
|
||||
return UIDevice.isIPad ? 40.0 : VDSLayout.space3X
|
||||
case .compact:
|
||||
return UIDevice.isIPad ? VDSLayout.space6X : VDSLayout.space3X
|
||||
}
|
||||
}
|
||||
|
||||
func horizontalValue() -> CGFloat { value }
|
||||
|
||||
func verticalValue() -> CGFloat { value }
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
/// Parameter to set the padding for the cell
|
||||
open var padding: Padding = .standard { didSet { setNeedsUpdate() } }
|
||||
|
||||
/// An object containing number of Button components per row for iPhones
|
||||
open var rowQuantityPhone: Int = 2 { didSet { updateRows() } }
|
||||
|
||||
/// An object containing number of Button components per row for iPads
|
||||
open var rowQuantityTablet: Int = 4 { didSet { updateRows() } }
|
||||
|
||||
/// An object containing number of Button components per row
|
||||
open var rowQuantity: Int { UIDevice.isIPad ? min(rowQuantityTablet,6) : min(rowQuantityPhone, 3) }
|
||||
|
||||
open var tilelets: [Tilelet] = [] { didSet { updateRows() } }
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
|
||||
///Called upon initializing the table view
|
||||
open override func setup() {
|
||||
super.setup()
|
||||
addSubview(matrixView)
|
||||
matrixView.pinToSuperView()
|
||||
}
|
||||
|
||||
/// Will update the table view, when called becasue of any changes in component parameters
|
||||
open override func updateView() {
|
||||
super.updateView()
|
||||
flowLayout.verticalPadding = padding.verticalValue()
|
||||
flowLayout.horizontalPadding = padding.horizontalValue()
|
||||
matrixView.reloadData()
|
||||
matrixView.collectionViewLayout.invalidateLayout()
|
||||
}
|
||||
|
||||
open override func setDefaults() {
|
||||
super.setDefaults()
|
||||
padding = .standard
|
||||
rows = [[]]
|
||||
}
|
||||
|
||||
func updateRows() {
|
||||
var tempRows: [[Tilelet]] = []
|
||||
for i in stride(from: 0, to: tilelets.count, by: rowQuantity) {
|
||||
let endIndex = min(i + rowQuantity, tilelets.count)
|
||||
let row = Array(tilelets[i..<endIndex])
|
||||
tempRows.append(row)
|
||||
}
|
||||
rows = tempRows
|
||||
}
|
||||
}
|
||||
|
||||
extension TileletGroup: UICollectionViewDelegate, UICollectionViewDataSource, TiletGroupCollectionViewLayoutDataDelegate {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - UICollectionViewDelegate & UICollectionViewDataSource
|
||||
//--------------------------------------------------
|
||||
|
||||
public func numberOfSections(in collectionView: UICollectionView) -> Int {
|
||||
return rows.count
|
||||
}
|
||||
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||
return rows[section].count
|
||||
}
|
||||
|
||||
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: TileletGroupCellItem.Identifier, for: indexPath) as? TileletGroupCellItem else { return UICollectionViewCell() }
|
||||
let currentItem = rows[indexPath.section][indexPath.row]
|
||||
cell.updateCell(component: currentItem)
|
||||
// let rowColors: (UIColor,UIColor) = indexPath.section % 2 == 0 ? (.green, .yellow) : (.blue, .brown)
|
||||
// cell.backgroundColor = indexPath.row % 2 == 0 ? rowColors.0 : rowColors.1
|
||||
return cell
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - TableCollectionViewLayoutDataDelegate
|
||||
//--------------------------------------------------
|
||||
|
||||
internal func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> Tilelet {
|
||||
return rows[indexPath.section][indexPath.row]
|
||||
}
|
||||
}
|
||||
|
||||
final class TileletGroupCellItem: UICollectionViewCell {
|
||||
|
||||
/// Identifier for TableCellItem
|
||||
static let Identifier: String = String(describing: TileletGroupCellItem.self)
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Main view which holds the content of the cell
|
||||
private let containerView = View()
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Initializers
|
||||
//--------------------------------------------------
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
setupCell()
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
setupCell()
|
||||
}
|
||||
|
||||
private func setupCell() {
|
||||
contentView.backgroundColor = .clear
|
||||
addSubview(containerView)
|
||||
containerView.pinToSuperView()
|
||||
}
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Methods
|
||||
//--------------------------------------------------
|
||||
|
||||
public func updateCell(component: Tilelet) {
|
||||
containerView.subviews.forEach({ $0.removeFromSuperview() })
|
||||
containerView.addSubview(component)
|
||||
component.pinToSuperView()
|
||||
}
|
||||
}
|
||||
142
VDS/Components/Tilelet/TileletGroup/TileletGroupFlowLayout.swift
Normal file
142
VDS/Components/Tilelet/TileletGroup/TileletGroupFlowLayout.swift
Normal file
@ -0,0 +1,142 @@
|
||||
//
|
||||
// TileletGroupPositionLayout.swift
|
||||
// VDS
|
||||
//
|
||||
// Created by Matt Bruce on 10/8/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import VDSCoreTokens
|
||||
|
||||
protocol TiletGroupCollectionViewLayoutDataDelegate: AnyObject {
|
||||
func collectionView(_ collectionView: UICollectionView, dataForItemAt indexPath: IndexPath) -> Tilelet
|
||||
}
|
||||
|
||||
class TileletGroupFlowLayout : UICollectionViewFlowLayout {
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Private Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Parameter to store the layout attributes of cell, while calculating the size & position of the cell
|
||||
private var itemCache: [UICollectionViewLayoutAttributes] = []
|
||||
|
||||
/// Parameter to store the total height of the collectionView
|
||||
private var layoutHeight: CGFloat = 0.0
|
||||
|
||||
/// Parameter to store the total width of the collectionView
|
||||
private var layoutWidth: CGFloat = 0.0
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Public Properties
|
||||
//--------------------------------------------------
|
||||
/// Spacing between items in the row
|
||||
public var horizontalPadding: CGFloat = VDSLayout.space1X
|
||||
|
||||
/// Spacing between rows (sections)
|
||||
public var verticalPadding: CGFloat = VDSLayout.space1X
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Internal Properties
|
||||
//--------------------------------------------------
|
||||
|
||||
weak var delegate: TiletGroupCollectionViewLayoutDataDelegate?
|
||||
|
||||
//--------------------------------------------------
|
||||
// MARK: - Overrides
|
||||
//--------------------------------------------------
|
||||
|
||||
/// Calculates the layout attribute properties & total height of the collectionView
|
||||
override func prepare() {
|
||||
super.prepare()
|
||||
|
||||
itemCache.removeAll()
|
||||
layoutHeight = 0.0
|
||||
|
||||
guard let collectionView, let delegate else { return }
|
||||
|
||||
let sections = collectionView.numberOfSections
|
||||
var yPos: CGFloat = 0.0
|
||||
|
||||
/// Calculate the available width for items (subtract the left and right padding)
|
||||
let collectionViewWidth = collectionView.bounds.width
|
||||
|
||||
/// Determine the number of columns and calculate item width based on available space
|
||||
///
|
||||
let columns = collectionView.numberOfItems(inSection: 0)
|
||||
let totalPadding = horizontalPadding * CGFloat(columns - 1)
|
||||
let availableWidth = collectionViewWidth - totalPadding
|
||||
let itemWidth = availableWidth / CGFloat(columns)
|
||||
|
||||
for currentSection in 0..<sections {
|
||||
let items = collectionView.numberOfItems(inSection: currentSection)
|
||||
|
||||
layoutWidth = 0.0
|
||||
var xPos: CGFloat = 0.0
|
||||
|
||||
/// Looping through all the items in the section, visually these are each column in the row
|
||||
for currentItem in 0..<items {
|
||||
let indexPath = IndexPath(row: currentItem, section: currentSection)
|
||||
let selectedItem = delegate.collectionView(collectionView, dataForItemAt: indexPath)
|
||||
let itemHeight = estimateHeightFor(item: selectedItem, with: itemWidth, index: indexPath)
|
||||
|
||||
layoutWidth += itemWidth + (currentItem > 0 ? horizontalPadding : 0)
|
||||
|
||||
let attribute = UICollectionViewLayoutAttributes(forCellWith: indexPath)
|
||||
attribute.frame = CGRect(x: xPos, y: yPos, width: itemWidth, height: itemHeight)
|
||||
itemCache.append(attribute)
|
||||
|
||||
/// Update x position for the next item
|
||||
xPos += itemWidth + horizontalPadding
|
||||
}
|
||||
|
||||
/// Determines the highest height from all the cells (columns) in the row
|
||||
let highestHeightForSection = itemCache
|
||||
.filter({ $0.indexPath.section == currentSection })
|
||||
.sorted(by: { $0.frame.size.height > $1.frame.size.height })
|
||||
.first?.frame.size.height ?? 0.0
|
||||
|
||||
/// Set the highest height as height to all the cells in the row for uniform height
|
||||
itemCache
|
||||
.filter({ $0.indexPath.section == currentSection })
|
||||
.forEach { attributes in
|
||||
attributes.frame.size.height = highestHeightForSection
|
||||
}
|
||||
|
||||
/// Adds the height to y position for the next section, including vertical padding
|
||||
yPos += highestHeightForSection + verticalPadding
|
||||
}
|
||||
|
||||
layoutHeight = yPos - verticalPadding /// Adjust bottom padding to total height
|
||||
}
|
||||
|
||||
/// Fetches estimated height by calling the cell's component estimated height and adding padding
|
||||
private func estimateHeightFor(item: Tilelet, with width: CGFloat, index: IndexPath) -> CGFloat {
|
||||
let itemWidth = width
|
||||
let maxSize = CGSize(width: itemWidth, height: CGFloat.greatestFiniteMagnitude)
|
||||
let estItemSize = item.systemLayoutSizeFitting(maxSize, withHorizontalFittingPriority: .required, verticalFittingPriority: .fittingSizeLevel)
|
||||
return estItemSize.height
|
||||
}
|
||||
|
||||
/// This will return the layout attributes for the elements in the defined rect
|
||||
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
|
||||
var visibleLayoutAttributes: [UICollectionViewLayoutAttributes] = []
|
||||
for attributes in itemCache {
|
||||
if attributes.frame.intersects(rect) {
|
||||
visibleLayoutAttributes.append(attributes)
|
||||
}
|
||||
}
|
||||
return visibleLayoutAttributes
|
||||
}
|
||||
|
||||
/// This will return the layout attributes at particular indexPath
|
||||
override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
|
||||
return itemCache.first(where: { $0.indexPath == indexPath })
|
||||
}
|
||||
|
||||
/// Returns the collectionView content size
|
||||
override var collectionViewContentSize: CGSize {
|
||||
return CGSize(width: collectionView?.bounds.width ?? layoutWidth, height: layoutHeight)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user