// // KeyboardAvoidingDialogView.swift // suixinkan // import SnapKit import UIKit /// 自定义居中弹窗的键盘避让基类,负责在键盘弹出时同步移动内容卡片。 class KeyboardAvoidingDialogView: UIView { /// 需要避让键盘的弹窗内容视图,通常是白色卡片容器。 var keyboardAvoidingContentView: UIView? /// 内容视图相对父视图的 centerY 约束,用于随键盘动画更新偏移。 var keyboardAvoidingCenterYConstraint: Constraint? /// 内容视图与屏幕顶部、键盘顶部之间保留的最小距离。 var keyboardAvoidingMinimumSpacing: CGFloat = AppSpacing.md private var keyboardObservers: [NSObjectProtocol] = [] override init(frame: CGRect) { super.init(frame: frame) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { unregisterKeyboardObservers() } override func didMoveToSuperview() { super.didMoveToSuperview() if superview == nil { unregisterKeyboardObservers() } else { registerKeyboardObservers() } } private func registerKeyboardObservers() { unregisterKeyboardObservers() let center = NotificationCenter.default keyboardObservers = [ center.addObserver( forName: UIResponder.keyboardWillChangeFrameNotification, object: nil, queue: .main ) { [weak self] notification in self?.handleKeyboardNotification(notification) }, center.addObserver( forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main ) { [weak self] notification in self?.handleKeyboardNotification(notification) } ] } private func unregisterKeyboardObservers() { keyboardObservers.forEach(NotificationCenter.default.removeObserver) keyboardObservers.removeAll() } private func handleKeyboardNotification(_ notification: Notification) { layoutIfNeeded() guard let userInfo = notification.userInfo else { return } let duration = (userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.25 let curveRaw = (userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? UInt(UIView.AnimationCurve.easeInOut.rawValue) let options = UIView.AnimationOptions(rawValue: curveRaw << 16) keyboardAvoidingCenterYConstraint?.update(offset: keyboardAvoidanceOffset(from: userInfo)) UIView.animate(withDuration: duration, delay: 0, options: options) { self.layoutIfNeeded() } } private func keyboardAvoidanceOffset(from userInfo: [AnyHashable: Any]) -> CGFloat { guard let contentView = keyboardAvoidingContentView, let keyboardFrameValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return 0 } let keyboardFrame = convert(keyboardFrameValue.cgRectValue, from: nil) guard keyboardFrame.minY < bounds.height else { return 0 } let contentHeight = contentView.bounds.height let baseMinY = bounds.midY - contentHeight / 2 let baseMaxY = bounds.midY + contentHeight / 2 let desiredBottom = keyboardFrame.minY - keyboardAvoidingMinimumSpacing let neededShift = max(0, baseMaxY - desiredBottom) let maxShift = max(0, baseMinY - keyboardAvoidingMinimumSpacing) return -min(neededShift, maxShift) } }