SwiftUI自定义OTP视图:输入框焦点自动切换异常求助
SwiftUI OTP 输入框焦点自动切换解决方案
问题根源
你的原代码中每个CustomOtpFieldView独立管理自己的@FocusState,导致无法跨视图控制焦点切换,依赖延迟的DispatchQueue也容易出现时序问题。下面是修复后的完整实现:
修复后的父视图
struct ContentView: View { private let fieldsCount = 6 @State private var otpFields: [String] @FocusState private var focusedField: Int? // 统一管理全局焦点索引 init() { _otpFields = State(initialValue: Array(repeating: "", count: fieldsCount)) } var body: some View { HStack(spacing: 10) { ForEach(0..<fieldsCount, id: \.self) { index in CustomOtpFieldView( text: $otpFields[index], focusedField: $focusedField, index: index, totalFields: fieldsCount ) } } .onAppear { focusedField = 0 // 初始聚焦第一个输入框 } } }
修复后的子视图
struct CustomOtpFieldView: View { @Binding var text: String @Binding var focusedField: Int? var index: Int var totalFields: Int var body: some View { TextField("", text: $text) .focused($focusedField, equals: index) // 绑定到父视图的全局焦点索引 .keyboardType(.numberPad) .textContentType(.oneTimeCode) .frame(width: 50, height: 50) .multilineTextAlignment(.center) .background(RoundedRectangle(cornerRadius: 5).stroke(Color.green)) .onChange(of: text) { newValue in // 过滤非数字字符 let filtered = newValue.filter { $0.isNumber } if filtered != newValue { text = filtered } // 限制输入长度为1 if text.count > 1 { text = String(text.prefix(1)) } handleFieldUpdate() } } private func handleFieldUpdate() { if text.count == 1 && index < totalFields - 1 { // 输入完成后自动跳转到下一个输入框 focusedField = index + 1 } else if text.isEmpty && index > 0 { // 删除内容后自动回退到上一个输入框 focusedField = index - 1 } } }
关键修改说明
- 全局焦点管理:将焦点状态提升到父视图,用
focusedField变量跟踪当前聚焦的输入框索引,子视图通过.focused($focusedField, equals: index)绑定到对应位置,彻底解决跨视图焦点控制问题。 - 移除冗余延迟:原代码中的
DispatchQueue延迟是因为独立焦点状态的时序冲突,统一管理后直接修改focusedField即可即时切换焦点。 - 增加回退逻辑:补充了删除字符时的焦点回退处理,符合用户常规交互习惯。
- 简化输入过滤:用
$0.isNumber替代硬编码字符集,代码更简洁且扩展性更好。
内容的提问来源于stack exchange,提问作者Nicolas
相关产品推荐
相关产品推荐

