SwiftUI实现逐位自动移位自定义数字TextField问题求解
3位联动数字输入TextField组件移位/退格逻辑实现
场景与需求
- 基于3个TextField搭建数字输入组件,从左到右排列为f3、f2、f1,f1为最右侧的首位输入位
- 已实现空态覆盖层:所有输入框为空时,拦截f2、f3的点击交互,点击覆盖层自动聚焦f1
- 待实现交互规则:
- 正向输入:输入第1个字符保留在f1;输入第2个字符时,原f1字符移位到f2,新输入字符留在f1;按该规则逐位左移;输入第4个字符时,自动替换f3中已有字符,始终保留最新输入的3位内容
- 退格逻辑:f1内容为空时按下删除键,f2字符自动回移到f1,f3字符自动回移到f2,逐位回退
现有代码的问题
原有实现仅处理了f1输入2位时向f2移位的单一场景,存在明显逻辑漏项:
- 移位没有做级联传递,字符到f2后不会继续向f3移动,无法实现满3位后新输入替换f3内容的效果
- 完全没有处理退格时的字符回移逻辑
- 写死
.disabled(f1.isEmpty)会导致f1被清空后,f2、f3直接锁死无法交互 - 没有做输入内容过滤,用户输入非数字字符会直接导致逻辑错乱
可直接运行的修复代码
// 补全原有代码缺失的焦点枚举定义 enum Field: Hashable { case f1, f2, f3 } struct TV1: View { @State var f1: String = "" @State var f2: String = "" @State var f3: String = "" @FocusState var ff: Field? func isFormEmpty() -> Bool { return f1.isEmpty && f2.isEmpty && f3.isEmpty } // 统一处理正向输入的级联移位 private func handleInputShift() { // 过滤非数字字符,限制f1最多暂存2位内容 f1 = String(f1.filter { $0.isNumber }.prefix(2)) guard f1.count == 2 else { return } let movedChar = f1.removeFirst() if f2.isEmpty { f2 = String(movedChar) } else { let f2Moved = f2.removeFirst() f2 = String(movedChar) // f3无论是否有值,直接接收f2移位过来的内容,实现新输入替换最老字符的效果 f3 = String(f2Moved) } } // 处理退格时的字符回移 private func handleBackspace() { guard f1.isEmpty, !f2.isEmpty || !f3.isEmpty else { return } if !f2.isEmpty { f1 = String(f2.removeFirst()) if !f3.isEmpty { f2 = String(f3.removeFirst()) } } } var body: some View { HStack(spacing: 12) { TextField("3", text: $f3) .font(.headline.weight(.semibold)) .keyboardType(.numberPad) .frame(width: 44, height: 44) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous)) .focused($ff, equals: .f3) .multilineTextAlignment(.center) // 仅全空状态下禁用高位输入框 .disabled(isFormEmpty()) TextField("2", text: $f2) .font(.headline.weight(.semibold)) .keyboardType(.numberPad) .frame(width: 44, height: 44) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous)) .focused($ff, equals: .f2) .multilineTextAlignment(.center) .disabled(isFormEmpty()) .onChange(of: f2) { _ in // 高位框输入后自动拉回焦点到f1,保持连续输入体验 ff = .f1 handleInputShift() } TextField("1", text: $f1) .font(.headline.weight(.semibold)) .keyboardType(.numberPad) .frame(width: 44, height: 44) .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 8, style: .continuous)) .focused($ff, equals: .f1) .multilineTextAlignment(.center) .onChange(of: f1) { newValue in // f1为空时检测到删除操作,触发回移 if newValue.isEmpty, f1.isEmpty { handleBackspace() return } f1 = String(f1.filter { $0.isNumber }) handleInputShift() } } .overlay ( Color.primary .opacity(isFormEmpty() && ff == nil ? 0.01 : 0) .onTapGesture { ff = .f1 } ) .onAppear { ff = .f1 } } }
逻辑验证说明
- 依次输入1、2、3、4,最终输入框显示为
2 3 4,最早输入的1被自动顶出,符合满位替换规则 - 连续按删除键,字符会从f1开始逐位回退,直到所有输入框清空触发空态拦截
- 输入过程中自动过滤非数字内容,不会因为输入字母、符号导致逻辑异常
- 非空状态下可以手动点击f2、f3编辑内容,编辑后自动回到f1焦点,不打断连续输入流程
内容的提问来源于stack exchange,提问作者Luis Ramirez
相关产品推荐
相关产品推荐

