Form for OTP entry with six single-character text fields
Your solution to this particular problem is fine in its own way. However when it comes to scaling, it will become quite messy. Since I don't see any scope of improvement/tweaking in your approach, I suggest an alternative solution.

As pointed out in the comments, you can get what you want with Outlet Collections, since they are basically an array of UIKit objects, thus making it easy to manipulate them for our specific case.



Step 1

First off, connect your textfields to an Outlet Collection, and delegates in Interface Builder itself (as shown in the image below), which gets rid of the redundant 

@IBOutlet var txtOTP1: UITextField!


and 

txtOTP1.delegate = self


statements.



@IBOutlet var textFieldsOutletCollection: [UITextField]!




Step 2

We are going to use a dictionary with the textfield as the key and its corresponding index in the outlet collection as the value:

var textFieldsIndexes:[UITextField:Int] = [:]

override func viewDidLoad() {
    for index in 0 ..&lt; textFieldsOutletCollection.count {
        textFieldsIndexes[textFieldsOutletCollection[index]] = index
    }
    super.viewDidLoad()
}




Step 3

The basic idea is, we can get the index of the textfield in our collection by supplying (to the dictionary) the textfield object we get in shouldChangeCharactersIn method, and then make the next or previous textfield the first responder:

enum Direction { case left, right }

func setNextResponder(_ index:Int?, direction:Direction) {

    guard let index = index else { return }

    if direction == .left {
        index == 0 ?
            (_ = textFieldsOutletCollection.first?.resignFirstResponder()) :
            (_ = textFieldsOutletCollection[(index - 1)].becomeFirstResponder())
    } else {
        index == textFieldsOutletCollection.count - 1 ?
            (_ = textFieldsOutletCollection.last?.resignFirstResponder()) :
            (_ = textFieldsOutletCollection[(index + 1)].becomeFirstResponder())
    }

}




Step 4

Now the shouldChangeCharactersIn method becomes more simple and clean:

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -&gt; Bool {
    if range.length == 0 {
        setNextResponder(textFieldsIndexes[textField], direction: .right)
        textField.text = string            
        return true
    } else if range.length == 1 {
        setNextResponder(textFieldsIndexes[textField], direction: .left)
        textField.text = ""            
        return false
    }
    return false
}




Final Code

With all the above code combined, your view controller should look like this:

import UIKit

class ViewController: UIViewController, UITextFieldDelegate {

    @IBOutlet var textFieldsOutletCollection: [UITextField]!

    var textFieldsIndexes:[UITextField:Int] = [:]

    override func viewDidLoad() {
        super.viewDidLoad()
        for index in 0 ..&lt; textFieldsOutletCollection.count {
            textFieldsIndexes[textFieldsOutletCollection[index]] = index
        }
    }

    enum Direction { case left, right }

    func setNextResponder(_ index:Int?, direction:Direction) {

        guard let index = index else { return }

        if direction == .left {
            index == 0 ?
                (_ = textFieldsOutletCollection.first?.resignFirstResponder()) :
                (_ = textFieldsOutletCollection[(index - 1)].becomeFirstResponder())
        } else {
            index == textFieldsOutletCollection.count - 1 ?
                (_ = textFieldsOutletCollection.last?.resignFirstResponder()) :
                (_ = textFieldsOutletCollection[(index + 1)].becomeFirstResponder())
        }

    }

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -&gt; Bool {

        if range.length == 0 {
            textField.text = string
            setNextResponder(textFieldsIndexes[textField], direction: .right)
            return true
        } else if range.length == 1 {
            textField.text = ""
            setNextResponder(textFieldsIndexes[textField], direction: .left)
            return false
        }

        return false

    }

}


This method should theoretically work for infinite number of UITextFields with same amount of code as opposed to your approach, in which the amount of code is dependent on the number of textfields used, which is not handsome/pretty.