EmojiInputController.swift 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. //
  2. // EmojiInputController.swift
  3. // LoopKit
  4. //
  5. // Copyright © 2017 LoopKit Authors. All rights reserved.
  6. //
  7. import UIKit
  8. public class EmojiInputController: UIInputViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout, IdentifiableClass {
  9. @IBOutlet private weak var collectionView: UICollectionView!
  10. @IBOutlet private weak var sectionIndex: UIStackView!
  11. public weak var delegate: EmojiInputControllerDelegate?
  12. var emojis: EmojiDataSource!
  13. static func instance(withEmojis emojis: EmojiDataSource) -> EmojiInputController {
  14. let bundle = Bundle(for: self)
  15. let storyboard = UIStoryboard(name: className, bundle: bundle)
  16. let controller = storyboard.instantiateInitialViewController() as! EmojiInputController
  17. controller.emojis = emojis
  18. return controller
  19. }
  20. public override func viewDidLoad() {
  21. super.viewDidLoad()
  22. inputView = view as? UIInputView
  23. inputView?.allowsSelfSizing = true
  24. view.translatesAutoresizingMaskIntoConstraints = false
  25. if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
  26. layout.sectionHeadersPinToVisibleBounds = true
  27. layout.sectionFootersPinToVisibleBounds = true
  28. }
  29. setupSectionIndex()
  30. // Scroll to medium absorption
  31. DispatchQueue.main.async {
  32. self.collectionView.scrollToItem(at: IndexPath(row: 0, section: 1), at: .left, animated: false)
  33. }
  34. }
  35. private func setupSectionIndex() {
  36. sectionIndex.removeAllArrangedSubviews()
  37. for section in emojis.sections {
  38. let label = UILabel(frame: .zero)
  39. label.text = section.indexSymbol
  40. sectionIndex.addArrangedSubview(label)
  41. }
  42. }
  43. @IBOutlet weak var deleteButton: UIButton! {
  44. didSet {
  45. let image = UIImage(systemName: "delete.left", compatibleWith: traitCollection)
  46. deleteButton.setImage(image, for: .normal)
  47. }
  48. }
  49. // MARK: - Actions
  50. @IBAction func switchKeyboard(_ sender: Any) {
  51. delegate?.emojiInputControllerDidAdvanceToStandardInputMode(self)
  52. }
  53. @IBAction func deleteBackward(_ sender: Any) {
  54. inputView?.playInputClick​()
  55. textDocumentProxy.deleteBackward()
  56. }
  57. @IBAction func indexTouched(_ sender: UIGestureRecognizer) {
  58. let xLocation = max(0, sender.location(in: sectionIndex).x / sectionIndex.frame.width)
  59. let items = sectionIndex.arrangedSubviews.count
  60. let section = min(items - 1, Int(xLocation * CGFloat(items)))
  61. collectionView.scrollToItem(at: IndexPath(item: 0, section: section), at: .left, animated: false)
  62. }
  63. // MARK: - UICollectionViewDataSource
  64. public func numberOfSections(in collectionView: UICollectionView) -> Int {
  65. return emojis.sections.count
  66. }
  67. public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  68. return emojis.sections[section].items.count
  69. }
  70. public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
  71. let cell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: kind == UICollectionView.elementKindSectionHeader ? EmojiInputHeaderView.className : "Footer", for: indexPath)
  72. if let cell = cell as? EmojiInputHeaderView {
  73. cell.titleLabel.text = emojis.sections[indexPath.section].title.localizedUppercase
  74. }
  75. return cell
  76. }
  77. public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  78. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: EmojiInputCell.className, for: indexPath) as! EmojiInputCell
  79. cell.label.text = emojis.sections[indexPath.section].items[indexPath.row]
  80. return cell
  81. }
  82. // MARK: - UICollectionViewDelegateFlowLayout
  83. // MARK: - UICollectionViewDelegate
  84. public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  85. collectionView.deselectItem(at: indexPath, animated: true)
  86. inputView?.playInputClick​()
  87. textDocumentProxy.insertText(emojis.sections[indexPath.section].items[indexPath.row])
  88. delegate?.emojiInputControllerDidSelectItemInSection(indexPath.section)
  89. }
  90. }
  91. public protocol EmojiInputControllerDelegate: AnyObject {
  92. func emojiInputControllerDidAdvanceToStandardInputMode(_ controller: EmojiInputController)
  93. func emojiInputControllerDidSelectItemInSection(_ section: Int)
  94. }
  95. // MARK: - Default Implementations
  96. extension EmojiInputControllerDelegate {
  97. public func emojiInputControllerDidSelectItemInSection(_ section: Int) { }
  98. }
  99. extension UIInputView: UIInputViewAudioFeedback {
  100. public var enableInputClicksWhenVisible: Bool { return true }
  101. func playInputClick​() {
  102. let device = UIDevice.current
  103. device.playInputClick()
  104. }
  105. }
  106. private extension UIStackView {
  107. func removeAllArrangedSubviews() {
  108. for view in arrangedSubviews {
  109. view.removeFromSuperview()
  110. }
  111. }
  112. }