Build an iOS front end to verify a numeric code using RxSwift

There are use cases, commonly related with passwordless authentication or second-factor authentication where iOS users are required to enter some 4-digits or 6-digits numeric code that they received in an email or text to their phones.

Image for post
Image for post

Here are the requirements of what we are going to build:

  1. There are 6-digits numeric code, so there are 6 large 'boxes’. Each box can only contain 1 single digit.
  2. After the users enter a digit in a box, the cursor automatically jumps to the next.
  3. The users can place the cursor at any box to make a correction. As soon as they tap on a box, the box goes empty. The user can then enter a new digit.
  4. At any given time, if all the boxes are completely filled in, the Verify button becomes enabled. If any box is empty, the Verify button is disabled.

Storyboard

I called it a ‘box’ but it’s just a TextField on top of a View that has a height of 1 unit. I then placed both of them inside a vertical StackView (meaning the axis is vertical). Then I duplicated 6 times to end up with 6 of these instances of StackView. Now it’s like playing with Lego, I placed all 6 instances of these StackView inside another StackView. The outer StackView is a horizontal one. This is a screenshot of how the ViewController looks in a Storyboard:

Image for post
Image for post

Some of the key attributes of the inner StackViews are:

Axis: Vertical
Alignment: Fill
Distribution: Equal Spacing
Spacing: 1

and of the outer StackView:

Axis: Horizontal
Alignment: Bottom
Distribution: Fill Equally
Spacing: 7

Some key attributes of the digit TextField:

Font: System 48.0
Border Style: None
Keyboard Type: Number Pad

The rest of the works in Storyboard involves playing with placing the right constraints.

Before we leave the Storyboard, create a Button with the label called Verify. Set its enabled attribute to false and alpha to 0.25. We want the Verify button to appear disabled by default.

Code

The first requirement is that each box which is a TextField can only contain 1 digit. The following answer on StackOverflow is just the right solution. So let’s create a file called LimitLengthTextField.swift with the following content:

Go back to Storyboard and make sure we set the Max Length of the TextField to 1, just as the above StackOverflow answer describes.

Next, we are ready to add RxSwift and RxCocoa to the Podfile:

Then import them to the ViewController class (remember to connect these IBOutlet to the Storyboard):

Now we will start adding some non-RxSwift code:

  1. We define an array of UITextField,( var digitTextFields: [UITextField] = [])from 6 individual TextField members. In ViewDidLoad(), we will populate the array as follow:
digitTextFields = [firstDigitTextField, secondDigitTextField,
thirdDigitTextField, fourthDigitTextField,
fifthDigitTextField, sixthDigitTextField]

3. Since we want to center each digit when it gets entered in the box, we add the following code

for digitTextField in digitTextFields {
digitTextField.textAlignment = .center
digitTextField.contentVerticalAlignment = .center
}

4. Create a helper method to enable or disable a button.

func enableButton(button: UIButton, enabled:Bool) {
button.isEnabled = enabled
button.alpha = enabled ? 1.0 : 0.25
}

We have the following code at this point:

5. Next, we get to the exciting part of adding RxSwift code. Instantiate a member variable called disposeBag. This is for the purpose of memory management in RxSwift to prevent memory leaks.

var digitTextFields: [UITextField] = []
let disposeBag = DisposeBag()

6. So far, disposeBag is still only for housekeeping purpose. Now to satisfy the requirement “After entering a digit, make cursor jumps to the next box”, we will need some data structure.

7. The following data structure is an array of tuple. Each tuple contains a TextField (which is aka box), and a potential next responder. For example, the first TextField has the next responder which is the second TextField. This means after a digit is entered in the first TextField, the cursor will automatically jumps to the second TextField. Another example, the sixth TextField do not have any next responder. Once a digit is entered in the sixth TextField, we just want to dismiss the number pad keyboard. In code,

let responders: [(digitTextField: UITextField, 
potentialNextResponder: UITextField?)] = [
(firstDigitTextField, secondDigitTextField),
(secondDigitTextField, thirdDigitTextField),
(thirdDigitTextField, fourthDigitTextField),
(fourthDigitTextField, fifthDigitTextField),
(fifthDigitTextField, sixthDigitTextField),
(sixthDigitTextField, nil)
]

8. RxSwift has a UIControl.Event called .editingChanged. For each TextField, we want to create an Observable from these asynchronous events. We will create this Observable and subscribe right away. In OnNext handler, we will do different things depending on whether there is a next responder. If there is a next responder, then the next responder becomes the first responder. If there is not, we just simply the first responder from the TextField. The above sounds horrible, but in code it actually is quite easy to follow:

9. For the next requirement, “Clear the digit if box is tapped on”, the code is very similar to the one above, but with 2 differences. The event we are interested is UIControl.Event.editingDidBegin . The second difference is in OnNext handler, we clear the TextField and disable the Verify button. In code:

10. For the last requirement, “Anytime, there’s an empty box, disable the Verify button. Else, enable the button”, when each TextField has its text changed, we can create an Observable from that.

let digitObservables = digitTextFields.map { $0.rx.text.map { $0 ?? "" } }

We will use the Rx operator combineLatest to create a resulting Observable that we subscribe to. In the OnNext handler, if any of TextField is empty, we set the Verify to false, otherwise true.

TLDR

Here’s the complete file

Conclusion

RxSwift is quite amazing. It allows a rather set of complex requirements to be expressed in a quite concise way. Anyway, that’s how we can build the front end of a numeric code verification in iOS using RxSwift.

Written by

Driven by passion and patience. Read my shorter posts https://dev.to/codeprototype (possibly duplicated from here but not always)

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store