Xcode12.3下SwiftUI实现OTP输入框下一位虚线随输入变色
OTP输入框动态高亮实现方案
不需要使用FocusState,基于你现有的隐藏TextField统一接收输入的实现,直接通过已输入的验证码长度判断下一位高亮位置即可,完全适配Xcode 12.3版本。
核心改动点
- 给单个OTP位视图增加序号参数,传入当前已输入的验证码长度,动态计算底部虚线的颜色
- 新增TextField第一响应者管理逻辑,适配点击唤起键盘的交互
- 完全保留原有验证码输入限制、自动拆分等核心逻辑不需要修改
完整可运行代码
import SwiftUI class ViewModel: ObservableObject { @Published var otpField = "" { didSet { guard otpField.count <= 6, otpField.last?.isNumber ?? true else { otpField = oldValue return } } } var otp1: String { guard otpField.count >= 1 else { return "" } return String(Array(otpField)[0]) } var otp2: String { guard otpField.count >= 2 else { return "" } return String(Array(otpField)[1]) } var otp3: String { guard otpField.count >= 3 else { return "" } return String(Array(otpField)[2]) } var otp4: String { guard otpField.count >= 4 else { return "" } return String(Array(otpField)[3]) } var otp5: String { guard otpField.count >= 5 else { return "" } return String(Array(otpField)[4]) } var otp6: String { guard otpField.count >= 6 else { return "" } return String(Array(otpField)[5]) } @Published var borderColor: Color = .black @Published var isTextFieldDisabled = false var successCompletionHandler: (()->())? } // 用于控制TextField第一响应者的辅助视图 struct TextFieldResponder: UIViewRepresentable { @Binding var isFirstResponder: Bool func makeUIView(context: Context) -> UIView { let view = UIView(frame: .zero) return view } func updateUIView(_ uiView: UIView, context: Context) { DispatchQueue.main.async { guard let textField = uiView.superview?.superview as? UITextField else { return } if isFirstResponder && !textField.isFirstResponder { textField.becomeFirstResponder() } else if !isFirstResponder && textField.isFirstResponder { textField.resignFirstResponder() } } } } struct ContentView: View { @StateObject var viewModel = ViewModel() @State var isFocused = false let textBoxWidth = UIScreen.main.bounds.width / 8 let textBoxHeight = UIScreen.main.bounds.width / 8 let spaceBetweenBoxes: CGFloat = 10 let paddingOfBox: CGFloat = 1 var textFieldOriginalWidth: CGFloat { (textBoxWidth*6)+(spaceBetweenBoxes*3)+((paddingOfBox*2)*3) } var body: some View { VStack { ZStack { HStack (spacing: spaceBetweenBoxes){ otpText(text: viewModel.otp1, index: 1, currentCount: viewModel.otpField.count) otpText(text: viewModel.otp2, index: 2, currentCount: viewModel.otpField.count) otpText(text: viewModel.otp3, index: 3, currentCount: viewModel.otpField.count) otpText(text: viewModel.otp4, index: 4, currentCount: viewModel.otpField.count) otpText(text: viewModel.otp5, index: 5, currentCount: viewModel.otpField.count) otpText(text: viewModel.otp6, index: 6, currentCount: viewModel.otpField.count) } TextField("", text: $viewModel.otpField, onEditingChanged: { editing in isFocused = editing }) .frame(width: isFocused ? 0 : textFieldOriginalWidth, height: textBoxHeight) .disabled(viewModel.isTextFieldDisabled) .textContentType(.oneTimeCode) .foregroundColor(.clear) .accentColor(.clear) .background(Color.clear) .keyboardType(.numberPad) .background(TextFieldResponder(isFirstResponder: $isFocused)) } .onTapGesture { isFocused = true } } } private func otpText(text: String, index: Int, currentCount: Int) -> some View { return Text(text) .font(.title) .frame(width: textBoxWidth, height: textBoxHeight) .background(VStack{ Spacer() RoundedRectangle(cornerRadius: 1) .frame(height: 1) // 仅下一位的虚线显示为绿色,其他位为灰色 .foregroundColor(index == currentCount + 1 ? .green : .gray) }) .padding(paddingOfBox) } }
效果说明
- 初始状态下还未输入任何数字时,第2位虚线自动显示为绿色
- 输入第1位数字后,第3位虚线变为绿色,依次类推
- 输入满6位数字后无下一位,所有虚线保持灰色
- 点击OTP输入区域自动唤起键盘,符合普通输入框的交互习惯
内容的提问来源于stack exchange,提问作者Dreiohc
相关产品推荐
相关产品推荐

