Skip to content

Commit 1c2b055

Browse files
committed
first commit
1 parent b44b168 commit 1c2b055

28 files changed

+1470
-0
lines changed

Package.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// swift-tools-version:5.3
2+
// The swift-tools-version declares the minimum version of Swift required to build this package.
3+
4+
import PackageDescription
5+
6+
let package = Package(
7+
name: "ListKit",
8+
platforms: [
9+
.iOS(.v13)
10+
],
11+
products: [
12+
.library(
13+
name: "ListKit",
14+
targets: ["ListKit"]),
15+
],
16+
dependencies: [
17+
],
18+
targets: [
19+
.target(
20+
name: "ListKit",
21+
dependencies: []),
22+
.testTarget(
23+
name: "ListKitTests",
24+
dependencies: ["ListKit"]),
25+
]
26+
)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
//
2+
// GroupBuilder.swift
3+
// ListKit
4+
//
5+
// Created by burt on 2021/09/12.
6+
//
7+
8+
import UIKit
9+
10+
public struct GroupBuilderResult {
11+
public let group: NSCollectionLayoutGroup
12+
public let items: [NSCollectionLayoutItem]
13+
}
14+
15+
@resultBuilder
16+
public struct GroupBuilder {
17+
public static func buildBlock(_ components: NSCollectionLayoutGroupConvertible...) -> GroupBuilderResult {
18+
precondition(components.count == 1, "Section contains only one Group!")
19+
guard let group = components.first else {
20+
fatalError("Section must have only one root Group!")
21+
}
22+
return GroupBuilderResult(group: group.toNSCollectionLayoutGroup(), items: group.items)
23+
}
24+
25+
public static func buildFinalResult(_ component: GroupBuilderResult) -> GroupBuilderResult {
26+
return component
27+
}
28+
29+
/// support if block
30+
public static func buildOptional(_ component: [NSCollectionLayoutGroupConvertible]?) -> [GroupBuilderResult] {
31+
return component?.compactMap { GroupBuilderResult(group: $0.toNSCollectionLayoutGroup(), items: $0.items) } ?? []
32+
}
33+
34+
/// support if-else block (if)
35+
public static func buildEither(first component: GroupBuilderResult) -> GroupBuilderResult {
36+
return component
37+
}
38+
39+
/// support if-else block (else)
40+
public static func buildEither(second component: GroupBuilderResult) -> GroupBuilderResult {
41+
return component
42+
}
43+
44+
/// GroupBuilder does not support for-in loop
45+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//
2+
// ItemBuilder.swift
3+
// ListKit
4+
//
5+
// Created by burt on 2021/09/12.
6+
//
7+
8+
import UIKit
9+
10+
@resultBuilder
11+
public struct ItemBuilder {
12+
public static func buildBlock(_ components: NSCollectionLayoutItemsConvertible...) -> [NSCollectionLayoutItem] {
13+
return components.flatMap { $0.toNSCollectionLayoutItems() }
14+
}
15+
16+
public static func buildFinalResult(_ component: [NSCollectionLayoutItem]) -> [NSCollectionLayoutItem] {
17+
return component
18+
}
19+
20+
/// support if block
21+
public static func buildOptional(_ component: [NSCollectionLayoutItemsConvertible]?) -> [NSCollectionLayoutItem] {
22+
return component?.flatMap { $0.toNSCollectionLayoutItems() } ?? []
23+
}
24+
25+
/// support if-else block (if)
26+
public static func buildEither(first component: [NSCollectionLayoutItemsConvertible]) -> [NSCollectionLayoutItem] {
27+
return component.flatMap { $0.toNSCollectionLayoutItems() }
28+
}
29+
30+
/// support if-else block (else)
31+
public static func buildEither(second component: [NSCollectionLayoutItemsConvertible]) -> [NSCollectionLayoutItem] {
32+
return component.flatMap { $0.toNSCollectionLayoutItems() }
33+
}
34+
35+
/// support for-in loop
36+
public static func buildArray(_ components: [NSCollectionLayoutItemsConvertible]) -> [NSCollectionLayoutItem] {
37+
return components.flatMap { $0.toNSCollectionLayoutItems() }
38+
}
39+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
//
2+
// SectionBuilder.swift
3+
// ListKit
4+
//
5+
// Created by burt on 2021/09/12.
6+
//
7+
8+
import UIKit
9+
10+
public class SectionBuilderResult: NSObject {
11+
public let sections: [Section]
12+
public let nsSections: [NSCollectionLayoutSection]
13+
14+
public init(sections: [Section], nsSections: [NSCollectionLayoutSection]) {
15+
self.sections = sections
16+
self.nsSections = nsSections
17+
}
18+
}
19+
20+
@resultBuilder
21+
public struct SectionBuilder {
22+
public static func buildBlock(_ components: NSCollectionLayoutSectionConvertible...) -> [SectionBuilderResult] {
23+
return components.compactMap { SectionBuilderResult(sections: $0.sections, nsSections: $0.toNSCollectionLayoutSections()) }
24+
}
25+
26+
public static func buildFinalResult(_ component: [SectionBuilderResult]) -> [SectionBuilderResult] {
27+
return component
28+
}
29+
30+
/// support if block
31+
public static func buildOptional(_ component: [SectionBuilderResult]?) -> [SectionBuilderResult] {
32+
return component ?? []
33+
}
34+
35+
/// support if-else block (if)
36+
public static func buildEither(first component: [SectionBuilderResult]) -> [SectionBuilderResult] {
37+
return component
38+
}
39+
40+
/// support if-else block (else)
41+
public static func buildEither(second component: [SectionBuilderResult]) -> [SectionBuilderResult] {
42+
return component
43+
}
44+
45+
/// support for-in loop
46+
public static func buildArray(_ components: [[SectionBuilderResult]]) -> [SectionBuilderResult] {
47+
let result = components.flatMap { $0 }
48+
return result
49+
}
50+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// ListKitCell.swift
3+
// ListKit
4+
//
5+
// Created by burt on 2021/09/11.
6+
//
7+
8+
import UIKit
9+
10+
internal class ListKitCell: UICollectionViewCell {
11+
}
12+
13+
extension ListKitCell {
14+
static var className: String {
15+
return String(reflecting: Self.self)
16+
}
17+
18+
internal override var reuseIdentifier: String? {
19+
return Self.className
20+
}
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// ListKitReusableView.swift
3+
// ListKit
4+
//
5+
// Created by burt on 2021/09/15.
6+
//
7+
8+
import UIKit
9+
10+
internal class ListKitReusableView: UICollectionReusableView {
11+
}
12+
13+
extension ListKitReusableView {
14+
static var className: String {
15+
return String(reflecting: Self.self)
16+
}
17+
18+
internal override var reuseIdentifier: String? {
19+
return Self.className
20+
}
21+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// DataSource.swift
3+
// ListKit
4+
//
5+
// Created by burt on 2021/09/11.
6+
//
7+
8+
import UIKit
9+
10+
public protocol DataSource {
11+
var layout: ComposeLayout? { get set }
12+
var collectionView: UICollectionView? { get set }
13+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//
2+
// DiffableDataSource.swift
3+
// ListKit
4+
//
5+
// Created by burt on 2021/09/16.
6+
//
7+
8+
import UIKit
9+
10+
public class DiffableDataSource: DataSource {
11+
public var layout: ComposeLayout?
12+
public weak var collectionView: UICollectionView?
13+
private var dataSource: UICollectionViewDiffableDataSource<Section, AnyComponent>?
14+
15+
public init() {
16+
}
17+
18+
func applySnapshot(animated: Bool) {
19+
guard let collectionView = collectionView else { return }
20+
if self.dataSource == nil {
21+
self.dataSource = UICollectionViewDiffableDataSource<Section, AnyComponent>(collectionView: collectionView) { [weak self] collectionView, indexPath, section in
22+
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ListKitCell.className, for: indexPath)
23+
if let wrapper = self?.layout?.sections[indexPath.section].components[indexPath.item], let contentView = wrapper.component.contentView() as? UIView {
24+
cell.contentView.viewWithTag(Int.max)?.removeFromSuperview()
25+
contentView.tag = Int.max
26+
cell.contentView.addSubview(contentView)
27+
28+
contentView.translatesAutoresizingMaskIntoConstraints = false
29+
let constraints = [
30+
cell.contentView.topAnchor.constraint(equalTo: contentView.topAnchor),
31+
cell.contentView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
32+
cell.contentView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
33+
cell.contentView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
34+
]
35+
NSLayoutConstraint.activate(constraints)
36+
37+
wrapper.component.render(in: contentView)
38+
39+
}
40+
return cell
41+
}
42+
43+
self.dataSource?.supplementaryViewProvider = { (collectionView, kind, indexPath) in
44+
let view = collectionView.dequeueReusableSupplementaryView(ofKind: ListKitReusableView.className, withReuseIdentifier: ListKitReusableView.className, for: indexPath)
45+
if let anySupplementaryComponent = SupplementaryComponentManager.shared[kind], let contentView = anySupplementaryComponent.contentView() as? UIView {
46+
47+
view.viewWithTag(Int.max)?.removeFromSuperview()
48+
contentView.tag = Int.max
49+
view.addSubview(contentView)
50+
51+
contentView.translatesAutoresizingMaskIntoConstraints = false
52+
let constraints = [
53+
view.topAnchor.constraint(equalTo: contentView.topAnchor),
54+
view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
55+
view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
56+
view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
57+
]
58+
NSLayoutConstraint.activate(constraints)
59+
60+
anySupplementaryComponent.render(in: contentView)
61+
}
62+
return view
63+
}
64+
collectionView.dataSource = dataSource
65+
}
66+
updateSnapshot(animated: animated)
67+
}
68+
69+
func updateSnapshot(animated: Bool) {
70+
guard let layout = layout else { return }
71+
var snapshot = NSDiffableDataSourceSnapshot<Section, AnyComponent>()
72+
snapshot.appendSections(layout.sections)
73+
for section in layout.sections {
74+
snapshot.appendItems(section.components.map { $0.component }, toSection: section)
75+
}
76+
dataSource?.apply(snapshot, animatingDifferences: true)
77+
}
78+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// ListKitPlainDataSource.swift
3+
// ListKit
4+
//
5+
// Created by burt on 2021/09/11.
6+
//
7+
8+
import UIKit
9+
10+
public class PlainDataSource: NSObject, DataSource, UICollectionViewDataSource {
11+
public var layout: ComposeLayout?
12+
public weak var collectionView: UICollectionView?
13+
14+
public func numberOfSections(in collectionView: UICollectionView) -> Int {
15+
guard let layout = layout else { return 0 }
16+
return layout.sections.count
17+
}
18+
19+
public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
20+
guard let layout = layout else { return 0 }
21+
return layout.sections[section].components.count
22+
}
23+
24+
public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
25+
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ListKitCell.className, for: indexPath)
26+
if let wrapper = layout?.sections[indexPath.section].components[indexPath.item], let contentView = wrapper.component.contentView() as? UIView {
27+
cell.contentView.viewWithTag(Int.max)?.removeFromSuperview()
28+
contentView.tag = Int.max
29+
cell.contentView.addSubview(contentView)
30+
31+
contentView.translatesAutoresizingMaskIntoConstraints = false
32+
let constraints = [
33+
cell.contentView.topAnchor.constraint(equalTo: contentView.topAnchor),
34+
cell.contentView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
35+
cell.contentView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
36+
cell.contentView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
37+
]
38+
NSLayoutConstraint.activate(constraints)
39+
40+
wrapper.component.render(in: contentView)
41+
}
42+
return cell
43+
}
44+
45+
public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
46+
47+
let view = collectionView.dequeueReusableSupplementaryView(ofKind: ListKitReusableView.className, withReuseIdentifier: ListKitReusableView.className, for: indexPath)
48+
if let anySupplementaryComponent = SupplementaryComponentManager.shared[kind], let contentView = anySupplementaryComponent.contentView() as? UIView {
49+
50+
view.viewWithTag(Int.max)?.removeFromSuperview()
51+
contentView.tag = Int.max
52+
view.addSubview(contentView)
53+
54+
contentView.translatesAutoresizingMaskIntoConstraints = false
55+
let constraints = [
56+
view.topAnchor.constraint(equalTo: contentView.topAnchor),
57+
view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor),
58+
view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
59+
view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor)
60+
]
61+
NSLayoutConstraint.activate(constraints)
62+
63+
anySupplementaryComponent.render(in: contentView)
64+
}
65+
return view
66+
}
67+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// NSObjectExtensions.swift
3+
// ListKit
4+
//
5+
// Created by burt on 2021/09/12.
6+
//
7+
8+
import Foundation
9+
10+
extension NSObject {
11+
private static let componentAssociation = AnyObjectAssociation()
12+
13+
var anyComponent: AnyObject? {
14+
get {
15+
return NSObject.componentAssociation[self]
16+
}
17+
set {
18+
NSObject.componentAssociation[self] = newValue
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)