// // LNKeyboardManager.swift // Lanu // // Created by OneeChan on 2026/1/5. // import Foundation protocol LNKeyboardNotify { func onKeyboardWillShow(curInput: UIView?, keyboardHeight: CGFloat) func onKeyboardShow(curInput: UIView?, keyboardHeight: CGFloat) func onKeyboardDidShow(curInput: UIView?, keyboardHeight: CGFloat) func onKeyboardWillHide(curInput: UIView?) func onKeyboardHide(curInput: UIView?) func onKeyboardDidHide(curInput: UIView?) } extension LNKeyboardNotify { func onKeyboardWillShow(curInput: UIView?, keyboardHeight: CGFloat) {} func onKeyboardShow(curInput: UIView?, keyboardHeight: CGFloat) {} func onKeyboardDidShow(curInput: UIView?, keyboardHeight: CGFloat) {} func onKeyboardWillHide(curInput: UIView?) {} func onKeyboardHide(curInput: UIView?) {} func onKeyboardDidHide(curInput: UIView?) {} } class LNKeyboardManager { static let shared = LNKeyboardManager() private weak var curInput: UIView? var isEditing: Bool { curInput != nil } private init() { addFirstResponderObservers() addKeyboardObservers() } private func addKeyboardObservers() { NotificationCenter.default.addObserver( forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main ) { [weak self] notify in guard let self else { return } guard let userInfo = notify.userInfo, let keyboardFrame = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect else { return } let keyboardHeight = CGRectGetHeight(keyboardFrame) // 获取键盘动画时长(默认0.25秒) let animationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25 // 获取键盘动画曲线(默认UIView.AnimationCurve.easeInOut) let animationCurveRawValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int ?? UIView.AnimationCurve.easeInOut.rawValue let visibleView = (curInput as? UITextInput)?.visibleView ?? curInput LNEventDeliver.notifyEvent { ($0 as? LNKeyboardNotify)?.onKeyboardWillShow(curInput: visibleView, keyboardHeight: keyboardHeight) } // 应用动画参数调整UI(使视图动画与键盘动画同步) UIView.animate(withDuration: animationDuration, delay: 0, options: UIView .AnimationOptions(rawValue: UInt(animationCurveRawValue)), animations: { LNEventDeliver.notifyEvent { ($0 as? LNKeyboardNotify)?.onKeyboardShow(curInput: visibleView, keyboardHeight: keyboardHeight) } }) { _ in LNEventDeliver.notifyEvent { ($0 as? LNKeyboardNotify)?.onKeyboardDidShow(curInput: visibleView, keyboardHeight: keyboardHeight) } } } NotificationCenter.default.addObserver( forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main ) { [weak self] notify in guard let self else { return } guard let userInfo = notify.userInfo else { return } // 获取键盘动画时长(默认0.25秒) let animationDuration = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 0.25 // 获取键盘动画曲线(默认UIView.AnimationCurve.easeInOut) let animationCurveRawValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? Int ?? UIView.AnimationCurve.easeInOut.rawValue let visibleView = (curInput as? UITextInput)?.visibleView ?? curInput LNEventDeliver.notifyEvent { ($0 as? LNKeyboardNotify)?.onKeyboardWillHide(curInput: visibleView) } // 应用动画参数调整UI(使视图动画与键盘动画同步) UIView.animate(withDuration: animationDuration, delay: 0, options: UIView .AnimationOptions(rawValue: UInt(animationCurveRawValue)), animations: { LNEventDeliver.notifyEvent { ($0 as? LNKeyboardNotify)?.onKeyboardHide(curInput: visibleView) } }) { _ in LNEventDeliver.notifyEvent { ($0 as? LNKeyboardNotify)?.onKeyboardDidHide(curInput: visibleView) } } } } private func addFirstResponderObservers() { // UITextField NotificationCenter.default.addObserver( forName: UITextField.textDidBeginEditingNotification, object: nil, queue: .main) { [weak self] notify in guard let self else { return } curInput = notify.object as? UITextField } NotificationCenter.default.addObserver( forName: UITextField.textDidEndEditingNotification, object: nil, queue: .main) { [weak self] notify in guard let self else { return } curInput = nil } // UITextView NotificationCenter.default.addObserver( forName: UITextView.textDidBeginEditingNotification, object: nil, queue: .main) { [weak self] notify in guard let self else { return } curInput = notify.object as? UITextView } NotificationCenter.default.addObserver( forName: UITextView.textDidEndEditingNotification, object: nil, queue: .main) { [weak self] notify in guard let self else { return } curInput = nil } } } private struct LNWeakProxy { weak var obj: UIView? } private var LNInputViewVisibleViewKey: UInt8 = 0 extension UITextInput { weak var visibleView: UIView? { get { let proxy = objc_getAssociatedObject(self, &LNInputViewVisibleViewKey) as? LNWeakProxy return proxy?.obj } set { objc_setAssociatedObject(self, &LNInputViewVisibleViewKey, LNWeakProxy(obj: newValue), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } }