Skip to content

Commit ad1ed87

Browse files
authored
v2.0 (#68)
* add ViewProviderComposer * remove layout restriction * cleanup CollectionComposer * basic working * outside flattening working * add CollectionHeaderComposer * support sticky header * cleanup examples * remove leftover * bump version * update tests * rename step 1: remove Collection * rename provider to source * rename * WIP * rename 2 * add Deprecated bridge * update readme * remove MCollectionView references * some renaming * final collectionView cleanup * add migration guide * cleanup migration guide * rename test and config codecov * leftover * cleanup simpleviewprovider * rename presenter to animator * update resource * update readme images * remove BasicProviderBuilder * Update README.md * update migration guide * update tests * Update README.md * update naming
1 parent 372c26d commit ad1ed87

89 files changed

Lines changed: 2998 additions & 1469 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CollectionKit.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "CollectionKit"
3-
s.version = "1.3.1"
3+
s.version = "2.0.0"
44
s.summary = "A modern swift framework for building data-driven reusable collection view components."
55

66
s.description = <<-DESC

CollectionKit.xcodeproj/project.pbxproj

Lines changed: 173 additions & 103 deletions
Large diffs are not rendered by default.

CollectionKit.xcodeproj/project.xcworkspace/contents.xcworkspacedata

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

CollectionKitTests/CollectionViewSpec.swift

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,17 @@ class CollectionViewSpec: QuickSpec {
1414

1515
override func spec() {
1616
describe("CollectionView") {
17-
var provider: CollectionProvider<Int, UILabel>!
17+
var provider: BasicProvider<Int, UILabel>!
1818
var collectionView: CollectionView!
1919
beforeEach {
20-
provider = CollectionProvider(
21-
data: [1, 2, 3, 4],
22-
viewUpdater: { (label: UILabel, data: Int, index: Int) in
20+
provider = BasicProvider(
21+
dataSource: ArrayDataSource(data: [1, 2, 3, 4]),
22+
viewSource: ClosureViewSource(viewUpdater: { (label: UILabel, data: Int, index: Int) in
2323
label.backgroundColor = .red
24-
label.layer.cornerRadius = 8
2524
label.textAlignment = .center
2625
label.text = "\(data)"
27-
},
28-
sizeProvider: { (index: Int, data: Int, collectionSize: CGSize) -> CGSize in
26+
}),
27+
sizeSource: { (index: Int, data: Int, collectionSize: CGSize) -> CGSize in
2928
return CGSize(width: 50, height: 50)
3029
}
3130
)
@@ -51,7 +50,7 @@ class CollectionViewSpec: QuickSpec {
5150
expect((collectionView.subviews[0] as! UILabel).text) == "1"
5251
expect((collectionView.subviews[0] as! UILabel).textAlignment) == NSTextAlignment.center
5352

54-
expect(provider.layout).to(beAKindOf(FlowLayout<Int>.self))
53+
expect(provider.layout).to(beAKindOf(FlowLayout.self))
5554
expect(collectionView.subviews[0].frame) == CGRect(x: 0, y: 0, width: 50, height: 50)
5655
expect(collectionView.subviews[1].frame) == CGRect(x: 50, y: 0, width: 50, height: 50)
5756
expect(collectionView.subviews[2].frame) == CGRect(x: 100, y: 0, width: 50, height: 50)
@@ -123,7 +122,7 @@ class CollectionViewSpec: QuickSpec {
123122
expect((collectionView.subviews[1] as! UILabel).text) == "2"
124123
expect(collectionView.contentSize) == CGSize(width: 100, height: 100)
125124

126-
provider.dataProvider = ArrayDataProvider(data:[9, 8, 5, 6, 7])
125+
provider.dataSource = ArrayDataSource(data:[9, 8, 5, 6, 7])
127126
collectionView.layoutIfNeeded()
128127
expect(collectionView.reloadCount) == 2
129128

@@ -134,7 +133,7 @@ class CollectionViewSpec: QuickSpec {
134133
expect((collectionView.subviews[1] as! UILabel).text) == "8"
135134
expect(collectionView.contentSize) == CGSize(width: 100, height: 150)
136135

137-
provider.dataProvider = ArrayDataProvider(data:[8, 5, 6, 7])
136+
provider.dataSource = ArrayDataSource(data:[8, 5, 6, 7])
138137
collectionView.layoutIfNeeded()
139138
expect(collectionView.reloadCount) == 3
140139

@@ -183,12 +182,12 @@ class CollectionViewSpec: QuickSpec {
183182
expect((collectionView.cell(at: 0) as! UILabel).text) == "1"
184183
expect((collectionView.cell(at: 1) as! UILabel).text) == "2"
185184

186-
provider = CollectionProvider(
187-
dataProvider: ArrayDataProvider(data: [0,0,0,0], identifierMapper: { _, data in "\(data)" }),
188-
viewUpdater: { (label: UILabel, data: Int, index: Int) in
185+
provider = BasicProvider(
186+
dataSource: ArrayDataSource(data: [0, 0, 0, 0]),
187+
viewSource: ClosureViewSource(viewUpdater: { (label: UILabel, data: Int, index: Int) in
189188
label.text = "\(data)"
190-
},
191-
sizeProvider: { (index: Int, data: Int, collectionSize: CGSize) -> CGSize in
189+
}),
190+
sizeSource: { (index: Int, data: Int, collectionSize: CGSize) -> CGSize in
192191
return CGSize(width: 50, height: 50)
193192
}
194193
)
@@ -219,18 +218,19 @@ class CollectionViewSpec: QuickSpec {
219218

220219
it("handles tap") {
221220
var lastTappedIndex: Int = -1
222-
provider = CollectionProvider(
223-
data: [0, 1, 2, 3],
224-
viewUpdater: { (label: UILabel, data: Int, index: Int) in
221+
provider = BasicProvider(
222+
dataSource: ArrayDataSource(data: [0, 1, 2, 3]),
223+
viewSource: ClosureViewSource(viewUpdater: { (label: UILabel, data: Int, index: Int) in
225224
label.text = "\(data)"
226-
},
227-
sizeProvider: { (index: Int, data: Int, collectionSize: CGSize) -> CGSize in
225+
}),
226+
sizeSource: { (index: Int, data: Int, collectionSize: CGSize) -> CGSize in
228227
return CGSize(width: 50, height: 50)
229228
},
230-
tapHandler: { view, index, dataProvider in
231-
lastTappedIndex = index
229+
tapHandler: { context in
230+
lastTappedIndex = context.index
232231
}
233232
)
233+
234234
collectionView.provider = provider
235235
collectionView.frame = CGRect(x: 0, y: 0, width: 500, height: 50)
236236
collectionView.layoutIfNeeded()

CollectionKitTests/CollectionComposerSpec.swift renamed to CollectionKitTests/ComposedProviderSpec.swift

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
//
2-
// CollectionComposerSpec.swift
2+
// ComposedProviderSpec.swift
33
// CollectionKitTests
44
//
55
// Created by Luke Zhao on 2017-09-02.
@@ -10,16 +10,16 @@
1010
import Quick
1111
import Nimble
1212

13-
class CollectionComposerSpec: QuickSpec {
13+
class ComposedProviderSpec: QuickSpec {
1414

1515
override func spec() {
1616

17-
describe("CollectionComposer") {
17+
describe("ComposedProvider") {
1818
it("combines multiple provider") {
1919
let provider1 = SimpleTestProvider(data: [1, 2, 3, 4])
2020
let provider2 = SimpleTestProvider(data: ["a", "b"])
2121
let provider3 = SimpleTestProvider(data: ["hello", "collectionKit"])
22-
let composer = CollectionComposer(provider1, provider2, provider3)
22+
let composer = ComposedProvider(sections: [provider1, provider2, provider3])
2323
let collectionView = CollectionView(provider: composer)
2424
collectionView.frame = CGRect(x: 0, y: 0, width: 300, height: 500)
2525
collectionView.layoutIfNeeded()
@@ -36,14 +36,16 @@ class CollectionComposerSpec: QuickSpec {
3636
let provider3 = SimpleTestProvider(data: ["hello", "collectionKit"])
3737
let provider4 = SimpleTestProvider(data: [])
3838
let provider5 = SimpleTestProvider(data: [5.0])
39-
let composer = CollectionComposer(
40-
CollectionComposer(
41-
CollectionComposer(provider1,
42-
provider2),
43-
provider3),
44-
CollectionComposer(),
45-
CollectionComposer(provider4,
46-
provider5)
39+
let composer = ComposedProvider(
40+
sections: [
41+
ComposedProvider(
42+
sections: [
43+
ComposedProvider(sections: [provider1, provider2]),
44+
provider3
45+
]),
46+
ComposedProvider(),
47+
ComposedProvider(sections: [provider4, provider5])
48+
]
4749
)
4850
let collectionView = CollectionView(provider: composer)
4951
collectionView.frame = CGRect(x: 0, y: 0, width: 300, height: 500)
@@ -60,7 +62,7 @@ class CollectionComposerSpec: QuickSpec {
6062
let provider1 = SimpleTestProvider(data: [1, 2, 3, 4])
6163
let provider2 = SimpleTestProvider(data: ["a", "b"])
6264
let provider3 = SimpleTestProvider(data: ["hello", "collectionKit"])
63-
let composer = CollectionComposer(provider1, provider2, provider3)
65+
let composer = ComposedProvider(sections: [provider1, provider2, provider3])
6466
let collectionView = CollectionView(provider: composer)
6567
collectionView.frame = CGRect(x: 0, y: 0, width: 300, height: 500)
6668
collectionView.layoutIfNeeded()
@@ -79,7 +81,7 @@ class CollectionComposerSpec: QuickSpec {
7981
expect(collectionView.reloadCount) == 3
8082
expect(collectionView.subviews[0].frame.origin) != CGPoint.zero
8183

82-
composer.presenter = ZoomPresenter()
84+
composer.animator = ScaleAnimator()
8385
collectionView.layoutIfNeeded()
8486
expect(collectionView.reloadCount) == 4
8587

@@ -100,15 +102,14 @@ class CollectionComposerSpec: QuickSpec {
100102
let provider3 = SimpleTestProvider(data: ["hello", "collectionKit"])
101103
let provider4 = SimpleTestProvider(data: [])
102104
let provider5 = SimpleTestProvider(data: [5.0])
103-
let composer = CollectionComposer(
104-
CollectionComposer(
105-
CollectionComposer(provider1,
106-
provider2),
107-
provider3),
108-
CollectionComposer(),
109-
CollectionComposer(provider4,
110-
provider5)
111-
)
105+
let composer = ComposedProvider(sections: [
106+
ComposedProvider(sections: [
107+
ComposedProvider(sections: [provider1, provider2]),
108+
provider3
109+
]),
110+
ComposedProvider(),
111+
ComposedProvider(sections: [provider4, provider5])
112+
])
112113
let collectionView = CollectionView(provider: composer)
113114
collectionView.frame = CGRect(x: 0, y: 0, width: 300, height: 500)
114115
collectionView.layoutIfNeeded()
@@ -127,22 +128,28 @@ class CollectionComposerSpec: QuickSpec {
127128
var lastTappedText: String?
128129
let provider1 = SimpleTestProvider(data: [1, 2, 3, 4])
129130
let provider2 = SimpleTestProvider(data: ["a", "b"])
130-
let tapProvider = CollectionProvider(
131-
data: [11, 12],
132-
viewUpdater: { (label: UILabel, data: Int, index: Int) in
131+
132+
let tapProvider = BasicProvider(
133+
dataSource: ArrayDataSource(data: [11, 12]),
134+
viewSource: ClosureViewSource(viewUpdater: {
135+
(label: UILabel, data: Int, index: Int) in
133136
label.text = "\(data)"
134-
},
135-
sizeProvider: { (index: Int, data: Int, collectionSize: CGSize) -> CGSize in
137+
}),
138+
sizeSource: { (index: Int, data: Int, collectionSize: CGSize) -> CGSize in
136139
return CGSize(width: 50, height: 50)
137140
},
138-
tapHandler: { view, index, dataProvider in
139-
lastTappedText = view.text
141+
tapHandler: { context in
142+
lastTappedText = context.view.text
140143
}
141144
)
142-
let composer = CollectionComposer(
143-
CollectionComposer(provider1,
144-
provider2),
145-
tapProvider
145+
146+
let composer = ComposedProvider(
147+
sections: [
148+
ComposedProvider(
149+
sections: [provider1,
150+
provider2]),
151+
tapProvider
152+
]
146153
)
147154
let collectionView = CollectionView(provider: composer)
148155
collectionView.frame = CGRect(x: 0, y: 0, width: 200, height: 500)

CollectionKitTests/FlowLayoutSpec.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,39 @@ class FlowLayoutSpec: QuickSpec {
1515
override func spec() {
1616
describe("FlowLayout") {
1717
it("should not crash when parent size is zero") {
18-
let layout = FlowLayout<CGSize>()
18+
let layout = FlowLayout()
1919
layout.mockLayout(parentSize: (0, 0))
2020
expect(layout.frames.count).to(equal(0))
2121
layout.mockLayout(parentSize: (0, 0), (200, 50))
2222
expect(layout.frames.count).to(equal(1))
2323
}
2424

2525
it("should wrap child") {
26-
let layout = FlowLayout<CGSize>()
26+
let layout = FlowLayout()
2727
layout.mockLayout(parentSize: (100, 100), (50, 50), (50, 50), (50, 50))
2828
expect(layout.frames).to(equal(frames((0, 0, 50, 50), (50, 0, 50, 50), (0, 50, 50, 50))))
2929
}
3030

3131
it("should work with linespacing") {
32-
let layout = FlowLayout<CGSize>(lineSpacing: 10)
32+
let layout = FlowLayout(lineSpacing: 10)
3333
layout.mockLayout(parentSize: (100, 300), (100, 100), (100, 100), (100, 100))
3434
expect(layout.frames).to(equal(frames((0, 0, 100, 100), (0, 110, 100, 100), (0, 220, 100, 100))))
3535
}
3636

3737
it("should work with interitemspacing") {
38-
let layout = FlowLayout<CGSize>(interitemSpacing: 10)
38+
let layout = FlowLayout(interitemSpacing: 10)
3939
layout.mockLayout(parentSize: (130, 100), (50, 50), (50, 50), (50, 50))
4040
expect(layout.frames).to(equal(frames((0, 0, 50, 50), (60, 0, 50, 50), (0, 50, 50, 50))))
4141
}
4242

4343
it("should work with interitemspacing and linespacing") {
44-
let layout = FlowLayout<CGSize>(lineSpacing: 10, interitemSpacing: 10)
44+
let layout = FlowLayout(lineSpacing: 10, interitemSpacing: 10)
4545
layout.mockLayout(parentSize: (130, 130), (50, 50), (50, 50), (50, 50))
4646
expect(layout.frames).to(equal(frames((0, 0, 50, 50), (60, 0, 50, 50), (0, 60, 50, 50))))
4747
}
4848

4949
it("should not display cells outside of the visible area") {
50-
let layout = FlowLayout<CGSize>().transposed()
50+
let layout = FlowLayout().transposed()
5151
layout.mockLayout(parentSize: (100, 50), (50, 50), (50, 50), (50, 50), (50, 50))
5252
let visible = layout.visibleIndexes(visibleFrame: CGRect(x: 50, y: 0, width: 100, height: 50))
5353
expect(visible).to(equal([1, 2]))

CollectionKitTests/RowLayoutSpec.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,27 @@ class RowLayoutSpec: QuickSpec {
1515
override func spec() {
1616
describe("RowLayout") {
1717
it("should not crash when parent size is zero") {
18-
let layout = RowLayout<CGSize>()
18+
let layout = RowLayout()
1919
layout.mockLayout(parentSize: (0, 0))
2020
expect(layout.frames.count).to(equal(0))
2121
layout.mockLayout(parentSize: (0, 0), (200, 50))
2222
expect(layout.frames.count).to(equal(1))
2323
}
2424

2525
it("should work without flex value") {
26-
let layout = RowLayout<CGSize>()
26+
let layout = RowLayout()
2727
layout.mockLayout((200, 50), (200, 50), (200, 50))
2828
expect(layout.frames).to(equal(frames((0,0,200,50), (200,0,200,50), (400,0,200,50))))
2929
}
3030

3131
it("should expand flexed item when there is space") {
32-
let layout = RowLayout<CGSize>("1")
32+
let layout = RowLayout("1")
3333
layout.mockLayout(parentSize: (700, 50), (200, 50), (200, 50), (200, 50))
3434
expect(layout.frames).to(equal(frames((0,0,200,50), (200,0, 300, 50), (500,0,200,50))))
3535
}
3636

3737
it("should not expand flex item where there is not enough space") {
38-
let layout = RowLayout<CGSize>("1", "2")
38+
let layout = RowLayout("1", "2")
3939
layout.mockLayout(parentSize: (250, 300), (250, 200), (50, 200), (50, 200))
4040
expect(layout.frames).to(equal(frames((0,0,250,200), (250,0,50,200), (300,0,50,200))))
4141
}

CollectionKitTests/TestUils.swift

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,51 @@ import UIKit
1010
import CollectionKit
1111
import UIKit.UIGestureRecognizerSubclass
1212

13-
extension CollectionLayout where Data == CGSize {
13+
extension Layout {
1414

15-
func mockLayout(parentSize: (CGFloat, CGFloat) = (300, 300), _ childSizes: (CGFloat, CGFloat)...) {
16-
layout(collectionSize: CGSize(width: parentSize.0, height: parentSize.1),
17-
dataProvider: ArrayDataProvider(data: sizes(childSizes)),
18-
sizeProvider: { (index, data, collectionSize) -> CGSize in
19-
return data
20-
})
15+
struct MockLayoutContext: LayoutContext {
16+
var parentSize: (CGFloat, CGFloat)
17+
var childSizes: [(CGFloat, CGFloat)]
18+
var collectionSize: CGSize {
19+
return CGSize(width: parentSize.0, height: parentSize.1)
20+
}
21+
var numberOfItems: Int {
22+
return childSizes.count
23+
}
24+
func data(at: Int) -> Any {
25+
return childSizes[at]
26+
}
27+
func identifier(at: Int) -> String {
28+
return "\(at)"
29+
}
30+
func size(at: Int, collectionSize: CGSize) -> CGSize {
31+
let size = childSizes[at]
32+
return CGSize(width: size.0, height: size.1)
33+
}
2134
}
2235

36+
func mockLayout(parentSize: (CGFloat, CGFloat) = (300, 300), _ childSizes: (CGFloat, CGFloat)...) {
37+
layout(context: MockLayoutContext(parentSize: parentSize, childSizes: childSizes))
38+
}
2339
}
2440

25-
class SimpleTestProvider<Data>: CollectionProvider<Data, UILabel> {
41+
class SimpleTestProvider<Data>: BasicProvider<Data, UILabel> {
2642

2743
var data: [Data] {
28-
get { return (dataProvider as! ArrayDataProvider<Data>).data }
29-
set { (dataProvider as! ArrayDataProvider<Data>).data = newValue }
44+
get { return (dataSource as! ArrayDataSource<Data>).data }
45+
set { (dataSource as! ArrayDataSource<Data>).data = newValue }
3046
}
3147

3248
convenience init(data: [Data]) {
3349
self.init(
34-
dataProvider: ArrayDataProvider(data: data, identifierMapper: { _, data in "\(data)" }),
35-
viewUpdater: { (label: UILabel, data: Data, index: Int) in
50+
dataSource: ArrayDataSource(data: data, identifierMapper: { _, data in "\(data)" }),
51+
viewSource: ClosureViewSource(viewUpdater: { (label: UILabel, data: Data, index: Int) in
3652
label.backgroundColor = .red
3753
label.layer.cornerRadius = 8
3854
label.textAlignment = .center
3955
label.text = "\(data)"
40-
},
41-
sizeProvider: { (index: Int, data: Data, collectionSize: CGSize) -> CGSize in
56+
}),
57+
sizeSource: { (index: Int, data: Data, collectionSize: CGSize) -> CGSize in
4258
return CGSize(width: 50, height: 50)
4359
}
4460
)

0 commit comments

Comments
 (0)