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

SwiftUI验证码控件设计与功能实现的优化方案咨询

SwiftUI验证码输入优化:多文本框方案的光标切换与错误状态处理

我尝试用6个文本框实现SwiftUI验证码输入,但遇到不少问题:初始只能操作第一个文本框、第一响应者切换卡顿,怀疑多文本框是不是最优方案。需要实现的核心功能:

  • 光标流畅前后切换
  • 输入错误时所有文本框变红

原实现代码:

import SwiftUI

struct VerficationCode: View {
@State private var numberOfCells: Int = 6
@State private var currentlySelectedCell = 0

var body: some View {
HStack {
    Group {
        ForEach(0 ..< self.numberOfCells) { index in
            CharacterInputCell(currentlySelectedCell: self.$currentlySelectedCell, index: index)
        }
    }.frame(width:15,height: 56)
    .padding(.horizontal)
    .foregroundColor(.white)
    .cornerRadius(10)
    .keyboardType(.numberPad)
     }
   }
 }

struct CharacterInputCell: View {
@State private var textValue: String = ""
@Binding var currentlySelectedCell: Int

var index: Int

var responder: Bool {return index == currentlySelectedCell }

var body: some View {
 CustomTextField(text: $textValue, currentlySelectedCell: $currentlySelectedCell, isFirstResponder: responder)
    }
}

struct CustomTextField: UIViewRepresentable {

class Coordinator: NSObject, UITextFieldDelegate {

@Binding var text: String
@Binding var currentlySelectedCell: Int

var didBecomeFirstResponder = false

init(text: Binding<String>, currentlySelectedCell: Binding<Int>{
    _text = text
    _currentlySelectedCell = currentlySelectedCell
}

func textFieldDidChangeSelection(_ textField: UITextField) {
    DispatchQueue.main.async {
        self.text = textField.text ?? ""
    }
}

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    let currentText = textField.text ?? ""
    guard let stringRange = Range(range, in: currentText) else { return false }
    let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
    if updatedText.count <= 1 {
        self.currentlySelectedCell += 1
      }
       return updatedText.count <= 1
     }
  }

 @Binding var text: String
 @Binding var currentlySelectedCell: Int

 var isFirstResponder: Bool = false

 func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.delegate = context.coordinator
        textField.textAlignment = .center
        textField.keyboardType = .decimalPad
        return textField
 }

func makeCoordinator() -> CustomTextField.Coordinator {
       return Coordinator(text: $text, currentlySelectedCell: $currentlySelectedCell)
}

func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
uiView.text = text
if isFirstResponder && !context.coordinator.didBecomeFirstResponder  {
    uiView.becomeFirstResponder()
    context.coordinator.didBecomeFirstResponder = true
          }
      }
  }

问题分析与优化方案

多文本框是验证码输入的成熟方案,你的问题主要出在响应者管理逻辑缺失、状态分散、错误状态未统一控制上,以下是针对性优化:

1. 核心优化代码

import SwiftUI

struct VerificationCodeView: View {
    @State private var numberOfCells: Int = 6
    @State private var currentlySelectedCell = 0
    @State private var codeValues = Array(repeating: "", count: 6)
    @State private var isError = false // 全局错误状态标记

    var body: some View {
        HStack(spacing: 8) {
            ForEach(0 ..< numberOfCells) { index in
                CharacterInputCell(
                    textValue: $codeValues[index],
                    currentlySelectedCell: $currentlySelectedCell,
                    index: index,
                    isError: $isError
                )
                .frame(width: 40, height: 56)
                .border(isError ? .red : .gray, cornerRadius: 10) // 错误时显示红色边框
            }
        }
        .keyboardType(.numberPad)
        .onAppear {
            currentlySelectedCell = 0 // 初始聚焦第一个输入框
        }
        // 示例:输入完成后触发验证逻辑
        .onChange(of: codeValues) { newValue in
            let fullCode = newValue.joined()
            if fullCode.count == 6 {
                // 替换为实际验证码验证逻辑
                isError = fullCode != "123456"
            } else {
                isError = false
            }
        }
    }
}

struct CharacterInputCell: View {
    @Binding var textValue: String
    @Binding var currentlySelectedCell: Int
    @Binding var isError: Bool
    let index: Int

    var isResponder: Bool { index == currentlySelectedCell }

    var body: some View {
        CustomTextField(
            text: $textValue,
            currentlySelectedCell: $currentlySelectedCell,
            index: index,
            isFirstResponder: isResponder
        )
    }
}

struct CustomTextField: UIViewRepresentable {
    @Binding var text: String
    @Binding var currentlySelectedCell: Int
    let index: Int
    var isFirstResponder: Bool = false

    class Coordinator: NSObject, UITextFieldDelegate {
        @Binding var text: String
        @Binding var currentlySelectedCell: Int
        let index: Int

        init(text: Binding<String>, currentlySelectedCell: Binding<Int>, index: Int) {
            _text = text
            _currentlySelectedCell = currentlySelectedCell
            self.index = index
        }

        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            let currentText = textField.text ?? ""
            guard let stringRange = Range(range, in: currentText) else { return false }
            let updatedText = currentText.replacingCharacters(in: stringRange, with: string)

            // 输入字符时跳转到下一个输入框
            if !string.isEmpty {
                text = string
                if index < 5 {
                    currentlySelectedCell = index + 1
                }
            } else {
                // 删除字符时跳转到上一个输入框
                text = ""
                if index > 0 {
                    currentlySelectedCell = index - 1
                }
            }
            return updatedText.count <= 1
        }

        // 点击输入框时切换当前选中项
        func textFieldDidBeginEditing(_ textField: UITextField) {
            currentlySelectedCell = index
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(text: $text, currentlySelectedCell: $currentlySelectedCell, index: index)
    }

    func makeUIView(context: Context) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.delegate = context.coordinator
        textField.textAlignment = .center
        textField.keyboardType = .numberPad
        textField.backgroundColor = .white
        return textField
    }

    func updateUIView(_ uiView: UITextField, context: Context) {
        uiView.text = text
        // 实时同步响应者状态
        if isFirstResponder {
            uiView.becomeFirstResponder()
        } else {
            uiView.resignFirstResponder()
        }
    }
}

// 预览
struct VerificationCodeView_Previews: PreviewProvider {
    static var previews: some View {
        VerificationCodeView()
            .padding()
            .background(Color.gray.opacity(0.1))
    }
}

2. 关键优化点说明

  • 统一状态管理:用codeValues数组存储所有输入框内容,替代每个输入框独立的@State,便于统一验证和状态同步
  • 流畅光标切换:
    • 新增删除字符时的向前跳转逻辑
    • 点击输入框时自动切换当前选中项
    • 移除原代码中didBecomeFirstResponder的错误标记逻辑,实时同步响应者状态
  • 错误状态处理:添加全局isError变量,统一控制所有输入框的边框颜色,验证逻辑触发时一键更新状态
  • 布局修复:调整输入框尺寸和间距,修复原代码中Group的frame设置错误,优化视觉效果

内容的提问来源于stack exchange,提问作者Sham Dhiman

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 09:20:36