Skip to content

Commit acfb4ff

Browse files
authored
Merge pull request #8 from engingulek/feature/genc-cv-laySour-Protocol
Feature/genc cv lay sour protocol
2 parents 2fcd42d + 616adc8 commit acfb4ff

3 files changed

Lines changed: 262 additions & 0 deletions

File tree

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
//
2+
// GenericCollectionLayoutProtocol.swift
3+
// GenericCollectionViewKit
4+
//
5+
// Created by Engin Gülek on 11.11.2025.
6+
//
7+
8+
import UIKit
9+
import Foundation
10+
11+
// MARK: - GenericCollectionLayoutProviderProtocol
12+
/// A protocol that defines how layout configurations are provided for each section
13+
/// of a collection view. Each section can have its own distinct layout style by returning
14+
/// a `LayoutSource` instance.
15+
public protocol GenericCollectionLayoutProviderProtocol {
16+
17+
///Returns a layout configuration (`LayoutSource`) for the specified section index.
18+
/// - Parameter sectionIndex: The index of the section whose layout is being requested.
19+
/// - Returns: A `LayoutSource` instance describing the layout configuration for that section.
20+
func layout(for sectionIndex: Int) -> LayoutSource
21+
}
22+
23+
// MARK: - GenericCollectionLayoutProvider
24+
/// A generic class that builds a `UICollectionViewCompositionalLayout`
25+
/// using the configurations provided by a `GenericCollectionLayoutProviderProtocol` conforming source.
26+
public class GenericCollectionLayoutProvider<Source: GenericCollectionLayoutProviderProtocol> {
27+
28+
///The source responsible for providing layout configurations per section.
29+
private let source: Source
30+
31+
/// Initializes a new instance of `GenericCollectionLayoutProvider` with a layout source.
32+
/// - Parameter source: An object conforming to `GenericCollectionLayoutProtocol`.
33+
public init(source: Source) {
34+
self.source = source
35+
}
36+
37+
// MARK: - Layout Creation
38+
/// Creates and returns a `UICollectionViewCompositionalLayout`
39+
/// based on the layout information provided by the source.
40+
/// This method dynamically constructs section layouts using each section’s `LayoutSource`.
41+
/// It handles item sizing, grouping, spacing, section insets, scroll direction, and headers.
42+
/// - Returns: A fully configured `UICollectionViewCompositionalLayout`.
43+
@MainActor
44+
public func createLayout() -> UICollectionViewCompositionalLayout {
45+
UICollectionViewCompositionalLayout { [weak self] sectionIndex, _ in
46+
guard let self = self else { return nil }
47+
48+
// Retrieve layout configuration for the current section
49+
let layoutSource = self.source.layout(for: sectionIndex)
50+
51+
// MARK: Item Configuration
52+
/// Define how individual items are sized (fractional or absolute).
53+
54+
let itemSize = NSCollectionLayoutSize(
55+
widthDimension: layoutSource.itemSize.width.type == .fractional
56+
? .fractionalWidth(layoutSource.itemSize.width.value)
57+
: .absolute(layoutSource.itemSize.width.value),
58+
heightDimension: layoutSource.itemSize.height.type == .fractional
59+
? .fractionalHeight(layoutSource.itemSize.height.value)
60+
: .absolute(layoutSource.itemSize.height.value))
61+
62+
let item = NSCollectionLayoutItem(layoutSize: itemSize)
63+
64+
// MARK: Group Configuration
65+
/// Define the layout and size of item groups (either horizontal or vertical).
66+
let groupSize = NSCollectionLayoutSize(
67+
widthDimension: layoutSource.groupSize.width.type == .absolute
68+
? .absolute(layoutSource.groupSize.width.value)
69+
: .fractionalWidth(layoutSource.groupSize.width.value),
70+
heightDimension: layoutSource.groupSize.height.type == .absolute
71+
? .absolute(layoutSource.groupSize.height.value)
72+
: .fractionalHeight(layoutSource.groupSize.height.value))
73+
74+
let group: NSCollectionLayoutGroup
75+
if layoutSource.groupOrientation == .horizontal {
76+
group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])
77+
} else {
78+
group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
79+
}
80+
81+
group.interItemSpacing = .fixed(layoutSource.interItemSpacing)
82+
83+
// MARK: Section Configuration
84+
/// Defines how groups are arranged within each section, including
85+
/// scrolling behavior, content insets, spacing, and supplementary views.
86+
let section = NSCollectionLayoutSection(group: group)
87+
section.orthogonalScrollingBehavior = layoutSource.scrollDirection == .horizontal ? .continuous : .none
88+
section.contentInsets = NSDirectionalEdgeInsets(
89+
top: layoutSource.sectionInsets.top,
90+
leading: layoutSource.sectionInsets.leading,
91+
bottom: layoutSource.sectionInsets.bottom,
92+
trailing: layoutSource.sectionInsets.trailing
93+
)
94+
section.interGroupSpacing = layoutSource.interGroupSpacing
95+
96+
// MARK: Header Configuration
97+
/// Adds a header supplementary view at the top of each section.
98+
let headerSize = NSCollectionLayoutSize(
99+
widthDimension: .fractionalWidth(1.0),
100+
heightDimension: .absolute(40)
101+
)
102+
let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
103+
layoutSize: headerSize,
104+
elementKind: UICollectionView.elementKindSectionHeader,
105+
alignment: .top
106+
)
107+
section.boundarySupplementaryItems = [sectionHeader]
108+
109+
return section
110+
}
111+
}
112+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
//
2+
// LayoutSource.swift
3+
// GenericCollectionViewKit
4+
//
5+
// Created by Engin Gülek on 11.11.2025.
6+
//
7+
8+
9+
import UIKit
10+
import Foundation
11+
12+
// MARK: - DimensionType
13+
/// Represents how a size dimension (width or height) is defined.
14+
/// It can be fractional (relative to its container), absolute (fixed value), or none (no defined size).
15+
public enum DimensionType {
16+
case fractional
17+
case absolute
18+
case none
19+
}
20+
21+
// MARK: - SizeInfo
22+
/// Encapsulates the width and height of an element using a specific `DimensionType` and a numeric value.
23+
/// It allows flexible sizing for compositional layouts.
24+
public struct SizeInfo {
25+
26+
///Initializes a new SizeInfo instance with specified width and height configurations.
27+
/// - Parameters:
28+
/// - width: A tuple containing the dimension type and value for width.
29+
/// - height: A tuple containing the dimension type and value for height.
30+
public init(width: (type: DimensionType, value: CGFloat),
31+
height: (type: DimensionType, value: CGFloat)) {
32+
self.width = width
33+
self.height = height
34+
}
35+
36+
///Width configuration consisting of a dimension type and a numeric value.
37+
let width: (type: DimensionType, value: CGFloat)
38+
39+
///Height configuration consisting of a dimension type and a numeric value.
40+
let height: (type: DimensionType, value: CGFloat)
41+
}
42+
43+
// MARK: - ScrollDirection
44+
/// Defines the direction in which the collection view scrolls — either horizontally or vertically.
45+
public enum ScrollDirection {
46+
case horizontal
47+
case vertical
48+
}
49+
50+
// MARK: - LayoutSource
51+
/// A configuration model for creating compositional layouts in a collection view.
52+
/// It defines item and group sizes, layout orientation, spacing, and scroll direction.
53+
public struct LayoutSource {
54+
55+
/// Initializes a new layout configuration with the given parameters.
56+
/// - Parameters:
57+
/// - groupOrientation: The orientation of items within a group (horizontal or vertical).
58+
/// - itemSize: The size configuration for individual items.
59+
/// - groupSize: The size configuration for each group of items.
60+
/// - sectionInsets: The spacing around each section (top, leading, bottom, trailing).
61+
/// - interItemSpacing: The spacing between items within a group.
62+
/// - interGroupSpacing: The spacing between groups.
63+
/// - scrollDirection: The overall scrolling direction of the collection view.
64+
public init(
65+
groupOrientation: ScrollDirection,
66+
itemSize: SizeInfo,
67+
groupSize: SizeInfo,
68+
sectionInsets: (top: CGFloat, leading: CGFloat, bottom: CGFloat, trailing: CGFloat),
69+
interItemSpacing: CGFloat,
70+
interGroupSpacing: CGFloat,
71+
scrollDirection: ScrollDirection
72+
) {
73+
self.groupOrientation = groupOrientation
74+
self.itemSize = itemSize
75+
self.groupSize = groupSize
76+
self.sectionInsets = sectionInsets
77+
self.interItemSpacing = interItemSpacing
78+
self.interGroupSpacing = interGroupSpacing
79+
self.scrollDirection = scrollDirection
80+
}
81+
82+
let groupOrientation: ScrollDirection
83+
let itemSize: SizeInfo
84+
let groupSize: SizeInfo
85+
let sectionInsets: (top: CGFloat,
86+
leading: CGFloat,
87+
bottom: CGFloat,
88+
trailing: CGFloat)
89+
90+
let interItemSpacing: CGFloat
91+
let interGroupSpacing: CGFloat
92+
let scrollDirection: ScrollDirection
93+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// LayoutSourceTeamplate.swift
3+
// GenericCollectionViewKit
4+
//
5+
// Created by Engin Gülek on 11.11.2025.
6+
//
7+
8+
import Foundation
9+
10+
// MARK: - LayoutSourceTemplate
11+
/// Provides predefined layout templates for commonly used collection view layouts.
12+
public enum LayoutSourceTeamplate {
13+
case horizontalSingleRow
14+
case verticalTwoPerRow
15+
case none
16+
17+
/// Returns a predefined layout configuration based on the selected template type.
18+
public var template: LayoutSource {
19+
switch self {
20+
case .horizontalSingleRow:
21+
// Horizontal scrolling layout with one row of items that stretch across the width.
22+
return LayoutSource(
23+
groupOrientation: .horizontal,
24+
itemSize: .init(width: (type: .fractional, value: 0.95), height: (type: .fractional, value: 1.0)),
25+
groupSize: .init(width: (type: .fractional, value: 1.0), height: (type: .fractional, value: 0.25)),
26+
sectionInsets: (top: 10, leading: 10, bottom: 10, trailing: 10),
27+
interItemSpacing: 10,
28+
interGroupSpacing: 10,
29+
scrollDirection: .horizontal
30+
)
31+
32+
case .verticalTwoPerRow:
33+
// Vertical scrolling layout displaying two items per row.
34+
return LayoutSource(
35+
groupOrientation: .horizontal,
36+
itemSize: .init(width: (type: .fractional, value: 1.0 / 2), height: (type: .fractional, value: 1.0)),
37+
groupSize: .init(width: (type: .fractional, value: 1.0), height: (type: .fractional, value: 0.40)),
38+
sectionInsets: (top: 10, leading: 5, bottom: 10, trailing: 5),
39+
interItemSpacing: 5,
40+
interGroupSpacing: 8,
41+
scrollDirection: .vertical
42+
)
43+
44+
case .none:
45+
// Empty layout with no sizing or spacing — used as a placeholder.
46+
return LayoutSource(
47+
groupOrientation: .horizontal,
48+
itemSize: .init(width: (type: .none, value: 0), height: (type: .none, value: 0)),
49+
groupSize: .init(width: (type: .none, value: 0), height: (type: .none, value: 0)),
50+
sectionInsets: (top: 0, leading: 0, bottom: 0, trailing: 0),
51+
interItemSpacing: 0,
52+
interGroupSpacing: 0,
53+
scrollDirection: .vertical
54+
)
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)