GifListViewController.swift 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. //
  2. // GifListViewController.swift
  3. import UIKit
  4. class GifListViewController: UIViewController {
  5. private var presenter: GifListPresenter = GifListPresenter()
  6. private lazy var collectionView: UICollectionView = {
  7. let collectionView = UICollectionView(frame: .zero, collectionViewLayout: GifListCollectionViewFlowLayout())
  8. collectionView.register(GifCollectionViewCell.self, forCellWithReuseIdentifier: GifCollectionViewCell.reuseIdentifier)
  9. collectionView.keyboardDismissMode = .onDrag
  10. return collectionView
  11. }()
  12. private lazy var searchBar: UISearchBar = {
  13. let sb = UISearchBar()
  14. sb.delegate = self
  15. sb.placeholder = "search library..."
  16. return sb
  17. }()
  18. /// Indicator view that we expect to show when no data is present
  19. private lazy var activityIndicator: UIActivityIndicatorView = {
  20. let aIndicator = UIActivityIndicatorView()
  21. aIndicator.hidesWhenStopped = true
  22. return aIndicator
  23. }()
  24. /// Delay the search by couple of ms so the web devs will like us
  25. private var delayedWorkItem: DispatchWorkItem?
  26. override func viewDidLoad() {
  27. super.viewDidLoad()
  28. self.setupUI()
  29. self.setupSelf()
  30. self.presenter.getData(query: "")
  31. }
  32. private func setupUI() {
  33. self.view.addSubview(self.collectionView)
  34. self.view.addSubview(self.activityIndicator)
  35. self.initialConstraintSetup()
  36. }
  37. private func setupSelf() {
  38. self.navigationItem.titleView = self.searchBar
  39. presenter.delegate = self
  40. collectionView.delegate = self
  41. collectionView.dataSource = self
  42. collectionView.prefetchDataSource = self
  43. }
  44. }
  45. // MARK: - Constraints
  46. extension GifListViewController {
  47. /*!
  48. Initial constraints setup
  49. */
  50. private func initialConstraintSetup() {
  51. self.collectionView.translatesAutoresizingMaskIntoConstraints = false
  52. self.activityIndicator.translatesAutoresizingMaskIntoConstraints = false
  53. NSLayoutConstraint.activate([
  54. self.collectionView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
  55. self.collectionView.leadingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.leadingAnchor),
  56. self.collectionView.trailingAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.trailingAnchor),
  57. self.collectionView.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
  58. self.activityIndicator.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
  59. self.activityIndicator.centerYAnchor.constraint(equalTo: self.view.centerYAnchor)
  60. ])
  61. }
  62. }
  63. // MARK: - Presenter delegate
  64. extension GifListViewController: GifListPresenterDelegate {
  65. func presenterDidReceiveInitialData() {
  66. DispatchQueue.main.async {
  67. self.collectionView.setContentOffset(CGPoint(x: 0, y: self.collectionView.safeAreaInsets.top), animated: false)
  68. self.collectionView.reloadData()
  69. if self.activityIndicator.isAnimating == true {
  70. self.activityIndicator.stopAnimating()
  71. }
  72. }
  73. }
  74. func presenterDidReceiveAdditionalData(at indexPaths: [IndexPath]) {
  75. DispatchQueue.main.async {
  76. self.collectionView.insertItems(at: indexPaths)
  77. }
  78. }
  79. func presenterWillStartLoadingForEmptyData() {
  80. DispatchQueue.main.async {
  81. self.activityIndicator.startAnimating()
  82. }
  83. }
  84. }
  85. // MARK: - SearchBar delegate
  86. extension GifListViewController: UISearchBarDelegate {
  87. func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
  88. self.delayedWorkItem?.cancel()
  89. delayedWorkItem = DispatchWorkItem { [weak self] in
  90. self?.presenter.getData(query: searchText)
  91. }
  92. DispatchQueue.global(qos: .userInitiated).asyncAfter(deadline: .now() + 0.2, execute: delayedWorkItem!)
  93. }
  94. }
  95. // MARK: - CollectionView delegate and data source
  96. extension GifListViewController: UICollectionViewDataSource, UICollectionViewDelegate {
  97. func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  98. return presenter.gifsList.count
  99. }
  100. func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  101. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: GifCollectionViewCell.reuseIdentifier, for: indexPath) as! GifCollectionViewCell
  102. cell.gif = presenter.gifsList[indexPath.row]
  103. return cell
  104. }
  105. }
  106. extension GifListViewController: UICollectionViewDataSourcePrefetching {
  107. func collectionView(_ collectionView: UICollectionView, prefetchItemsAt indexPaths: [IndexPath]) {
  108. guard presenter.isLoadingNewPage == false else { return }
  109. for indexPath in indexPaths {
  110. if indexPath.row > presenter.gifsList.count - Constants.GiphyPaginationLimit/2 {
  111. delayedWorkItem?.cancel()
  112. presenter.getData(query: self.searchBar.text ?? "")
  113. break
  114. }
  115. }
  116. }
  117. }