From b2c00419626db7915d17bc9d4d44b3dd5e74e7ba Mon Sep 17 00:00:00 2001
From: Bruno Muniz Azevedo Filho <bruno@elixxir.io>
Date: Fri, 22 Jul 2022 19:20:38 -0300
Subject: [PATCH] Using compositional layout w/ collectionview on search left
 controller

---
 .../Controllers/SearchLeftController.swift    | 48 ++++++++++-------
 .../Utils/SearchDiffableDataSource.swift      | 23 --------
 .../ViewModels/SearchLeftViewModel.swift      | 10 ++++
 .../Views/SearchLeftSectionHeader.swift       | 32 +++++++++++
 .../SearchFeature/Views/SearchLeftView.swift  | 53 +++++++++++++++++--
 Sources/Shared/AutoGenerated/Strings.swift    |  2 +
 .../Resources/en.lproj/Localizable.strings    |  2 +
 Sources/Shared/Views/AvatarCell.swift         |  6 +--
 8 files changed, 128 insertions(+), 48 deletions(-)
 delete mode 100644 Sources/SearchFeature/Utils/SearchDiffableDataSource.swift
 create mode 100644 Sources/SearchFeature/Views/SearchLeftSectionHeader.swift

diff --git a/Sources/SearchFeature/Controllers/SearchLeftController.swift b/Sources/SearchFeature/Controllers/SearchLeftController.swift
index 4b71340e..e7c39382 100644
--- a/Sources/SearchFeature/Controllers/SearchLeftController.swift
+++ b/Sources/SearchFeature/Controllers/SearchLeftController.swift
@@ -19,10 +19,10 @@ final class SearchLeftController: UIViewController {
 
     lazy private var screenView = SearchLeftView()
 
-    private var dataSource: SearchDiffableDataSource!
     private(set) var viewModel = SearchLeftViewModel()
     private var drawerCancellables = Set<AnyCancellable>()
     private let adrpURLString = "https://links.xx.network/adrp"
+    private var dataSource: UICollectionViewDiffableDataSource<SearchSection, SearchItem>!
 
     private var cancellables = Set<AnyCancellable>()
     private var hudCancellables = Set<AnyCancellable>()
@@ -33,7 +33,7 @@ final class SearchLeftController: UIViewController {
 
     override func viewDidLoad() {
         super.viewDidLoad()
-        setupTableView()
+        setupCollectionView()
         setupBindings()
     }
 
@@ -41,18 +41,17 @@ final class SearchLeftController: UIViewController {
         screenView.inputField.endEditing(true)
     }
 
-    private func setupTableView() {
-        screenView.tableView.separatorStyle = .none
-        screenView.tableView.tableFooterView = UIView()
-        screenView.tableView.register(AvatarCell.self)
-        screenView.tableView.dataSource = dataSource
-        screenView.tableView.delegate = self
+    private func setupCollectionView() {
+        screenView.collectionView.delegate = self
+        screenView.collectionView.dataSource = dataSource
+        screenView.collectionView.register(AvatarCell.self)
+        screenView.collectionView.registerSectionHeader(SearchLeftSectionHeader.self)
 
-        dataSource = SearchDiffableDataSource(
-            tableView: screenView.tableView
-        ) { tableView, indexPath, item in
+        dataSource = UICollectionViewDiffableDataSource<SearchSection, SearchItem>(
+            collectionView: screenView.collectionView
+        ) { collectionView, indexPath, item in
             let contact: Contact
-            let cell = tableView.dequeueReusableCell(forIndexPath: indexPath, ofType: AvatarCell.self)
+            let cell: AvatarCell = collectionView.dequeueReusableCell(forIndexPath: indexPath)
 
             let h1Text: String
             var h2Text: String?
@@ -111,6 +110,23 @@ final class SearchLeftController: UIViewController {
 
             return cell
         }
+
+        dataSource.supplementaryViewProvider = { [weak self] collectionView, kind, indexPath in
+            guard let self = self else { return nil }
+
+            let sectionIdentifier = self.dataSource.snapshot().sectionIdentifiers[indexPath.section]
+            let sectionView = collectionView.dequeueReusableSupplementaryView(
+                ofKind: kind,
+                withReuseIdentifier: String(describing: SearchLeftSectionHeader.self),
+                for: indexPath
+            )
+
+            if let sectionView = sectionView as? SearchLeftSectionHeader, case .connections = sectionIdentifier {
+                sectionView.set(title: Localized.Ud.localResults)
+            }
+
+            return sectionView
+        }
     }
 
     private func setupBindings() {
@@ -422,8 +438,8 @@ final class SearchLeftController: UIViewController {
 
 }
 
-extension SearchLeftController: UITableViewDelegate {
-    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+extension SearchLeftController: UICollectionViewDelegate {
+    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
         if let item = dataSource.itemIdentifier(for: indexPath) {
             switch item {
             case .stranger(let contact):
@@ -434,10 +450,6 @@ extension SearchLeftController: UITableViewDelegate {
         }
     }
 
-    func tableView(_ tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
-        (view as! UITableViewHeaderFooterView).textLabel?.textColor = Asset.neutralWeak.color
-    }
-
     private func didTap(contact: Contact) {
         guard contact.authStatus == .stranger else {
             coordinator.toContact(contact, from: self)
diff --git a/Sources/SearchFeature/Utils/SearchDiffableDataSource.swift b/Sources/SearchFeature/Utils/SearchDiffableDataSource.swift
deleted file mode 100644
index 10d7bccc..00000000
--- a/Sources/SearchFeature/Utils/SearchDiffableDataSource.swift
+++ /dev/null
@@ -1,23 +0,0 @@
-import UIKit
-import XXModels
-
-enum SearchSection {
-    case stranger
-    case connections
-}
-
-enum SearchItem: Equatable, Hashable {
-    case stranger(Contact)
-    case connection(Contact)
-}
-
-class SearchDiffableDataSource: UITableViewDiffableDataSource<SearchSection, SearchItem> {
-    override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
-        switch snapshot().sectionIdentifiers[section] {
-        case .stranger:
-            return ""
-        case .connections:
-            return "LOCAL RESULTS"
-        }
-    }
-}
diff --git a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
index 6c0a8390..d7749c15 100644
--- a/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
+++ b/Sources/SearchFeature/ViewModels/SearchLeftViewModel.swift
@@ -7,6 +7,16 @@ import Countries
 import Integration
 import DependencyInjection
 
+enum SearchSection {
+    case stranger
+    case connections
+}
+
+enum SearchItem: Equatable, Hashable {
+    case stranger(Contact)
+    case connection(Contact)
+}
+
 typealias SearchSnapshot = NSDiffableDataSourceSnapshot<SearchSection, SearchItem>
 
 struct SearchLeftViewState {
diff --git a/Sources/SearchFeature/Views/SearchLeftSectionHeader.swift b/Sources/SearchFeature/Views/SearchLeftSectionHeader.swift
new file mode 100644
index 00000000..a8d3a829
--- /dev/null
+++ b/Sources/SearchFeature/Views/SearchLeftSectionHeader.swift
@@ -0,0 +1,32 @@
+import UIKit
+import Shared
+
+final class SearchLeftSectionHeader: UICollectionReusableView {
+    private let titleLabel = UILabel()
+
+    override func prepareForReuse() {
+        super.prepareForReuse()
+        titleLabel.text = nil
+    }
+
+    override init(frame: CGRect) {
+        super.init(frame: frame)
+
+        titleLabel.textColor = Asset.neutralWeak.color
+        titleLabel.font = Fonts.Mulish.bold.font(size: 12.0)
+
+        addSubview(titleLabel)
+
+        titleLabel.snp.makeConstraints {
+            $0.top.equalToSuperview().offset(5)
+            $0.left.equalToSuperview().offset(24)
+            $0.bottom.equalToSuperview()
+        }
+    }
+
+    required init?(coder: NSCoder) { nil }
+
+    func set(title: String) {
+        titleLabel.text = title
+    }
+}
diff --git a/Sources/SearchFeature/Views/SearchLeftView.swift b/Sources/SearchFeature/Views/SearchLeftView.swift
index bbdda3f9..b4d59f3b 100644
--- a/Sources/SearchFeature/Views/SearchLeftView.swift
+++ b/Sources/SearchFeature/Views/SearchLeftView.swift
@@ -2,26 +2,71 @@ import UIKit
 import Shared
 
 final class SearchLeftView: UIView {
-    let tableView = UITableView()
     let inputStackView = UIStackView()
     let inputField = SearchComponent()
     let emptyView = SearchLeftEmptyView()
     let countryButton = SearchCountryComponent()
     let placeholderView = SearchLeftPlaceholderView()
 
+    lazy var collectionView: UICollectionView = {
+        let itemSize = NSCollectionLayoutSize(
+            widthDimension: .fractionalWidth(1),
+            heightDimension: .estimated(1)
+        )
+
+        let item = NSCollectionLayoutItem(layoutSize: itemSize)
+
+        let groupSize = NSCollectionLayoutSize(
+            widthDimension: .fractionalWidth(1),
+            heightDimension: .estimated(1)
+        )
+
+        let group = NSCollectionLayoutGroup.horizontal(
+            layoutSize: groupSize,
+            subitems: [item]
+        )
+
+        let section = NSCollectionLayoutSection(group: group)
+        section.interGroupSpacing = 5
+        section.contentInsets = NSDirectionalEdgeInsets(
+            top: 0,
+            leading: 10,
+            bottom: 0,
+            trailing: 10
+        )
+
+        let headerFooterSize = NSCollectionLayoutSize(
+            widthDimension: .fractionalWidth(1.0),
+            heightDimension: .estimated(44)
+        )
+
+        let sectionHeader = NSCollectionLayoutBoundarySupplementaryItem(
+            layoutSize: headerFooterSize,
+            elementKind: UICollectionView.elementKindSectionHeader,
+            alignment: .top
+        )
+
+        section.boundarySupplementaryItems = [sectionHeader]
+
+        let layout = UICollectionViewCompositionalLayout(section: section)
+        let collectionView = UICollectionView(frame: bounds, collectionViewLayout: layout)
+        collectionView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
+        collectionView.backgroundColor = Asset.neutralWhite.color
+        return collectionView
+    }()
+
     init() {
         super.init(frame: .zero)
 
         emptyView.isHidden = true
         backgroundColor = Asset.neutralWhite.color
-        tableView.backgroundColor = Asset.neutralWhite.color
 
         inputStackView.spacing = 5
         inputStackView.addArrangedSubview(countryButton)
         inputStackView.addArrangedSubview(inputField)
 
         addSubview(inputStackView)
-        addSubview(tableView)
+        addSubview(collectionView)
         addSubview(emptyView)
         addSubview(placeholderView)
 
@@ -47,7 +92,7 @@ final class SearchLeftView: UIView {
             $0.right.equalToSuperview().offset(-20)
         }
 
-        tableView.snp.makeConstraints {
+        collectionView.snp.makeConstraints {
             $0.top.equalTo(inputField.snp.bottom).offset(20)
             $0.left.equalToSuperview()
             $0.right.equalToSuperview()
diff --git a/Sources/Shared/AutoGenerated/Strings.swift b/Sources/Shared/AutoGenerated/Strings.swift
index 315babf8..ed3fb2e5 100644
--- a/Sources/Shared/AutoGenerated/Strings.swift
+++ b/Sources/Shared/AutoGenerated/Strings.swift
@@ -1211,6 +1211,8 @@ public enum Localized {
   }
 
   public enum Ud {
+    /// LOCAL RESULTS
+    public static let localResults = Localized.tr("Localizable", "ud.localResults")
     /// There are no users with that %@.
     public static func noneFound(_ p1: Any) -> String {
       return Localized.tr("Localizable", "ud.noneFound", String(describing: p1))
diff --git a/Sources/Shared/Resources/en.lproj/Localizable.strings b/Sources/Shared/Resources/en.lproj/Localizable.strings
index 2b726391..b0dfdda4 100644
--- a/Sources/Shared/Resources/en.lproj/Localizable.strings
+++ b/Sources/Shared/Resources/en.lproj/Localizable.strings
@@ -988,6 +988,8 @@
 = "Edit your new contact’s nickname so you know who they are.";
 "ud.nicknameDrawer.save"
 = "Save";
+"ud.localResults"
+= "LOCAL RESULTS";
 
 "ud.search.input"
 = "Search by %@";
diff --git a/Sources/Shared/Views/AvatarCell.swift b/Sources/Shared/Views/AvatarCell.swift
index aeae12dd..cc39fc57 100644
--- a/Sources/Shared/Views/AvatarCell.swift
+++ b/Sources/Shared/Views/AvatarCell.swift
@@ -1,7 +1,7 @@
 import UIKit
 import Combine
 
-public final class AvatarCell: UITableViewCell {
+public final class AvatarCell: UICollectionViewCell {
     public struct Action {
         public var title: String
         public var color: UIColor
@@ -32,8 +32,8 @@ public final class AvatarCell: UITableViewCell {
     private let actionButton = AvatarCellButton()
     private var cancellables = Set<AnyCancellable>()
 
-    public override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
-        super.init(style: style, reuseIdentifier: reuseIdentifier)
+    public override init(frame: CGRect) {
+        super.init(frame: frame)
 
         backgroundColor = Asset.neutralWhite.color
         separatorView.backgroundColor = Asset.neutralLine.color
-- 
GitLab