使用 TextField 为 SwiftUI 创建 OTP 页面

Creating OTP page for SwiftUI Using TextField

我正在尝试为我的应用程序创建一个 OTP 页面,但我不知道如何在第一个文本字段中输入一个数字后使下一个文本字段成为焦点。

我为 OTP 的每个数字创建了 6 个文本字段。一旦我输入第一个文本字段中的一个数字,下一个文本字段应该是第一响应者,依此类推,直到所有 6 位数字都完成。

我不确定如何在 Swift UI 中做到这一点。到目前为止,我设法只创建了 6 行,如屏幕截图所示。预期每行应该只有一个数字。因此,一旦我输入一个整数,下一个文本字段就应该成为焦点。

我尝试了其他 post,比如使用 @FocusState,但它说未知属性。

我也尝试了自定义文本字段 如何移动到 SwiftUI 中的下一个文本字段? 但我似乎无法让它工作。


import SwiftUI


struct ContentView: View {
    
    
    @State private var OTP1 = ""
    @State private var OTP2 = ""
    @State private var OTP3 = ""
    @State private var OTP4 = ""
    @State private var OTP5 = ""
    @State private var OTP6 = ""
    
    
    var body: some View {
        
        VStack {
            
            HStack(spacing: 16) {
                VStack {
                    TextField("", text: $OTP1)
                    Line()
                        .stroke(style: StrokeStyle(lineWidth: 1))
                        .frame(width: 41, height: 1)
                }
                
                VStack {
                    TextField("", text: $OTP2)
                    Line()
                        .stroke(style: StrokeStyle(lineWidth: 1))
                        .frame(width: 41, height: 1)
                }
                
                VStack {
                    TextField("", text: $OTP3)
                    Line()
                        .stroke(style: StrokeStyle(lineWidth: 1))
                        .frame(width: 41, height: 1)
                }
                
                VStack {
                    TextField("", text: $OTP4)
                    Line()
                        .stroke(style: StrokeStyle(lineWidth: 1))
                        .frame(width: 41, height: 1)
                }
                
                VStack {
                    TextField("", text: $OTP5)
                    Line()
                        .stroke(style: StrokeStyle(lineWidth: 1))
                        .frame(width: 41, height: 1)
                }
                
                VStack {
                    TextField("", text: $OTP6)
                    Line()
                        .stroke(style: StrokeStyle(lineWidth: 1))
                        .frame(width: 41, height: 1)
                }
            }
            
        }
    }
}

struct Line: Shape {
    func path(in rect: CGRect) -> Path {
        var path = Path()
        path.move(to: CGPoint(x: 0, y: 0))
        path.addLine(to: CGPoint(x: rect.width, y: 0))
        return path
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewLayout(.fixed(width: 560, height: 50))
    }
}

My OTP Page

Expected field

这是我对 iOS 14.

的回答

景色。


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)
                          otpText(text: viewModel.otp2)
                          otpText(text: viewModel.otp3)
                          otpText(text: viewModel.otp4)
                          otpText(text: viewModel.otp5)
                          otpText(text: viewModel.otp6)
                      }
                      
                      
                      TextField("", text: $viewModel.otpField)
                      .frame(width: isFocused ? 0 : textFieldOriginalWidth, height: textBoxHeight)
                      .disabled(viewModel.isTextFieldDisabled)
                      .textContentType(.oneTimeCode)
                      .foregroundColor(.clear)
                      .accentColor(.clear)
                      .background(Color.clear)
                      .keyboardType(.numberPad)
                  }
          }
      }
      
      private func otpText(text: String) -> some View {
          
          return Text(text)
              .font(.title)
              .frame(width: textBoxWidth, height: textBoxHeight)
              .background(VStack{
                Spacer()
                RoundedRectangle(cornerRadius: 1)
                    .frame(height: 0.5)
               })
              .padding(paddingOfBox)
      }
}

这是视图模型。

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: (()->())?
    
    @Published var showResendText = false

}

不是很可重用,但它有效.... 如果您想更改长度,请不要忘记更新 viewModel 的 otpFielddidSet 和视图 textFieldOriginalWidth.

这里的想法是隐藏 TextField 并让用户看起来像是在输入框中。

一个想法可能是在用户输入时通过使用 TextField 中的 isEditing 闭包来缩小 TextField。您可能希望缩小它,以便用户无法粘贴文本或获取“弹出窗口”或文本字段光标。