You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 14:24:59