You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动