SAIInputBarDisplayable.swift 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. //
  2. // SAIInputBarDisplayable.swift
  3. // SAIInputBar
  4. //
  5. // Created by SAGESSE on 8/26/16.
  6. // Copyright © 2016-2017 SAGESSE. All rights reserved.
  7. //
  8. import UIKit
  9. ///
  10. /// 显示协议
  11. ///
  12. /// NOTE: 目前不支持`UICollectionViewController`和`UITableViewController`
  13. ///
  14. public protocol SAIInputBarDisplayable: NSObjectProtocol {
  15. var scrollView: SAIInputBarScrollViewType { get }
  16. }
  17. public protocol SAIInputBarScrollViewType: NSObjectProtocol {
  18. var frame: CGRect { get }
  19. var bounds: CGRect { get }
  20. var contentSize: CGSize { set get }
  21. var contentOffset: CGPoint { set get }
  22. var contentInset: UIEdgeInsets { set get }
  23. var scrollIndicatorInsets: UIEdgeInsets { set get }
  24. func layoutSubviews()
  25. }
  26. extension UIScrollView: SAIInputBarScrollViewType {
  27. }
  28. // MARK:
  29. internal extension SAIInputBarDisplayable {
  30. func ib_inputBar(_ inputBar: SAIInputBar, initWithFrame frame: CGRect) {
  31. UIView.performWithoutAnimation {
  32. self.ib_extContentSize = CGSize(width: frame.width, height: frame.height)
  33. self.ib_extContentOffset = CGPoint.zero
  34. }
  35. }
  36. func ib_inputBar(_ inputBar: SAIInputBar, showWithFrame frame: CGRect) {
  37. guard let window = inputBar.window else {
  38. return
  39. }
  40. // If the previous scenario inputViewSet is Empty, forced to update
  41. if window.value(forKeyPath: "rootViewController.ib_oldInputViewSet.isEmpty") as? Bool ?? true {
  42. UIView.performWithoutAnimation {
  43. ib_inputBar(inputBar, didChangeFrame: frame)
  44. }
  45. } else {
  46. ib_inputBar(inputBar, didChangeFrame: frame)
  47. }
  48. }
  49. func ib_inputBar(_ inputBar: SAIInputBar, hideWithFrame frame: CGRect) {
  50. guard let window = inputBar.window else {
  51. return
  52. }
  53. // If the next scene inputViewSet is Empty, ignore the hidden events
  54. if window.value(forKeyPath: "rootViewController.ib_newInputViewSet.isEmpty") as? Bool ?? true {
  55. return
  56. }
  57. ib_inputBar(inputBar, didChangeFrame: frame)
  58. }
  59. func ib_inputBar(_ inputBar: SAIInputBar, didChangeOffset offset: CGPoint) {
  60. guard scrollView.contentOffset.y >= -scrollView.contentInset.top else {
  61. return
  62. }
  63. ib_extContentOffset = CGPoint(x: -offset.x, y: -offset.y)
  64. }
  65. func ib_inputBar(_ inputBar: SAIInputBar, didChangeFrame frame: CGRect) {
  66. let ty = (frame.height - self.ib_extContentSize.height) + (0 - self.ib_extContentOffset.y)
  67. guard ty != 0 else {
  68. return // no change
  69. }
  70. UIView.animate(withDuration: _SAInputDefaultAnimateDuration) { [scrollView] in
  71. UIView.setAnimationCurve(_SAInputDefaultAnimateCurve)
  72. let pt = scrollView.contentOffset
  73. let edg = scrollView.contentInset
  74. self.ib_extContentSize = CGSize(width: frame.width, height: frame.height)
  75. self.ib_extContentOffset = CGPoint.zero
  76. let ny = pt.y + ty
  77. let minY = -edg.top
  78. let maxY = scrollView.contentSize.height - scrollView.frame.height + (edg.bottom + ty)
  79. scrollView.contentOffset = CGPoint(x: pt.x, y: max(max(ny, maxY), minY))
  80. scrollView.layoutSubviews()
  81. }
  82. }
  83. var ib_extContentOffset: CGPoint {
  84. set {
  85. let oldValue = ib_extContentOffset
  86. guard oldValue != newValue else {
  87. return
  88. }
  89. var edg1 = scrollView.contentInset
  90. var edg2 = scrollView.scrollIndicatorInsets
  91. edg1.bottom += newValue.y - oldValue.y
  92. edg2.bottom += newValue.y - oldValue.y
  93. scrollView.contentInset = edg1
  94. scrollView.scrollIndicatorInsets = edg2
  95. return objc_setAssociatedObject(self, &_SAInputBarDisplayableExtContentOffset, NSValue(cgPoint: newValue), .OBJC_ASSOCIATION_RETAIN)
  96. }
  97. get {
  98. return (objc_getAssociatedObject(self, &_SAInputBarDisplayableExtContentOffset) as? NSValue)?.cgPointValue ?? CGPoint.zero
  99. }
  100. }
  101. var ib_extContentSize: CGSize {
  102. set {
  103. let oldValue = ib_extContentSize
  104. guard oldValue != newValue else {
  105. return
  106. }
  107. var edg1 = scrollView.contentInset
  108. var edg2 = scrollView.scrollIndicatorInsets
  109. edg1.bottom += newValue.height - oldValue.height
  110. edg2.bottom += newValue.height - oldValue.height
  111. scrollView.contentInset = edg1
  112. scrollView.scrollIndicatorInsets = edg2
  113. return objc_setAssociatedObject(self, &_SAInputBarDisplayableExtContentSize, NSValue(cgSize: newValue), .OBJC_ASSOCIATION_RETAIN)
  114. }
  115. get {
  116. return (objc_getAssociatedObject(self, &_SAInputBarDisplayableExtContentSize) as? NSValue)?.cgSizeValue ?? CGSize.zero
  117. }
  118. }
  119. }
  120. private extension UIViewController {
  121. /// 为了解决切换页面时的输入焦点问题
  122. @objc func ib_inputWindowController_setInputViewSet(_ arg1: AnyObject) {
  123. ib_newInputViewSet = arg1
  124. ib_inputWindowController_setInputViewSet(arg1)
  125. ib_oldInputViewSet = arg1
  126. }
  127. @objc var ib_oldInputViewSet: AnyObject? {
  128. set { return objc_setAssociatedObject(self, &_SAInputUIInputWindowControllerOldInputViewSet, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  129. get { return objc_getAssociatedObject(self, &_SAInputUIInputWindowControllerOldInputViewSet) as AnyObject? }
  130. }
  131. @objc var ib_newInputViewSet: AnyObject? {
  132. set { return objc_setAssociatedObject(self, &_SAInputUIInputWindowControllerNewInputViewSet, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
  133. get { return objc_getAssociatedObject(self, &_SAInputUIInputWindowControllerNewInputViewSet) as AnyObject? }
  134. }
  135. }
  136. // MARK: method swizzling
  137. internal func SAIInputBarDisplayableLoad() {
  138. _SAInputExchangeSelector(NSClassFromString("UIInputWindowController"), "setInputViewSet:", "ib_inputWindowController_setInputViewSet:")
  139. }
  140. private var _SAInputBarDisplayableExtContentSize = "_SAInputBarDisplayableExtContentSize"
  141. private var _SAInputBarDisplayableExtContentOffset = "_SAInputBarDisplayableExtContentOffset"
  142. private var _SAInputUIInputWindowControllerNewInputViewSet = "_SAInputUIInputWindowControllerNewInputViewSet"
  143. private var _SAInputUIInputWindowControllerOldInputViewSet = "_SAInputUIInputWindowControllerOldInputViewSet"