SwiftUI iOS:如何绕过系统键盘自动获取短信OTP填充自定义字段
在SwiftUI中用自定义键盘实现OTP自动填充
可以实现,核心思路是借助系统对oneTimeCode文本类型的支持,通过隐藏的原生UITextField接收OTP,再同步到自定义输入组件上。以下是具体实现步骤:
1. 封装支持OTP自动填充的自定义TextField组件
因为SwiftUI原生TextField的自定义能力有限,我们用UIViewRepresentable封装一个底层为UITextField的组件,专门用来接收系统OTP填充:
struct OTPTextField: UIViewRepresentable { @Binding var otp: String // 可选:OTP填充完成后的回调 var onOTPComplete: ((String) -> Void)? func makeUIView(context: Context) -> UITextField { let textField = UITextField() // 关键:告诉系统这是一次性验证码输入框 textField.textContentType = .oneTimeCode textField.keyboardType = .numberPad // 隐藏系统键盘,用自定义键盘替代 textField.inputView = UIView() textField.delegate = context.coordinator // 监听文本变化,同步到绑定变量 textField.addTarget(context.coordinator, action: #selector(Coordinator.textChanged(_:)), for: .editingChanged) return textField } func updateUIView(_ uiView: UITextField, context: Context) { uiView.text = otp } func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject, UITextFieldDelegate { let parent: OTPTextField init(_ parent: OTPTextField) { self.parent = parent } @objc func textChanged(_ textField: UITextField) { parent.otp = textField.text ?? "" // 如果OTP长度符合预期,触发完成回调 if parent.otp.count == 6 { parent.onOTPComplete?(parent.otp) } } // 限制输入长度,避免多余字符 func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let newLength = (textField.text?.count ?? 0) + string.count - range.length return newLength <= 6 } } }
2. 构建SwiftUI页面,整合自定义输入展示和键盘
把隐藏的OTPTextField、自定义输入框(比如6个方框)和自定义数字键盘组合起来:
struct OTPView: View { @State private var otp = "" @State private var isOTPFieldFocused = true // 控制隐藏TextField的焦点 var body: some View { VStack(spacing: 30) { // 自定义OTP输入展示(6个方框) HStack(spacing: 15) { ForEach(0..<6) { index in let char = otp.count > index ? String(otp[otp.index(otp.startIndex, offsetBy: index)]) : "" Text(char) .font(.title2) .frame(width: 50, height: 60) .border(.gray.opacity(0.5)) .cornerRadius(8) .background(otp.count == index ? Color.blue.opacity(0.1) : Color.clear) } } // 隐藏的OTPTextField,用来接收系统OTP填充 OTPTextField(otp: $otp) { completedOTP in print("OTP填充完成:\(completedOTP)") // 这里可以做验证、跳转等操作 } .frame(width: 0, height: 0) .opacity(0) .focused($isOTPFieldFocused) // 保持焦点,确保能接收OTP // 自定义数字键盘 VStack(spacing: 12) { ForEach(0..<3) { row in HStack(spacing: 20) { let startNum = row * 3 + 1 ForEach(startNum..<startNum+3) { num in Button("\(num)") { addDigit("\(num)") } .frame(width: 85, height: 85) .background(.gray.opacity(0.2)) .cornerRadius(12) .font(.title) } } } HStack(spacing: 20) { // 占位按钮 Button("") {} .frame(width: 85, height: 85) Button("0") { addDigit("0") } .frame(width: 85, height: 85) .background(.gray.opacity(0.2)) .cornerRadius(12) .font(.title) Button("←") { removeLastDigit() } .frame(width: 85, height: 85) .background(.gray.opacity(0.2)) .cornerRadius(12) .font(.title) } } } .padding() .onAppear { // 页面加载时自动让隐藏TextField获得焦点 DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { isOTPFieldFocused = true } } } // 自定义键盘输入逻辑 private func addDigit(_ digit: String) { guard otp.count < 6 else { return } otp.append(digit) } private func removeLastDigit() { guard !otp.isEmpty else { return } otp.removeLast() } }
原理说明
- 系统会识别标记为
textContentType(.oneTimeCode)的输入框,当收到包含OTP的短信时,自动提供填充选项,即使这个输入框被隐藏。 - 隐藏的TextField保持焦点状态,才能接收系统的OTP填充,填充后的内容通过绑定变量同步到自定义的输入展示组件上。
- 自定义数字键盘的输入直接修改
otp变量,同时也会同步到隐藏的TextField,保证双向数据一致。
注意事项
- 系统会自动处理短信权限,用户收到OTP短信时会弹出填充提示,无需额外申请权限。
- 部分场景下,用户可能需要下拉通知栏点击短信中的填充按钮触发自动填充,这是系统的安全机制。
内容的提问来源于stack exchange,提问作者Joseph
相关产品推荐
相关产品推荐

