// // GifListViewController.swift import UIKit class GifListViewController: UIViewController { private var presenter: GifListPresenter = GifListPresenter() private lazy var collectionView: UICollectionView = { let collectionView = UICollectionView(frame: .zero, collectionViewLayout: GifListCollectionViewFlowLayout()) collectionView.register(GifCollectionViewCell.self, forCellWithReuseIdentifier: GifCollectionViewCell.reuseIdentifier) collectionView.keyboardDismissMode = .onDrag return collectionView }() private lazy var searchBar: UISearchBar = { let sb = UISearchBar() sb.delegate = self sb.placeholder = "search library..." return sb }() /// Indicator view that we expect to show when no data is present private lazy var activityIndicator: UIActivityIndicatorView = { let aIndicator = UIActivityIndicatorView() aIndicator.hidesWhenStopped = true return aIndicator }() /// Delay the search by couple of ms so the web devs will like us private var delayedWorkItem: DispatchWorkItem? override func viewDidLoad() { super.viewDidLoad() self.setupUI() self.setupSelf() self.presenter.getData(query: "") } private func setupUI() { self.view.addSubview(self.collectionView) self.view.addSubview(self.activityIndicator) self.initialConstraintSetup() } private func setupSelf() { self.navigationItem.titleView = self.searchBar presenter.delegate = self collectionView.delegate = self collectionView.dataSource = self collectionView.prefetchDataSource = self } } // MARK: - Constraints extension GifListViewController { /*! Initial constraints setup */ private func initialConstraintSetup() { self.collectionView.translatesAutoresizingMaskIntoConstraints = false self.activityIndicator.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ self.collectionView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor), self.collectionView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor), self.collectionView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor), self.collectionView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor), self.activityIndicator.centerXAnchor.constraint(equalTo: self.view.centerXAnchor), self.activityIndicator.centerYAnchor.constraint(equalTo: self.view.centerYAnchor) ]) } } // MARK: - Presenter delegate extension GifListViewController: GifListPresenterDelegate { func presenterDidReceiveInitialData() { DispatchQueue.main.async { self.collectionView.setContentOffset(CGPoint(x: 0, y: self.collectionView.safeAreaInsets.top), animated: false) self.collectionView.reloadData() if self.activityIndicator.isAnimating == true { self.activityIndicator.stopAnimating() } } } func presenterDidReceiveAdditionalData(at indexPaths: [IndexPath]) { DispatchQueue.main.async { self.collectionView.insertItems(at: indexPaths) } } func presenterWillStartLoadingForEmptyData() { DispatchQueue.main.async { self.activityIndicator.startAnimating() } } } // MARK: - SearchBar delegate extension GifListViewController: UISearchBarDelegate { func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { self.delayedWorkItem?.cancel() delayedWorkItem = DispatchWorkItem { [weak self] in self?.presenter.getData(query: searchText) } DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.2, execute: delayedWorkItem!) } } // MARK: - CollectionView delegate and data source extension GifListViewController: UICollectionViewDataSource, UICollectionViewDelegate { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return presenter.gifsList.count } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: GifCollectionViewCell.reuseIdentifier, for: indexPath) as! GifCollectionViewCell cell.gif = presenter.gifsList[indexPath.row] return cell } } extension GifListViewController: UICollectionViewDataSourcePrefetching { func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) { guard presenter.isLoadingNewPage == false else { return } for indexPath in indexPaths { if indexPath.row > presenter.gifsList.count - Constants.GiphyPaginationLimit/2 { delayedWorkItem?.cancel() presenter.getData(query: self.searchBar.text ?? "") break } } } }