Skip to content

Commit 793f587

Browse files
authored
Create README.md
1 parent 7dd298b commit 793f587

File tree

1 file changed

+206
-0
lines changed

1 file changed

+206
-0
lines changed

README.md

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
# ReusableDataSource
2+
3+
Never again write a custom UITableView or UICollectionView data source. **Disclamer**: not really, but this is a good start.
4+
5+
## Implementation
6+
7+
Implement ```ReusablePresenter``` protocol on a reusable view.
8+
9+
```Swift
10+
class TextTableViewCell: UITableViewCell, ReusablePresenter {
11+
func present(viewModel: String) {
12+
textLabel?.text = viewModel
13+
}
14+
}
15+
```
16+
17+
Create view models and specify presenter types using ```ReusableViewModel``` struct.
18+
19+
```Swift
20+
let viewModels = [
21+
ReusableViewModel<TextTableViewCell>(viewModel: "Cell 1").anyPresentable,
22+
ReusableViewModel<TextTableViewCell>(viewModel: "Cell 2").anyPresentable,
23+
ReusableViewModel<ImageTextTableViewCell>(
24+
viewModel: ImageTextTableViewCellViewModel(
25+
textViewModel: "Cell 3",
26+
imageViewModel: #imageLiteral(resourceName: "filter")
27+
)
28+
).anyPresentable,
29+
ReusableViewModel<TextTableViewCell>(viewModel: "Cell 2").anyPresentable
30+
]
31+
```
32+
33+
Create a ```ReusableTableViewDataSource``` and present the view models.
34+
35+
```Swift
36+
let dataSource = ReusableTableViewDataSource()
37+
38+
tableView.dataSource = dataSource
39+
40+
dataSource.present(presentableViewModels: reusableViewModels, on: tableView)
41+
```
42+
43+
That's it! The reusable data source manages the cell creation and data presentation. Check it out by running the demo project.
44+
45+
![Demo project](https://image.ibb.co/g9BT3n/Screen_Shot_2018_05_04_at_23_51_30.png)
46+
47+
## Installation
48+
49+
### Carthage
50+
51+
```
52+
github "Rep2/ReusableDataSource" ~> 0.1
53+
```
54+
55+
## Detailed overview
56+
57+
```ReusablePresenter``` defines a view model type that the reusable view can present.
58+
59+
```Swift
60+
protocol ReusablePresenter {
61+
associatedtype ViewModel
62+
63+
static var source: ReusablePresenterSource { get }
64+
65+
func present(viewModel: ViewModel)
66+
}
67+
```
68+
69+
To satisfy this protocol all that is needed is to implement ```present(viewModel: ViewModel)``` function. ```associatedtype``` is inferred from the function.
70+
71+
```source``` value determines how the reusable view is created. It's default value is ```class```. Ignore it for now.
72+
73+
```Swift
74+
enum ReusablePresenterSource {
75+
case nib
76+
case `class`
77+
case storyboard
78+
}
79+
```
80+
81+
```ReusableViewModel``` connects the presenter and the view model that the presenter knows how to present.
82+
83+
```Swift
84+
struct ReusableViewModel<Presenter: ReusablePresenter> {
85+
let viewModel: Presenter.ViewModel
86+
87+
init(viewModel: Presenter.ViewModel) {
88+
self.viewModel = viewModel
89+
}
90+
}
91+
```
92+
93+
Simply create it by specifying ```ReusablePresenter``` type and passing the associated ```ViewModel```.
94+
95+
```Swift
96+
ReusableViewModel<TextTableViewCell>(viewModel: "Cell 1")
97+
```
98+
99+
```ReusableTableViewDataSource``` and ```ReusableCollectionViewDataSource``` do the hard work. Initialize and set them as ```UITableView``` or ```UICollectionView``` data source.
100+
101+
```Swift
102+
let dataSource = ReusableTableViewDataSource()
103+
104+
tableView.dataSource = dataSource
105+
```
106+
107+
To be able to mix and match different view models we need to do something called ```Type erasure```. Take a look at this article to get the gist of it [Swift: Attempting to Understand Type Erasure](https://www.natashatherobot.com/swift-type-erasure/).
108+
109+
Basically, we need to remove the generic part of a ```ReusableViewModel``` to be able to put it into an array.
110+
111+
Generic part removal is done by ```AnyTableViewPresentableViewModel``` and ```AnyCollectionViewPresentableViewModel``` for ```UITableViewCell``` and ```UICollectionViewCell``` respectively. The process is simmilar to how Swift Stdlib handles ```Type erasure```. I got the motivation from [Type-erasure in Stdlib](http://robnapier.net/type-erasure-in-stdlib).
112+
113+
```Swift
114+
class AnyTableViewPresentableViewModel {
115+
let dequeueAndPresentCellCallback: (UITableView) -> UITableViewCell
116+
let registerCellCallback: (UITableView) -> Void
117+
118+
init<Presenter: ReusablePresenter>(base: ReusableViewModel<Presenter>) where Presenter: UITableViewCell {
119+
self.dequeueAndPresentCellCallback = { (tableView: UITableView) -> UITableViewCell in
120+
tableView.dequeueAndPresent(presentableViewModel: base, for: IndexPath(item: 0, section: 0))
121+
}
122+
123+
self.registerCellCallback = { (tableView: UITableView) in
124+
tableView.register(cell: Presenter.self, reusableCellSource: Presenter.source)
125+
}
126+
}
127+
}
128+
```
129+
130+
They remove the generic part of a ```ReusableViewModel``` keeping the information we need -> how to register and dequeue the cell.
131+
132+
Property ```anyPresentable``` of ```ReusableViewModel``` can be used to simplify the process.
133+
134+
```Swift
135+
extension ReusableViewModel where Presenter: UITableViewCell {
136+
var anyPresentable: AnyTableViewPresentableViewModel {
137+
return AnyTableViewPresentableViewModel(base: self)
138+
}
139+
}
140+
141+
extension ReusableViewModel where Presenter: UICollectionViewCell {
142+
var anyPresentable: AnyCollectionViewPresentableViewModel {
143+
return AnyCollectionViewPresentableViewModel(base: self)
144+
}
145+
}
146+
```
147+
148+
Finally, pass the ```Type erased``` view models to the reusable data source.
149+
150+
```Swift
151+
dataSource.present(presentableViewModels: viewModels, on: tableView)
152+
```
153+
154+
Reusable data sources use the ```PresentableViewModel``` dequeue method to create the cell of a proper type and present the view model.
155+
156+
```Swift
157+
extension UITableView {
158+
/**
159+
Returns a "cell" on which `presentableViewModel` was presented.
160+
161+
- Important: Causes the app to crashes with `NSInternalInconsistencyException` if the `PresentingCell` type isn't previously registered.
162+
*/
163+
func dequeueAndPresent<Presenter: ReusablePresenter>(presentableViewModel: ReusableViewModel<Presenter>, for indexPath: IndexPath) -> Presenter
164+
where Presenter: UITableViewCell {
165+
let cell = dequeueReusableCell(for: indexPath) as Presenter
166+
167+
cell.present(viewModel: presentableViewModel.viewModel)
168+
169+
return cell
170+
}
171+
172+
/**
173+
Registers a reusable "cell" using `CustomStringConvertible` as the reuese identifier.
174+
175+
- Important: Call before `dequeueReusableCell(for:)` to avoid `NSInternalInconsistencyException`.
176+
*/
177+
public func register<T: UITableViewCell>(cell: T.Type, reusableCellSource: ReusablePresenterSource) {
178+
switch reusableCellSource {
179+
case .nib:
180+
register(UINib(nibName: String(describing: cell), bundle: nil), forCellReuseIdentifier: String(describing: cell))
181+
case .class, .storyboard:
182+
register(T.self, forCellReuseIdentifier: String(describing: cell.self))
183+
}
184+
}
185+
186+
/**
187+
Returns a "cell" of a given type using `CustomStringConvertible` as the reuese identifier.
188+
189+
- Important: Force unwraps the "cell". Causes the app to crashes with `NSInternalInconsistencyException` if the cell type isn't previously registered.
190+
*/
191+
public func dequeueReusableCell<T: UITableViewCell>(for indexPath: IndexPath) -> T {
192+
return dequeueReusableCell(for: indexPath)!
193+
}
194+
195+
/**
196+
Returns an optional "cell" of a given type using `CustomStringConvertible` as the reuese identifier.
197+
198+
- Important: Causes the app to crashes with `NSInternalInconsistencyException` if the cell type isn't previously registered.
199+
*/
200+
public func dequeueReusableCell<T: UITableViewCell>(for indexPath: IndexPath) -> T? {
201+
return dequeueReusableCell(withIdentifier: String(describing: T.self), for: indexPath) as? T
202+
}
203+
}
204+
```
205+
206+
This is also where the ```ReusablePresenterSource``` comes into play. Data source automatically registers ```reuseIdentifier``` based on ```ReusablePresenter.source``` property. To disable this behavior set data sources ```automaticallyRegisterReuseIdentifiers``` to ```fasle```.

0 commit comments

Comments
 (0)