GifListViewController.swift 5.5 KB

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