Home > Software design >  Sms Verification Code Design and Functionality in SwiftUI
Sms Verification Code Design and Functionality in SwiftUI

Time:09-06

I tried implementing it with six textfields but found a number of problems as a lot of work, blocking all but the first textfield for initial input, laggy move of firstResponder and whatnot, what made me wonder if having 6 textfields is 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 behaviour/functionality?

Screenshot:-

Design

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 behaviour/functionality?, I've tried to implement but no results yet.

Any help would be greatly appreciated.

Thanks in advance.

CodePudding user response:

Code:-

import SwiftUI
public struct ContentView: View {

var maxDigits: Int = 6
var label = "Enter One Time Password"
@State var pin: String = ""
@State var showPin = true
var handler: (String, (Bool) -> Void) -> Void
public var body: some View {
    VStack {
        Text(label).font(.title)
        ZStack {
            pinDots
            backgroundField
        }
    }
}
private var pinDots: some View {
    HStack {
        Spacer()
        ForEach(0..<maxDigits) { index in
            Image(systemName: self.getImageName(at: index))
                .font(.system(size: 60, weight: .thin, design: .default))
            Spacer()
        }
    }
}

private func getImageName(at index: Int) -> String {
    if index >= self.pin.count {
        return "square"
    }
    if self.showPin {
        return self.pin.digits[index].numberString   ".square"
    }
    return "square"
}

private var backgroundField: some View {
    let boundPin = Binding<String>(get: { self.pin }, set: { newValue in
        self.pin = newValue
        self.submitPin()
    })
    
    return TextField("", text: boundPin, onCommit: submitPin)
        .accentColor(.clear)
        .foregroundColor(.clear)
        .keyboardType(.numberPad)
}


private var showPinButton: some View {
    Button(action: {
        self.showPin.toggle()
    }, label: {
        self.showPin ?
            Image(systemName: "eye.slash.fill").foregroundColor(.primary) :
            Image(systemName: "eye.fill").foregroundColor(.primary)
    })
}

private func submitPin() {
    if pin.count == maxDigits {
        handler(pin) { isSuccess in
            if isSuccess {
                print("pin matched, go to next page, no action to perfrom here")
            } else {
                pin = ""
                print("this has to called after showing toast why is the failure")
              }
           }
        }
    }

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        //maxDigits =  Set According to your condition
        //label =  Set Title
        //Pin Count
        ContentView(maxDigits: 6, label: "A", pin: "6", showPin: true) { strng, reponsetrue in
            
        }
    }
}


}
extension String {
var digits: [Int] {
    var result = [Int]()
    for char in self {
        if let number = Int(String(char)) {
            result.append(number)
        }
    }
    return result
   }
}

extension Int {

var numberString: String {
    
    guard self < 10 else { return "0" }
    
    return String(self)
   }
}

Reference Link

  • Related