I tried implementing it with six text fields but found a number of problems as a lot of work, blocking all but the first text field for initial input, the laggy move of the first responder, and whatnot, which made me wonder if having 6 text fields are really the best approach.
The hard part is the functionality (i.e the cursor moving smoothly, getting back and forth, making all of them red when input is wrong, etc) How could I achieve such behavior/functionality?
Screenshot:-

Code Below:-
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
}
}
}
Can someone please explain to me How could I achieve such behavior/functionality? I've tried to implement but no results yet.
Any help would be greatly appreciated.
Thanks in advance.
Ok this is not a fully working solution but an idea that can get you there if you choose to use SwiftUI.
First, you have this enum to use as focus state
Then, here's the general idea for the
VerificationCodeTextField(which will implement what you want with a set number of digits)The
.onChange(of: cells[index])function and theupdateFocusfunction are what you have to now implement. The general idea I had is this.onChangetextcells[index]should have at most 1 charactercells[index]has 1 character, every previous indices should also have a character0)digitscode, edge case oftext = "", etc).updateFocusThis should essentially bring the focus to the
focusIndex. That way, the user is always typing from there. Remember,focusIndexshould be the last cell with 1 character.Final Notes
Ok, I haven't actually implemented this fully so I could be missing something. But I believe this should work.
Also, Note the view methods above:
That is coming from a system I essentially built up but these are all what they sound like: