// // EmojiPickerCollectionViewController.swift // Tusker // // Created by Shadowfacts on 10/12/20. // Copyright © 2020 Shadowfacts. All rights reserved. // import UIKit import Pachyderm private let reuseIdentifier = "EmojiCell" protocol EmojiPickerCollectionViewControllerDelegate: AnyObject { func selectedEmoji(_ emoji: Emoji) } // It would be nice to replace this with a LazyVGrid when the deployment target is bumped to 14.0 class EmojiPickerCollectionViewController: UICollectionViewController { weak var delegate: EmojiPickerCollectionViewControllerDelegate? private weak var mastodonController: MastodonController! private var dataSource: UICollectionViewDiffableDataSource! var searchQuery: String = "" { didSet { guard let emojis = mastodonController.customEmojis else { return } let snapshot = createFilteredSnapshot(emojis: emojis) DispatchQueue.main.async { self.dataSource.apply(snapshot) } } } init(mastodonController: MastodonController) { self.mastodonController = mastodonController let itemWidth = NSCollectionLayoutDimension.fractionalWidth(1.0 / 10) let itemSize = NSCollectionLayoutSize(widthDimension: itemWidth, heightDimension: itemWidth) let item = NSCollectionLayoutItem(layoutSize: itemSize) let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0), heightDimension: itemWidth) let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item]) group.interItemSpacing = .fixed(4) let section = NSCollectionLayoutSection(group: group) section.interGroupSpacing = 4 let layout = UICollectionViewCompositionalLayout(section: section) super.init(collectionViewLayout: layout) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewDidLoad() { super.viewDidLoad() additionalSafeAreaInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8) // use negative indicator insets to bring the indicators back to the edge of the containing view // using collectionView.contentInset doesn't work the compositional layout ignores the inset when calculating fractional widths collectionView.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: -8, bottom: 0, right: -8) collectionView.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 8, right: 0) collectionView.backgroundColor = .clear collectionView.register(EmojiCollectionViewCell.self, forCellWithReuseIdentifier: reuseIdentifier) dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView) { (collectionView, indexPath, item) -> UICollectionViewCell? in let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! EmojiCollectionViewCell cell.updateUI(emoji: item.emoji) return cell } mastodonController.getCustomEmojis { (emojis) in DispatchQueue.main.async { self.dataSource.apply(self.createFilteredSnapshot(emojis: emojis)) } } } private func createFilteredSnapshot(emojis: [Emoji]) -> NSDiffableDataSourceSnapshot { let items: [Item] if searchQuery.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { items = emojis.map { Item(emoji: $0) } } else { items = emojis .map { ($0, FuzzyMatcher.match(pattern: searchQuery, str: $0.shortcode)) } .filter(\.1.matched) .sorted { $0.1.score > $1.1.score } .map { Item(emoji: $0.0) } } var snapshot = NSDiffableDataSourceSnapshot() snapshot.appendSections([.emojis]) snapshot.appendItems(items) return snapshot } // MARK: UICollectionViewDelegate override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { guard let item = dataSource.itemIdentifier(for: indexPath) else { return } delegate?.selectedEmoji(item.emoji) } } extension EmojiPickerCollectionViewController { enum Section { case emojis } struct Item: Hashable, Equatable { let emoji: Emoji func hash(into hasher: inout Hasher) { hasher.combine(emoji.shortcode) } static func ==(lhs: Item, rhs: Item) -> Bool { lhs.emoji.shortcode == rhs.emoji.shortcode } } }