LNKeyboardManager.swift 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. //
  2. // LNKeyboardManager.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2026/1/5.
  6. //
  7. import Foundation
  8. protocol LNKeyboardNotify {
  9. func onKeyboardWillShow(curInput: UIView?, keyboardHeight: CGFloat)
  10. func onKeyboardShow(curInput: UIView?, keyboardHeight: CGFloat)
  11. func onKeyboardDidShow(curInput: UIView?, keyboardHeight: CGFloat)
  12. func onKeyboardWillHide(curInput: UIView?)
  13. func onKeyboardHide(curInput: UIView?)
  14. func onKeyboardDidHide(curInput: UIView?)
  15. }
  16. extension LNKeyboardNotify {
  17. func onKeyboardWillShow(curInput: UIView?, keyboardHeight: CGFloat) {}
  18. func onKeyboardShow(curInput: UIView?, keyboardHeight: CGFloat) {}
  19. func onKeyboardDidShow(curInput: UIView?, keyboardHeight: CGFloat) {}
  20. func onKeyboardWillHide(curInput: UIView?) {}
  21. func onKeyboardHide(curInput: UIView?) {}
  22. func onKeyboardDidHide(curInput: UIView?) {}
  23. }
  24. class LNKeyboardManager {
  25. static let shared = LNKeyboardManager()
  26. private weak var curInput: UIView?
  27. var isEditing: Bool {
  28. curInput != nil
  29. }
  30. private init() {
  31. addFirstResponderObservers()
  32. addKeyboardObservers()
  33. }
  34. private func addKeyboardObservers() {
  35. NotificationCenter.default.addObserver(
  36. forName: UIResponder.keyboardWillShowNotification,
  37. object: nil, queue: .main
  38. ) { [weak self] notify in
  39. guard let self else { return }
  40. guard let userInfo = notify.userInfo,
  41. let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
  42. else { return }
  43. let keyboardHeight = CGRectGetHeight(keyboardFrame)
  44. // 获取键盘动画时长(默认0.25秒)
  45. let animationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25
  46. // 获取键盘动画曲线(默认UIView.AnimationCurve.easeInOut)
  47. let animationCurveRawValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int ?? UIView.AnimationCurve.easeInOut.rawValue
  48. let visibleView = (curInput as? UITextInput)?.visibleView ?? curInput
  49. LNEventDeliver.notifyEvent {
  50. ($0 as? LNKeyboardNotify)?.onKeyboardWillShow(curInput: visibleView, keyboardHeight: keyboardHeight)
  51. }
  52. // 应用动画参数调整UI(使视图动画与键盘动画同步)
  53. UIView.animate(withDuration: animationDuration,
  54. delay: 0,
  55. options: UIView
  56. .AnimationOptions(rawValue: UInt(animationCurveRawValue)),
  57. animations: {
  58. LNEventDeliver.notifyEvent {
  59. ($0 as? LNKeyboardNotify)?.onKeyboardShow(curInput: visibleView, keyboardHeight: keyboardHeight)
  60. }
  61. }) { _ in
  62. LNEventDeliver.notifyEvent {
  63. ($0 as? LNKeyboardNotify)?.onKeyboardDidShow(curInput: visibleView, keyboardHeight: keyboardHeight)
  64. }
  65. }
  66. }
  67. NotificationCenter.default.addObserver(
  68. forName: UIResponder.keyboardWillHideNotification,
  69. object: nil, queue: .main
  70. ) { [weak self] notify in
  71. guard let self else { return }
  72. guard let userInfo = notify.userInfo else { return }
  73. // 获取键盘动画时长(默认0.25秒)
  74. let animationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25
  75. // 获取键盘动画曲线(默认UIView.AnimationCurve.easeInOut)
  76. let animationCurveRawValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int ?? UIView.AnimationCurve.easeInOut.rawValue
  77. let visibleView = (curInput as? UITextInput)?.visibleView ?? curInput
  78. LNEventDeliver.notifyEvent {
  79. ($0 as? LNKeyboardNotify)?.onKeyboardWillHide(curInput: visibleView)
  80. }
  81. // 应用动画参数调整UI(使视图动画与键盘动画同步)
  82. UIView.animate(withDuration: animationDuration,
  83. delay: 0,
  84. options: UIView
  85. .AnimationOptions(rawValue: UInt(animationCurveRawValue)),
  86. animations: {
  87. LNEventDeliver.notifyEvent {
  88. ($0 as? LNKeyboardNotify)?.onKeyboardHide(curInput: visibleView)
  89. }
  90. }) { _ in
  91. LNEventDeliver.notifyEvent {
  92. ($0 as? LNKeyboardNotify)?.onKeyboardDidHide(curInput: visibleView)
  93. }
  94. }
  95. }
  96. }
  97. private func addFirstResponderObservers() {
  98. // UITextField
  99. NotificationCenter.default.addObserver(
  100. forName: UITextField.textDidBeginEditingNotification,
  101. object: nil, queue: .main)
  102. { [weak self] notify in
  103. guard let self else { return }
  104. curInput = notify.object as? UITextField
  105. }
  106. NotificationCenter.default.addObserver(
  107. forName: UITextField.textDidEndEditingNotification,
  108. object: nil, queue: .main)
  109. { [weak self] notify in
  110. guard let self else { return }
  111. curInput = nil
  112. }
  113. // UITextView
  114. NotificationCenter.default.addObserver(
  115. forName: UITextView.textDidBeginEditingNotification,
  116. object: nil, queue: .main)
  117. { [weak self] notify in
  118. guard let self else { return }
  119. curInput = notify.object as? UITextView
  120. }
  121. NotificationCenter.default.addObserver(
  122. forName: UITextView.textDidEndEditingNotification,
  123. object: nil, queue: .main)
  124. { [weak self] notify in
  125. guard let self else { return }
  126. curInput = nil
  127. }
  128. }
  129. }
  130. private struct LNWeakProxy {
  131. weak var obj: UIView?
  132. }
  133. private var LNInputViewVisibleViewKey: UInt8 = 0
  134. extension UITextInput {
  135. weak var visibleView: UIView? {
  136. get {
  137. let proxy = objc_getAssociatedObject(self, &LNInputViewVisibleViewKey) as? LNWeakProxy
  138. return proxy?.obj
  139. }
  140. set {
  141. objc_setAssociatedObject(self, &LNInputViewVisibleViewKey, LNWeakProxy(obj: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
  142. }
  143. }
  144. }