-
Notifications
You must be signed in to change notification settings - Fork 15
/
Copy pathImagesViewController.swift
247 lines (183 loc) · 6.87 KB
/
ImagesViewController.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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
//
// ViewController.swift
// Example
//
// Created by Nacho Soto on 11/30/18.
// Copyright © 2018 Nacho Soto. All rights reserved.
//
import UIKit
import Result
import ReactiveSwift
final class ImagesViewController: UIViewController {
private let fetcher: ImageFetcher
init(fetcher: ImageFetcher) {
self.fetcher = fetcher
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
var openImageRequests: Signal<FlickrImageData, NoError> {
return self.collectionView.openImageRequests
}
// MARK: -
override func loadView() {
self.view = {
let view = UIView()
view.addSubview(self.collectionView)
return view
}()
}
override func viewDidLoad() {
super.viewDidLoad()
self.searchBar.delegate = self
self.navigationItem.titleView = self.searchBar
self.queries
.producer
.skipRepeats()
.filter { !$0.isEmpty }
.debounce(ImagesViewController.debounceDuration, on: QueueScheduler())
.flatMap(.latest) { [fetcher = self.fetcher] query in
fetcher
.fetchImages(query: query)
.on(starting: { print("Searching: \(query)") })
}
.observe(on: UIScheduler())
.take(during: self.reactive.lifetime)
.startWithResult { result in
switch result {
case .success(let images):
self.collectionView.images = images
case .failure(let error):
print("Error: \(error)")
}
}
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.collectionView.frame = self.view.bounds
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.searchBar.becomeFirstResponder()
}
// MARK: -
private let queries = MutableProperty<String>("")
// MARK: - Views
private lazy var collectionView = CollectionView()
private let searchBar: UISearchBar = {
let searchBar = UISearchBar()
searchBar.searchBarStyle = .minimal
searchBar.placeholder = "Search…"
searchBar.sizeToFit()
searchBar.isTranslucent = true
searchBar.backgroundImage = UIImage()
return searchBar
}()
// MARK: -
private static let debounceDuration: TimeInterval = 0.3
}
extension ImagesViewController: UISearchBarDelegate {
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.queries.value = searchText
}
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
searchBar.resignFirstResponder()
}
}
private final class CollectionView: UIView {
private let dataSource = DataSource()
private let collectionView: UICollectionView
private let layout: UICollectionViewFlowLayout = {
let layout = UICollectionViewFlowLayout()
return layout
}()
init() {
self.collectionView = UICollectionView(
frame: .zero,
collectionViewLayout: self.layout
)
(self.openImageRequests, self.openImageRequestsObserver) = Signal.pipe()
super.init(frame: .zero)
self.collectionView.contentInsetAdjustmentBehavior = .always
self.collectionView.dataSource = self.dataSource
self.collectionView.delegate = self
self.collectionView.collectionViewLayout = self.layout
self.collectionView.register(Cell.self, forCellWithReuseIdentifier: Cell.reuseIdentifier)
self.addSubview(self.collectionView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: -
var images: [FlickrImageData] = [] {
didSet {
self.dataSource.images = self.images
self.collectionView.reloadData()
}
}
let openImageRequests: Signal<FlickrImageData, NoError>
private let openImageRequestsObserver: Signal<FlickrImageData, NoError>.Observer
// MARK: -
override func layoutSubviews() {
super.layoutSubviews()
guard self.bounds.size.width > 0 else { return }
let availableWidth = self.bounds.size.width
- self.collectionView.adjustedContentInset.left
- self.collectionView.adjustedContentInset.right
let imageWidth: CGFloat = (availableWidth / CGFloat(CollectionView.imagesPerRow)) - self.layout.minimumLineSpacing
self.layout.itemSize = CGSize(
width: imageWidth,
height: imageWidth
)
self.collectionView.frame = self.bounds
}
private static let imagesPerRow: Int = 3
}
extension CollectionView: UICollectionViewDelegate {
func collectionView(
_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath
) {
self.openImageRequestsObserver.send(value: self.images[indexPath.item])
}
}
private final class Cell: UICollectionViewCell {
private let imageView: Photos.ImageView = {
return Photos.createAspectFillView(initialFrame: .zero)
}()
override init(frame: CGRect) {
super.init(frame: frame)
self.contentView.addSubview(self.imageView)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
self.imageView.frame = self.bounds
}
override func prepareForReuse() {
super.prepareForReuse()
self.data = nil
}
// MARK: -
var data: FlickrImageData? {
get { return self.imageView.data?.imageData }
set { self.imageView.data = newValue.map(Photos.Data.init) }
}
static let reuseIdentifier: String = "cell"
}
private final class DataSource: NSObject, UICollectionViewDataSource {
var images: [FlickrImageData] = []
@objc func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
@objc func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.images.count
}
@objc func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Cell.reuseIdentifier, for: indexPath) as! Cell
cell.data = self.images[indexPath.item]
return cell
}
}