EmojiInputController.swift 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  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. }
  31. private func setupSectionIndex() {
  32. sectionIndex.removeAllArrangedSubviews()
  33. for section in emojis.sections {
  34. let label = UILabel(frame: .zero)
  35. label.text = section.indexSymbol
  36. sectionIndex.addArrangedSubview(label)
  37. }
  38. }
  39. @IBOutlet weak var deleteButton: UIButton! {
  40. didSet {
  41. if #available(iOSApplicationExtension 13.0, *) {
  42. let image = UIImage(systemName: "delete.left", compatibleWith: traitCollection)
  43. deleteButton.setImage(image, for: .normal)
  44. }
  45. }
  46. }
  47. // MARK: - Actions
  48. @IBAction func switchKeyboard(_ sender: Any) {
  49. delegate?.emojiInputControllerDidAdvanceToStandardInputMode(self)
  50. }
  51. @IBAction func deleteBackward(_ sender: Any) {
  52. inputView?.playInputClick​()
  53. textDocumentProxy.deleteBackward()
  54. }
  55. @IBAction func indexTouched(_ sender: UIGestureRecognizer) {
  56. let xLocation = max(0, sender.location(in: sectionIndex).x / sectionIndex.frame.width)
  57. let items = sectionIndex.arrangedSubviews.count
  58. let section = min(items - 1, Int(xLocation * CGFloat(items)))
  59. collectionView.scrollToItem(at: IndexPath(item: 0, section: section), at: .left, animated: false)
  60. }
  61. // MARK: - UICollectionViewDataSource
  62. public func numberOfSections(in collectionView: UICollectionView) -> Int {
  63. return emojis.sections.count
  64. }
  65. public func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
  66. return emojis.sections[section].items.count
  67. }
  68. public func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
  69. let cell = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: kind == UICollectionView.elementKindSectionHeader ? EmojiInputHeaderView.className : "Footer", for: indexPath)
  70. if let cell = cell as? EmojiInputHeaderView {
  71. cell.titleLabel.text = emojis.sections[indexPath.section].title.localizedUppercase
  72. }
  73. return cell
  74. }
  75. public func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
  76. let cell = collectionView.dequeueReusableCell(withReuseIdentifier: EmojiInputCell.className, for: indexPath) as! EmojiInputCell
  77. cell.label.text = emojis.sections[indexPath.section].items[indexPath.row]
  78. return cell
  79. }
  80. // MARK: - UICollectionViewDelegateFlowLayout
  81. // MARK: - UICollectionViewDelegate
  82. public func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
  83. collectionView.deselectItem(at: indexPath, animated: true)
  84. inputView?.playInputClick​()
  85. textDocumentProxy.insertText(emojis.sections[indexPath.section].items[indexPath.row])
  86. delegate?.emojiInputControllerDidSelectItemInSection(indexPath.section)
  87. }
  88. }
  89. public protocol EmojiInputControllerDelegate: AnyObject {
  90. func emojiInputControllerDidAdvanceToStandardInputMode(_ controller: EmojiInputController)
  91. func emojiInputControllerDidSelectItemInSection(_ section: Int)
  92. }
  93. // MARK: - Default Implementations
  94. extension EmojiInputControllerDelegate {
  95. public func emojiInputControllerDidSelectItemInSection(_ section: Int) { }
  96. }
  97. extension UIInputView: UIInputViewAudioFeedback {
  98. public var enableInputClicksWhenVisible: Bool { return true }
  99. func playInputClick​() {
  100. let device = UIDevice.current
  101. device.playInputClick()
  102. }
  103. }
  104. private extension UIStackView {
  105. func removeAllArrangedSubviews() {
  106. for view in arrangedSubviews {
  107. view.removeFromSuperview()
  108. }
  109. }
  110. }