forked from HamzaGhazouani/HGPlaceholders
/
PlaceholderDataSourceDelegate.swift
218 lines (170 loc) · 8.6 KB
/
PlaceholderDataSourceDelegate.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
//
// PlaceholderDataSourceDelegate.swift
// Pods
//
// Created by Hamza Ghazouani on 20/07/2017.
//
//
import Foundation
/// This class is responsible for implementing the `UITableViewDataSource` and `UITableViewDelegate` protocols.
/// Each placeholder view is an tableview with one cell, that takes all the tableview frame
class PlaceholderDataSourceDelegate: NSObject {
// MARK: properties
/// The placeholder object
let placeholder: Placeholder
// MARK: init methods
/// Create and return a PlaceholderDataSourceDelegate object with the specified Placeholder
///
/// - Parameter placeholder: the placeholder object
init(placeholder: Placeholder) {
self.placeholder = placeholder
}
// MARK: Utilities methods
/// fill the placeholder cell to the texts and styles
///
/// - Parameters:
/// - cell: the cell can be an UITableViewCell or UICollectionViewCell, but it must be conform to the protocol CellPlaceholding
/// - placeholder: the placeholder object
/// - tintColor: the tint color to apply
func fill(cell: CellPlaceholding, to placeholder: Placeholder, tintColor: UIColor?) {
/* if the the placeholder created by xib and data/style are nil, we should keep the xib data/style */
// apply style
if let style = placeholder.style {
cell.apply(style: style, tintColor: tintColor)
}
// apply data
if let data = placeholder.data {
cell.apply(data: data)
}
}
/// Animate the cell (UICollectionViewCell / UITableViewCell)
///
/// - Parameter cell: the cell to animate, it should be conform to the protocol CellPlaholding
func animate(cell: CellPlaceholding) {
// animate the imageView
guard let imageView = cell.placeholderImageView else { return }
let rotate = CGAffineTransform(rotationAngle: -0.2)
let stretchAndRotate = rotate.scaledBy(x: 0.5, y: 0.5)
imageView.transform = stretchAndRotate
imageView.alpha = 0.5
UIView.animate(withDuration: 1.5, delay: 0.0, usingSpringWithDamping: 0.45, initialSpringVelocity: 10.0, options:[.curveEaseOut], animations: {
imageView.alpha = 1.0
let rotate = CGAffineTransform(rotationAngle: 0.0)
let stretchAndRotate = rotate.scaledBy(x: 1.0, y: 1.0)
imageView.transform = stretchAndRotate
}, completion: nil)
}
/// Returns the height of the scroll view by removing the top and bottom inset + the height of the refresh control
///
/// - Parameter scrollView: the scroll view
/// - Returns: the height of the scroll view without refresh control, top and bottom inset
func height(of scrollView: UIScrollView, headerHeight: CGFloat = 0) -> CGFloat {
var height = scrollView.bounds.height
if #available(iOS 10, *) {
if scrollView.refreshControl?.isRefreshing ?? false {
height -= scrollView.refreshControl?.bounds.height ?? 0
}
}
if #available(iOS 11, *) {
height -= (scrollView.adjustedContentInset.top + scrollView.adjustedContentInset.bottom)
} else {
height -= (scrollView.contentInset.top + scrollView.contentInset.bottom)
}
height -= headerHeight
return height
}
}
// MARK: table view data source methods
/**
* The placeholder template data source, adopt the UITableViewDataSource protocol
*/
extension PlaceholderDataSourceDelegate: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let reuseIdentifier = placeholder.cellIdentifier ?? PlaceholderTableViewCell.reuseIdentifier
guard let cell = tableView.dequeueReusableCell(withIdentifier: reuseIdentifier) else {
fatalError(
"Failed to dequeue a cell with identifier \(reuseIdentifier). "
+ "Check that the reuseIdentifier is set properly in your XIB/Storyboard "
+ "and that you registered the cell beforehand"
)
}
cell.selectionStyle = .none
// If the cell does not inherit from PlaceholderTableViewCell, the data and the style can't be applied
guard let placeholderTableViewCell = cell as? PlaceholderTableViewCell else {
return cell
}
fill(cell: placeholderTableViewCell, to: placeholder, tintColor: tableView.tintColor)
// forward action to placeholder delegate
placeholderTableViewCell.onActionButtonTap = { [unowned self] in
guard let placeholderTableView = (tableView as? TableView) else { return }
placeholderTableView.placeholderDelegate?.view(tableView, actionButtonTappedFor: self.placeholder)
}
return cell
}
}
// MARK: - table view delegate methods
/**
* The placeholder template delegate, adopt the UITableViewDelegate protocol
* Implement the method tableView:heightForRowAt: to much cell size to tableview size
* And tableView:willDisplay: to animate the cell if needed
*/
extension PlaceholderDataSourceDelegate: UITableViewDelegate {
// the placeholder cell takes always the size of the table view
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
var tableViewHeight = height(of: tableView, headerHeight: tableView.sectionHeaderHeight)
// subtract tableHeaderView Height out of height
let style = placeholder.style
if style?.shouldShowTableViewHeader != true {
tableViewHeight -= tableView.tableHeaderView?.bounds.height ?? 0
}
// subtract tableFooterView Height out of height
if style?.shouldShowTableViewFooter != true {
tableViewHeight -= tableView.tableFooterView?.bounds.height ?? 0
}
return tableViewHeight
}
// animate the cell
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if placeholder.style == nil || placeholder.style?.isAnimated == false {
return
}
guard let placeholderTableViewCell = cell as? PlaceholderTableViewCell else { return }
animate(cell: placeholderTableViewCell)
}
}
// MARK: - collection view data source methods
extension PlaceholderDataSourceDelegate: UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 1
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let reuseIdentifier = placeholder.cellIdentifier ?? PlaceholderCollectionViewCell.reuseIdentifier
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath)
// If the cell does not inherit from PlaceholderTableViewCell, the data and the style can't be applied
guard let placeholderCollectionViewCell = cell as? PlaceholderCollectionViewCell else {
return cell
}
fill(cell: placeholderCollectionViewCell, to: placeholder, tintColor: collectionView.tintColor)
// forward action to placeholder delegate
placeholderCollectionViewCell.onActionButtonTap = { [unowned self] in
guard let placeholderCollectionView = collectionView as? CollectionView else { return }
placeholderCollectionView.placeholderDelegate?.view(collectionView, actionButtonTappedFor: self.placeholder)
}
return cell
}
}
extension PlaceholderDataSourceDelegate: UICollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
return .zero
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
return .zero
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let collectionViewHeight = height(of: collectionView)
return CGSize(width: collectionView.bounds.width, height: collectionViewHeight)
}
}