iOS自动OTP验证问题:OTP未显示在键盘工具栏
解决iOS多个OTP输入框自动填充不显示的问题
核心原因
iOS的AutoFill OTP功能是为单个输入框设计的。当你使用多个拆分的输入框时,系统无法识别如何将完整的OTP字符串分配到每个独立输入框中,因此不会触发键盘上方的自动填充提示。
解决方案
方案1:使用隐藏输入框接收OTP(推荐)
通过一个隐藏的UITextField接收系统自动填充的OTP,再将内容拆分到多个显示用的输入框中,兼顾自动填充和用户手动输入体验。
代码实现:
- 添加隐藏输入框并配置:
private let otpReceiverTextField = UITextField() override func viewDidLoad() { super.viewDidLoad() // 配置用于接收OTP的隐藏输入框 otpReceiverTextField.isHidden = true otpReceiverTextField.textContentType = .oneTimeCode otpReceiverTextField.keyboardType = .default otpReceiverTextField.addTarget(self, action: #selector(otpReceived(_:)), for: .editingChanged) view.addSubview(otpReceiverTextField) // 配置四个显示用的输入框:移除.oneTimeCode设置,保留数字键盘 [otp1, otp2, otp3, otp4].forEach { textField in textField?.keyboardType = .numberPad textField?.addTarget(self, action: #selector(didEdited), for: .editingChanged) } // 让隐藏输入框成为第一响应者,触发OTP自动填充提示 otpReceiverTextField.becomeFirstResponder() }
- 处理OTP自动填充后的拆分逻辑:
@objc private func otpReceived(_ textField: UITextField) { guard let otp = textField.text, otp.count == 4 else { return } // 将完整OTP拆分到四个输入框 otp1.text = String(otp[otp.startIndex]) otp2.text = String(otp[otp.index(after: otp.startIndex)]) otp3.text = String(otp[otp.index(otp.startIndex, offsetBy: 2)]) otp4.text = String(otp[otp.index(otp.startIndex, offsetBy: 3)]) // 填充完成后隐藏键盘 otpReceiverTextField.resignFirstResponder() }
- 修改原有
didEdited方法,同步手动输入到隐藏输入框:
@objc func didEdited(textField: UITextField) { let text = textField.text // 同步四个输入框的内容到隐藏输入框 let combinedOTP = (otp1.text ?? "") + (otp2.text ?? "") + (otp3.text ?? "") + (otp4.text ?? "") otpReceiverTextField.text = combinedOTP // 原有光标跳转逻辑保持不变 if text?.count == 1 { switch textField { case otp1: otp2.becomeFirstResponder() case otp2: otp3.becomeFirstResponder() case otp3: otp4.becomeFirstResponder() case otp4: otp4.resignFirstResponder() default: break } } if text?.count == 0 { switch textField { case otp1: otp1.becomeFirstResponder() case otp2: otp2.text = "" otp1.becomeFirstResponder() case otp3: otp3.text = "" otp2.becomeFirstResponder() case otp4: otp4.text = "" otp3.becomeFirstResponder() default: break } } }
方案2:手动监听短信通知提取OTP
如果必须保留多个独立输入框,可以通过监听系统短信通知,手动提取OTP并填充。
代码实现:
- 请求通知权限:
override func viewDidLoad() { super.viewDidLoad() // 请求通知权限以监听短信 UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, _ in if granted { DispatchQueue.main.async { UIApplication.shared.registerForRemoteNotifications() } } } // 原有输入框配置逻辑... }
- 添加通知监听与OTP提取:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) // 监听系统短信通知 NotificationCenter.default.addObserver(self, selector: #selector(extractOTPFromSMS(_:)), name: NSNotification.Name("NSSMSMessageNotification"), object: nil) } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) NotificationCenter.default.removeObserver(self, name: NSNotification.Name("NSSMSMessageNotification"), object: nil) } @objc private func extractOTPFromSMS(_ notification: Notification) { guard let userInfo = notification.userInfo, let smsContent = userInfo["message"] as? String else { return } // 正则匹配4位数字OTP(根据实际长度调整) let regexPattern = "\\d{4}" guard let regex = try? NSRegularExpression(pattern: regexPattern) else { return } let matches = regex.matches(in: smsContent, range: NSRange(smsContent.startIndex..., in: smsContent)) if let match = matches.first, let otpRange = Range(match.range, in: smsContent) { let otp = String(smsContent[otpRange]) // 填充到四个输入框 if otp.count == 4 { otp1.text = String(otp[0]) otp2.text = String(otp[1]) otp3.text = String(otp[2]) otp4.text = String(otp[3]) } } }
额外检查项
- 确认短信中的App名称与Xcode项目设置的Bundle Display Name完全一致,系统会通过名称匹配确定是否为当前App的OTP。
- 设备设置中,确保「密码自动填充」已开启,且勾选了「短信验证码」选项(iOS16及以上版本)。
- 不要在多个显示用输入框上设置
.oneTimeCode,仅在接收OTP的单个输入框上配置该属性。
内容的提问来源于stack exchange,提问作者Arish
相关产品推荐
相关产品推荐

