iOS中UITextField开启isSecureTextEntry时的键盘高度管理
解决SecureTextEntry输入框切换时视图跳动的问题
我太懂这个真机专属的坑了!切换普通输入框和带isSecureTextEntry = true的密码框时,QuickTypeBar的显示/隐藏会让键盘高度瞬间波动,触发多次键盘通知,直接导致视图像“抽搐”一样上下跳——模拟器看不到QuickTypeBar,难怪你在模拟器里复现不了。
问题根源
当你在两种输入框之间切换时,系统会先快速调整键盘高度(比如隐藏/显示QuickTypeBar),再稳定到最终的键盘状态。你的代码每次收到键盘通知就立即更新约束,自然会跟着这两次高度变化反复调整视图位置,出现先下移再复位的诡异效果。
解决方案
这里有几个实用的修复方案,你可以根据需求选:
1. 忽略小幅度的键盘高度变化
QuickTypeBar的高度大概在44pt左右,我们可以判断键盘高度的变化差,小于这个数值就跳过更新:
private func updateConstraints(_ notification: Notification) { guard let toFrame = notification.userInfo?[UIApplication.keyboardFrameEndUserInfoKey] as? CGRect, let fromFrame = notification.userInfo?[UIApplication.keyboardFrameBeginUserInfoKey] as? CGRect else { return } let heightDiff = abs(toFrame.height - fromFrame.height) // 过滤QuickTypeBar导致的小幅度波动 if heightDiff < 50 { return } let constant = -toFrame.size.height - offset boardViewBottomAnchorConstraint.constant = constant // 配合键盘动画让过渡更平滑 UIView.animate(withDuration: 0.3, delay: 0, options: .curveEaseInOut) { self.view.layoutIfNeeded() } }
2. 延迟更新约束,跳过瞬间波动
用延迟执行+取消重复任务的方式,只处理最终稳定的键盘状态:
private func updateConstraints(_ notification: Notification) { guard let _ = notification.userInfo?[UIApplication.keyboardFrameEndUserInfoKey] as? CGRect else { return } // 取消之前的待执行任务,避免重复更新 NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(updateFinalPosition(_:)), object: notification) // 延迟0.1秒执行,跳过中间的瞬间高度变化 perform(#selector(updateFinalPosition(_:)), with: notification, afterDelay: 0.1) } @objc private func updateFinalPosition(_ notification: Notification) { guard let toFrame = notification.userInfo?[UIApplication.keyboardFrameEndUserInfoKey] as? CGRect else { return } let constant = -toFrame.size.height - offset boardViewBottomAnchorConstraint.constant = constant UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() } }
3. 简化通知监听逻辑
keyboardWillChangeFrame已经包含了键盘显示、隐藏、变化的所有场景,你可以去掉keyboardWillShow的监听,避免重复处理:
override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .darkGray // 只保留这两个通知就够了 NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(_:)), name: UIResponder.keyboardWillHideNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(_:)), name: UIResponder.keyboardWillChangeFrameNotification, object: nil) // ... 其他初始化代码不变 }
额外优化建议
你可以放弃直接用键盘高度移动视图的逻辑,改为判断输入框是否被遮挡,这样更精准:
func textFieldDidBeginEditing(_ textField: UITextField) { // 获取输入框在视图坐标系中的底部位置 let textFieldBottom = textField.convert(textField.bounds, to: view).maxY // 获取键盘弹出后视图的可见底部位置 let keyboardHeight = (NotificationCenter.default.postedNotifications.first(where: { $0.name == UIResponder.keyboardWillChangeFrameNotification })?.userInfo?[UIApplication.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0 let visibleBottom = view.bounds.height - keyboardHeight - offset // 如果输入框被遮挡,计算需要上移的距离 if textFieldBottom > visibleBottom { let neededOffset = textFieldBottom - visibleBottom boardViewBottomAnchorConstraint.constant = boardViewBottomAnchorConstraintConstant - neededOffset } else { boardViewBottomAnchorConstraint.constant = boardViewBottomAnchorConstraintConstant } UIView.animate(withDuration: 0.3) { self.view.layoutIfNeeded() } }
这样不管键盘怎么变化,都能保证当前输入框始终在可见区域,彻底避免跳动问题。
内容的提问来源于stack exchange,提问作者user9383160




